org.n52.wps.server.handler.RequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.n52.wps.server.handler.RequestHandler.java

Source

/**
 * Copyright (C) 2007 - 2014 52North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * If the program is linked with libraries which are licensed under one of
 * the following licenses, the combination of the program with the linked
 * library is not considered a "derivative work" of the program:
 *
 *        Apache License, version 2.0
 *        Apache Software License, version 1.0
 *        GNU Lesser General Public License, version 3
 *        Mozilla Public License, versions 1.0, 1.1 and 2.0
 *        Common Development and Distribution License (CDDL), version 1.0
 *
 * Therefore the distribution of the program linked with libraries licensed
 * under the aforementioned licenses, is permitted by the copyright holders
 * if the distribution is compliant with both the GNU General Public
 * License version 2 and the aforementioned licenses.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 */
package org.n52.wps.server.handler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.n52.wps.server.ExceptionReport;
import org.n52.wps.server.WebProcessingService;
import org.n52.wps.server.request.CapabilitiesRequest;
import org.n52.wps.server.request.DescribeProcessRequest;
import org.n52.wps.server.request.ExecuteRequest;
import org.n52.wps.server.request.Request;
import org.n52.wps.server.request.RetrieveResultRequest;
import org.n52.wps.server.response.ExecuteResponse;
import org.n52.wps.server.response.Response;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

/**
 * This class accepts client requests, determines its type and then schedules
 * the {@link ExecuteRequest}'s for execution. The request is executed for a
 * short time, within the client will be served with an immediate result. If the
 * time runs out, the client will be served with a reference to the future
 * result. The client can come back later to retrieve the result. Uses
 * "computation_timeout_seconds" from wps.properties
 * 
 * @author Timon ter Braak
 */
public class RequestHandler {

    public static final String VERSION_ATTRIBUTE_NAME = "version";

    /** Computation timeout in seconds */
    protected static RequestExecutor pool = new RequestExecutor();

    protected OutputStream os;

