/** * Copyright (C) 2007 - 2016 52°North 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 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 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 not specified.", ExceptionReport.MISSING_PARAMETER_VALUE, "service"); }else{ if(!serviceNode.getNodeValue().equalsIgnoreCase("WPS")){ throw new ExceptionReport("Parameter not specified.", ExceptionReport.INVALID_PARAMETER_VALUE, "service"); } } isCapabilitiesNode = nodeName.toLowerCase().contains("capabilities"); if(versionNode == null && !isCapabilitiesNode) { throw new ExceptionReport("Parameter 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 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(); } }