52n-wps-server-gcube/src/main/java/org/n52/wps/server/handler/RequestHandler.java

399 lines
14 KiB
Java
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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<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();
}
}