    private static Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);

    protected String responseMimeType;

    protected Request req;

    // Empty constructor due to classes which extend the RequestHandler
    protected RequestHandler() {

    }

    /**
     * Handles requests of type HTTP_GET (currently capabilities and
     * describeProcess). A Map is used to represent the client input.
     * 
     * @param params
     *            The client input
     * @param os
     *            The OutputStream to write the response to.
     * @throws ExceptionReport
     *             If the requested operation is not supported
     */
    public RequestHandler(Map<String, String[]> params, OutputStream os) throws ExceptionReport {
        this.os = os;
        //sleepingTime is 0, by default.
        /*if(WPSConfiguration.getInstance().exists(PROPERTY_NAME_COMPUTATION_TIMEOUT)) {
           this.sleepingTime = Integer.parseInt(WPSConfiguration.getInstance().getProperty(PROPERTY_NAME_COMPUTATION_TIMEOUT));
        }
        String sleepTime = WPSConfig.getInstance().getWPSConfig().getServer().getComputationTimeoutMilliSeconds();
        */

        Request req;
        CaseInsensitiveMap ciMap = new CaseInsensitiveMap(params);

        /*
         * check if service parameter is present and equals "WPS"
         * otherwise an ExceptionReport will be thrown
         */
        String serviceType = Request.getMapValue("service", ciMap, true);

        if (!serviceType.equalsIgnoreCase("WPS")) {
            throw new ExceptionReport("Parameter <service> is not correct, expected: WPS, got: " + serviceType,
                    ExceptionReport.INVALID_PARAMETER_VALUE, "service");
        }

        /*
         * check language. if not supported, return ExceptionReport
         * Fix for https://bugzilla.52north.org/show_bug.cgi?id=905
         */
        String language = Request.getMapValue("language", ciMap, false);

        if (language != null) {
            Request.checkLanguageSupported(language);
        }

        // get the request type
        String requestType = Request.getMapValue("request", ciMap, true);

        if (requestType.equalsIgnoreCase("GetCapabilities")) {
            req = new CapabilitiesRequest(ciMap);
        } else if (requestType.equalsIgnoreCase("DescribeProcess")) {
            req = new DescribeProcessRequest(ciMap);
        } else if (requestType.equalsIgnoreCase("Execute")) {
            req = new ExecuteRequest(ciMap);
            setResponseMimeType((ExecuteRequest) req);
        } else if (requestType.equalsIgnoreCase("RetrieveResult")) {
            req = new RetrieveResultRequest(ciMap);
        } else {
            throw new ExceptionReport(
                    "The requested Operation is not supported or not applicable to the specification: "
                            + requestType,
                    ExceptionReport.OPERATION_NOT_SUPPORTED, requestType);
        }

        this.req = req;
    }

    /**
     * Handles requests of type HTTP_POST (currently executeProcess). A Document
     * is used to represent the client input. This Document must first be parsed
     * from an InputStream.
     * 
     * @param is
     *            The client input
     * @param os
     *            The OutputStream to write the response to.
     * @throws ExceptionReport
     */
    public RequestHandler(InputStream is, OutputStream os) throws ExceptionReport {
        String nodeName, localName, nodeURI, version = null;
        Document doc;
        this.os = os;

        boolean isCapabilitiesNode = false;

        try {
            System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
                    "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl");

            DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance();
            fac.setNamespaceAware(true);

            // parse the InputStream to create a Document
            doc = fac.newDocumentBuilder().parse(is);

            // Get the first non-comment child.
            Node child = doc.getFirstChild();
            while (child.getNodeName().compareTo("#comment") == 0) {
                child = child.getNextSibling();
            }
            nodeName = child.getNodeName();
            localName = child.getLocalName();
            nodeURI = child.getNamespaceURI();
            Node versionNode = child.getAttributes().getNamedItem("version");

            /*
             * check for service parameter. this has to be present for all requests
             */
            Node serviceNode = child.getAttributes().getNamedItem("service");

            if (serviceNode == null) {
                throw new ExceptionReport("Parameter <service> not specified.",
                        ExceptionReport.MISSING_PARAMETER_VALUE, "service");
            } else {
                if (!serviceNode.getNodeValue().equalsIgnoreCase("WPS")) {
                    throw new ExceptionReport("Parameter <service> not specified.",
                            ExceptionReport.INVALID_PARAMETER_VALUE, "service");
                }
            }

            isCapabilitiesNode = nodeName.toLowerCase().contains("capabilities");
            if (versionNode == null && !isCapabilitiesNode) {
                throw new ExceptionReport("Parameter <version> not specified.",
                        ExceptionReport.MISSING_PARAMETER_VALUE, "version");
            }
            //TODO: I think this can be removed, as capabilities requests do not have a version parameter (BenjaminPross)
            if (!isCapabilitiesNode) {
                //            version = child.getFirstChild().getTextContent();//.getNextSibling().getFirstChild().getNextSibling().getFirstChild().getNodeValue();
                version = child.getAttributes().getNamedItem("version").getNodeValue();
            }
            /*
             * check language, if not supported, return ExceptionReport
             * Fix for https://bugzilla.52north.org/show_bug.cgi?id=905
             */
            Node languageNode = child.getAttributes().getNamedItem("language");
            if (languageNode != null) {
                String language = languageNode.getNodeValue();
                Request.checkLanguageSupported(language);
            }
        } catch (SAXException e) {
            throw new ExceptionReport("There went something wrong with parsing the POST data: " + e.getMessage(),
                    ExceptionReport.NO_APPLICABLE_CODE, e);
        } catch (IOException e) {
            throw new ExceptionReport("There went something wrong with the network connection.",
                    ExceptionReport.NO_APPLICABLE_CODE, e);
        } catch (ParserConfigurationException e) {
            throw new ExceptionReport("There is a internal parser configuration error",
                    ExceptionReport.NO_APPLICABLE_CODE, e);
        }
        //Fix for Bug 904 https://bugzilla.52north.org/show_bug.cgi?id=904
        if (!isCapabilitiesNode && version == null) {
            throw new ExceptionReport("Parameter <version> not specified.", ExceptionReport.MISSING_PARAMETER_VALUE,
                    "version");
        }
        if (!isCapabilitiesNode && !version.equals(Request.SUPPORTED_VERSION)) {
            throw new ExceptionReport("Version not supported.", ExceptionReport.INVALID_PARAMETER_VALUE, "version");
        }
        // get the request type
        if (nodeURI.equals(WebProcessingService.WPS_NAMESPACE) && localName.equals("Execute")) {
            req = new ExecuteRequest(doc);
            setResponseMimeType((ExecuteRequest) req);
        } else if (nodeURI.equals(WebProcessingService.WPS_NAMESPACE) && localName.equals("GetCapabilities")) {
            req = new CapabilitiesRequest(doc);
            this.responseMimeType = "text/xml";
        } else if (nodeURI.equals(WebProcessingService.WPS_NAMESPACE) && localName.equals("DescribeProcess")) {
            req = new DescribeProcessRequest(doc);
            this.responseMimeType = "text/xml";

        } else if (!localName.equals("Execute")) {
            throw new ExceptionReport(
                    "The requested Operation not supported or not applicable to the specification: " + nodeName,
                    ExceptionReport.OPERATION_NOT_SUPPORTED, localName);
        } else if (nodeURI.equals(WebProcessingService.WPS_NAMESPACE)) {
            throw new ExceptionReport("specified namespace is not supported: " + nodeURI,
                    ExceptionReport.INVALID_PARAMETER_VALUE);
        }
    }

    /**
     * Handle a request after its type is determined. The request is scheduled
     * for execution. If the server has enough free resources, the client will
     * be served immediately. If time runs out, the client will be asked to come
     * back later with a reference to the result.
     * 
     * @param req The request of the client.
     * @throws ExceptionReport
     */
    public void handle() throws ExceptionReport {
        Response resp = null;
        if (req == null) {
            throw new ExceptionReport("Internal Error", "");
        }
        if (req instanceof ExecuteRequest) {
            // cast the request to an executerequest
            ExecuteRequest execReq = (ExecuteRequest) req;

            execReq.updateStatusAccepted();

            ExceptionReport exceptionReport = null;
            try {
                if (execReq.isStoreResponse()) {
                    resp = new ExecuteResponse(execReq);
                    InputStream is = resp.getAsStream();
                    IOUtils.copy(is, os);
                    is.close();
                    pool.submit(execReq);
                    return;
                }
                try {
                    // retrieve status with timeout enabled
                    try {
                        resp = pool.submit(execReq).get();
                    } catch (ExecutionException ee) {
                        LOGGER.warn("exception while handling ExecuteRequest.");
                        // the computation threw an error
                        // probably the client input is not valid
                        if (ee.getCause() instanceof ExceptionReport) {
                            exceptionReport = (ExceptionReport) ee.getCause();
                        } else {
                            exceptionReport = new ExceptionReport(
                                    "An error occurred in the computation: " + ee.getMessage(),
                                    ExceptionReport.NO_APPLICABLE_CODE);
                        }
                    } catch (InterruptedException ie) {
                        LOGGER.warn("interrupted while handling ExecuteRequest.");
                        // interrupted while waiting in the queue
                        exceptionReport = new ExceptionReport("The computation in the process was interrupted.",
                                ExceptionReport.NO_APPLICABLE_CODE);
                    }
                } finally {
                    if (exceptionReport != null) {
                        LOGGER.debug("ExceptionReport not null: " + exceptionReport.getMessage());
                        // NOT SURE, if this exceptionReport is also written to the DB, if required... test please!
                        throw exceptionReport;
                    }
                    // send the result to the outputstream of the client.
                    /*   if(((ExecuteRequest) req).isQuickStatus()) {
                          resp = new ExecuteResponse(execReq);
                       }*/
                    else if (resp == null) {
                        LOGGER.warn("null response handling ExecuteRequest.");
                        throw new ExceptionReport("Problem with handling threads in RequestHandler",
                                ExceptionReport.NO_APPLICABLE_CODE);
                    }
                    if (!execReq.isStoreResponse()) {
                        InputStream is = resp.getAsStream();
                        IOUtils.copy(is, os);
                        is.close();
                        LOGGER.info("Served ExecuteRequest.");
                    }
                }
            } catch (RejectedExecutionException ree) {
                LOGGER.warn("exception handling ExecuteRequest.", ree);
                // server too busy?
                throw new ExceptionReport(
                        "The requested process was rejected. Maybe the server is flooded with requests.",
                        ExceptionReport.SERVER_BUSY);
            } catch (Exception e) {
                LOGGER.error("exception handling ExecuteRequest.", e);
                if (e instanceof ExceptionReport) {
                    throw (ExceptionReport) e;
                }
                throw new ExceptionReport("Could not read from response stream.",
                        ExceptionReport.NO_APPLICABLE_CODE);
            }
        } else {
            // for GetCapabilities and DescribeProcess:
            resp = req.call();
            try {
                InputStream is = resp.getAsStream();
                IOUtils.copy(is, os);
                is.close();
            } catch (IOException e) {
                throw new ExceptionReport("Could not read from response stream.",
                        ExceptionReport.NO_APPLICABLE_CODE);
            }

        }
    }

    protected void setResponseMimeType(ExecuteRequest req) {
        if (req.isRawData()) {
            responseMimeType = req.getExecuteResponseBuilder().getMimeType();
        } else {
            responseMimeType = "text/xml";
        }

    }

    public String getResponseMimeType() {
        if (responseMimeType == null) {
            return "text/xml";
        }
        return responseMimeType.toLowerCase();
    }

}