ee.ria.xroad.opmonitordaemon.QueryRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for ee.ria.xroad.opmonitordaemon.QueryRequestHandler.java

Source

/**
 * The MIT License
 * Copyright (c) 2016 Estonian Information System Authority (RIA), Population Register Centre (VRK)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package ee.ria.xroad.opmonitordaemon;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.zip.GZIPOutputStream;
import javax.activation.DataHandler;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.attachment.AttachmentMarshaller;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import com.google.gson.Gson;

import com.sun.istack.ByteArrayDataSource;
import com.sun.xml.bind.api.AccessorException;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang3.StringUtils;

import org.xml.sax.SAXException;

import ee.ria.xroad.common.CodedException;
import ee.ria.xroad.common.message.JaxbUtils;
import ee.ria.xroad.common.message.SoapMessageEncoder;
import ee.ria.xroad.common.message.SoapMessageImpl;
import ee.ria.xroad.common.message.SoapUtils;
import ee.ria.xroad.common.util.JsonUtils;
import ee.ria.xroad.common.util.ResourceUtils;
import ee.ria.xroad.opmonitordaemon.message.ObjectFactory;

import static ee.ria.xroad.common.ErrorCodes.*;
import static ee.ria.xroad.common.util.MimeUtils.HEADER_CONTENT_ID;
import static ee.ria.xroad.common.util.MimeUtils.HEADER_CONTENT_TRANSFER_ENCODING;

/**
 * Base class for operational daemon query request handlers.
 */
@Slf4j
abstract class QueryRequestHandler {

    static final ObjectFactory OBJECT_FACTORY = new ObjectFactory();
    static final Gson GSON = JsonUtils.getSerializer();

    private static final JAXBContext JAXB_CTX = initJaxbCtx();
    private static final Schema OP_MONITORING_SCHEMA = createSchema();

    /**
     * Handle the given request and write the response in the provided output
     * stream.
     * @param requestSoap the request SOAP message
     * @param out the output stream for writing the response
     * @param contentTypeCallback function to call when response content type
     * is available (before writing to output stream)
     */
    abstract void handle(SoapMessageImpl requestSoap, OutputStream out, Consumer<String> contentTypeCallback)
            throws Exception;

    private static JAXBContext initJaxbCtx() {
        try {
            return JAXBContext.newInstance(ObjectFactory.class);
        } catch (JAXBException e) {
            throw new RuntimeException(e);
        }
    }

    private static Schema createSchema() {
        try {
            return SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
                    .newSchema(ResourceUtils.getClasspathResource("op-monitoring.xsd"));
        } catch (SAXException e) {
            throw new CodedException(X_INTERNAL_ERROR, e);
        }
    }

    private static Unmarshaller createUnmarshaller(Class<?> clazz) throws Exception {
        Unmarshaller unmarshaller = JaxbUtils.createUnmarshaller(clazz);
        unmarshaller.setEventHandler(QueryRequestHandler::validationFailed);
        unmarshaller.setSchema(OP_MONITORING_SCHEMA);

        return unmarshaller;
    }

    private static boolean validationFailed(ValidationEvent event) {
        if (event.getSeverity() == ValidationEvent.FATAL_ERROR) {
            return false;
        }

        if (event.getSeverity() == ValidationEvent.ERROR && event.getLinkedException() instanceof AccessorException
                && event.getLinkedException().getCause() instanceof CodedException) {
            return false;
        }

        return true;
    }

    @SuppressWarnings("unchecked")
    static <T> T getRequestData(SoapMessageImpl requestSoap, Class<?> clazz) throws Exception {
        Unmarshaller unmarshaller = createUnmarshaller(clazz);

        try {
            return (T) unmarshaller.unmarshal(SoapUtils.getFirstChild(requestSoap.getSoap().getSOAPBody()), clazz)
                    .getValue();
        } catch (UnmarshalException e) {
            throw new CodedException(X_INVALID_REQUEST, e.getLinkedException()).withPrefix(CLIENT_X);
        }
    }

    static SoapMessageImpl createResponse(SoapMessageImpl requestMessage, JAXBElement<?> jaxbElement)
            throws Exception {
        return createResponse(requestMessage, createMarshaller(), jaxbElement);
    }

    static SoapMessageImpl createResponse(SoapMessageImpl requestMessage, Marshaller marshaller,
            JAXBElement<?> jaxbElement) throws Exception {
        return SoapUtils.toResponse(requestMessage, soap -> {
            soap.getSOAPBody().removeContents();
            marshaller.marshal(jaxbElement, soap.getSOAPBody());
        });
    }

    private static Marshaller createMarshaller() throws Exception {
        return createMarshaller(null);
    }

    static Marshaller createMarshaller(AttachmentMarshaller attachmentMarshaller) throws Exception {
        Marshaller marshaller = JAXB_CTX.createMarshaller();

        marshaller.setAttachmentMarshaller(attachmentMarshaller);
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);

        return marshaller;
    }

    static DataHandler createAttachmentDataSource(byte[] payload, String cid, String contentType) {
        return new DataHandler(new ByteArrayDataSource(payload, contentType)) {
            @Override
            public String getName() {
                return cid;
            }
        };
    }

    static byte[] compress(String data) throws IOException {
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);

        try (ByteArrayOutputStream bos = new ByteArrayOutputStream(dataBytes.length)) {
            GZIPOutputStream gzip = new GZIPOutputStream(bos);
            gzip.write(dataBytes);
            gzip.close();

            return bos.toByteArray();
        }
    }

    private static Map<String, String> getAdditionalAttachmentHeaders(String cid) {
        Map<String, String> additionalHeaders = new HashMap<>();

        additionalHeaders.put(HEADER_CONTENT_TRANSFER_ENCODING, "binary");
        additionalHeaders.put(HEADER_CONTENT_ID, "<" + cid + ">");

        return additionalHeaders;
    }

    @RequiredArgsConstructor
    protected static final class SoapEncoderAttachmentMarshaller extends AttachmentMarshaller {
        private static final String CID_PREFIX = "cid:";

        private final SoapMessageEncoder responseEncoder;

        private final Map<String, DataHandler> attachments = new HashMap<>();

        void encodeAttachments() throws Exception {
            for (Entry<String, DataHandler> attach : attachments.entrySet()) {
                responseEncoder.attachment(attach.getValue().getContentType(), attach.getValue().getInputStream(),
                        getAdditionalAttachmentHeaders(attach.getKey()));
            }
        }

        @Override
        public String addSwaRefAttachment(DataHandler data) {
            String name = data.getName();

            if (StringUtils.isEmpty(name)) {
                name = UUID.randomUUID().toString();
            }

            attachments.put(name, data);

            return CID_PREFIX + name;
        }

        @Override
        public String addMtomAttachment(DataHandler data, String elementNamespace, String elementLocalName) {
            // not using MTOM attachments
            return null;
        }

        @Override
        public String addMtomAttachment(byte[] data, int offset, int length, String mimeType,
                String elementNamespace, String elementLocalName) {
            // not using MTOM attachments
            return null;
        }
    }
}