wps/src/main/java/org/gcube/data/analysis/wps/WebProcessingService.java

468 lines
18 KiB
Java
Raw Normal View History

package org.gcube.data.analysis.wps;
/**
* Copyright (C) 2007 - 2014 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.
org.gcube.dataanalysis.wps.statisticalmanager.synchserver.capabilities52.wps.server;
*/
// FvK: added Property Change Listener support
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.xmlbeans.XmlException;
import org.gcube.data.analysis.wps.repository.AlgorithmUpdater;
import org.gcube.data.analysis.wps.repository.GcubeAlgorithmRepository;
import org.gcube.dataanalysis.wps.statisticalmanager.synchserver.mapping.EnvironmentVariableManager;
import org.gcube.smartgears.ContextProvider;
import org.gcube.smartgears.context.application.ApplicationContext;
import org.n52.wps.GeneratorDocument.Generator;
import org.n52.wps.ParserDocument.Parser;
import org.n52.wps.commons.WPSConfig;
import org.n52.wps.io.GeneratorFactory;
import org.n52.wps.io.ParserFactory;
import org.n52.wps.server.CapabilitiesConfiguration;
import org.n52.wps.server.ExceptionReport;
import org.n52.wps.server.database.DatabaseFactory;
import org.n52.wps.util.XMLBeansHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This WPS supports HTTP GET for describeProcess and getCapabilities and XML-POST for execute.
*
* @author foerster
*
*/
public class WebProcessingService extends HttpServlet {
// Universal version identifier for a Serializable class.
// Should be used here, because HttpServlet implements the java.io.Serializable
private static final long serialVersionUID = 8943233273641771839L;
public static String PROPERTY_NAME_WEBAPP_PATH = "webappPath";
public static String BASE_DIR = null;
public static String WEBAPP_PATH = null;
public static String SERVLET_PATH = "WebProcessingService";
public static String WPS_NAMESPACE = "http://www.opengis.net/wps/1.0.0";
public static String DEFAULT_LANGUAGE = "en-US";
protected static Logger LOGGER = LoggerFactory.getLogger(WebProcessingService.class);
private static final String DIRECTORY_PARAM = "algorithmDirectory";
private static final String MAX_COMPUTATION_PARAM = "maxComputation";
private static final String SAVEONSTORAGE_PARAM = "saveOnStorage";
private static final String SIMULATION_MODE_PARAM = "simulationMode";
private static final String USER_WIRTE_EXCLUDED_PARAM = "excludeWritesOnSH";
private static final String CONFIG_PATH = "configPath";
private ApplicationContext context = ContextProvider.get();
private EnvironmentVariableManager env ;
@Override
public void init() throws ServletException {
LOGGER.info("initialising WPS");
String dir = (String)context.application().getInitParameter(DIRECTORY_PARAM);
GcubeAlgorithmRepository.setUpdater(new AlgorithmUpdater(dir));
int maxComp = Integer.parseInt(context.application().getInitParameter(MAX_COMPUTATION_PARAM));
boolean saveOnStorage = Boolean.parseBoolean(context.application().getInitParameter(SAVEONSTORAGE_PARAM));
boolean simulationMode = Boolean.parseBoolean(context.application().getInitParameter(SIMULATION_MODE_PARAM));
String configPath = context.application().getInitParameter(CONFIG_PATH);
String userWriteExcludedValue = context.application().getInitParameter(USER_WIRTE_EXCLUDED_PARAM);
List<String> excludedUserForWrite = null;
if (userWriteExcludedValue.isEmpty() || userWriteExcludedValue==null) {
excludedUserForWrite = Collections.emptyList();
LOGGER.debug("none is excluded in write");
}else if(!userWriteExcludedValue.equals("*")){
excludedUserForWrite = Arrays.asList(userWriteExcludedValue.split(","));
excludedUserForWrite.stream().forEach(i -> i.trim());
LOGGER.debug("users excluded in write are {}",excludedUserForWrite);
} else
LOGGER.debug("all users excluded in write");
env = new EnvironmentVariableManager(maxComp, saveOnStorage, simulationMode, excludedUserForWrite, configPath);
LOGGER.info("WPS initialised");
}
/**
*
* Returns a preconfigured OutputStream It takes care of: - caching - content-Encoding
*
* @param hsRequest
* the HttpServletRequest
* @param hsResponse
* the HttpServlerResponse
* @return the preconfigured OutputStream
* @throws IOException
* a task of the tomcat
*/
@SuppressWarnings("unused")
private static OutputStream getConfiguredOutputStream(HttpServletRequest hsRequest, HttpServletResponse hsResponse) throws IOException {
/*
* Forbids clients to cache the response May solve problems with proxies and bad implementations
*/
hsResponse.setHeader("Expires", "0");
if (hsRequest.getProtocol().equals("HTTP/1.1")) {
hsResponse.setHeader("Cache-Control", "no-cache");
} else if (hsRequest.getProtocol().equals("HTTP/1.0")) {
hsResponse.setHeader("Pragma", "no-cache");
}
// Enable/disable gzip compression
if (hsRequest.getHeader("Accept-Encoding") != null
&& hsRequest.getHeader("Accept-Encoding").indexOf("gzip") >= 0) {
hsResponse.setHeader("Content-Encoding", "gzip");
LOGGER.info("gzip-Compression for output enabled");
return new GZIPOutputStream(hsResponse.getOutputStream());
} // else {
LOGGER.info("gzip-Compression for output disabled");
return hsResponse.getOutputStream();
// }
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
// this is important to set the lon lat support for correct CRS transformation.
System.setProperty("org.geotools.referencing.forceXY", "true");
LOGGER.info("WebProcessingService initializing...");
try {
if (WPSConfig.getInstance(config) == null) {
LOGGER.error("Initialization failed! Please look at the properties file!");
return;
}
}
catch (Exception e) {
LOGGER.error("Initialization failed! Please look at the properties file!", e);
return;
}
LOGGER.info("Initialization of wps properties successful!");
BASE_DIR = this.getServletContext().getRealPath("");
Parser[] parsers = WPSConfig.getInstance().getActiveRegisteredParser();
ParserFactory.initialize(parsers);
Generator[] generators = WPSConfig.getInstance().getActiveRegisteredGenerator();
GeneratorFactory.initialize(generators);
// call RepositoyManager to initialize
LOGGER.info("Algorithms initialized");
// String customWebappPath = WPSConfiguration.getInstance().getProperty(PROPERTY_NAME_WEBAPP_PATH);
String customWebappPath = WPSConfig.getInstance().getWPSConfig().getServer().getWebappPath();
if (customWebappPath != null) {
WEBAPP_PATH = customWebappPath;
}
else {
WEBAPP_PATH = "wps";
LOGGER.warn("No custom webapp path found, use default wps");
}
LOGGER.info("webappPath is set to: " + customWebappPath);
try {
CapabilitiesConfiguration.getInstance(BASE_DIR + File.separator + "config"
+ File.separator + "wpsCapabilitiesSkeleton.xml");
}
catch (IOException e) {
LOGGER.error("error while initializing capabilitiesConfiguration", e);
}
catch (XmlException e) {
LOGGER.error("error while initializing capabilitiesConfiguration", e);
}
// Get an instance of the database for initialization of the database
DatabaseFactory.getDatabase();
LOGGER.info("WPS up and running!");
// FvK: added Property Change Listener support
// creates listener and register it to the wpsConfig instance.
// it will listen to changes of the wpsCapabilities
WPSConfig.getInstance().addPropertyChangeListener(org.n52.wps.commons.WPSConfig.WPSCAPABILITIES_SKELETON_PROPERTY_EVENT_NAME,
new PropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent propertyChangeEvent) {
LOGGER.info(this.getClass().getName()
+ ": Received Property Change Event: "
+ propertyChangeEvent.getPropertyName());
try {
CapabilitiesConfiguration.reloadSkeleton();
}
catch (IOException e) {
LOGGER.error("error while initializing capabilitiesConfiguration",
e);
}
catch (XmlException e) {
LOGGER.error("error while initializing capabilitiesConfiguration",
e);
}
}
});
// FvK: added Property Change Listener support
// creates listener and register it to the wpsConfig instance.
// it will listen to changes of the wpsConfiguration
WPSConfig.getInstance().addPropertyChangeListener(org.n52.wps.commons.WPSConfig.WPSCONFIG_PROPERTY_EVENT_NAME,
new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent propertyChangeEvent) {
LOGGER.info(this.getClass().getName()
+ ": Received Property Change Event: "
+ propertyChangeEvent.getPropertyName());
try {
CapabilitiesConfiguration.reloadSkeleton();
}
catch (IOException e) {
LOGGER.error("error while initializing capabilitiesConfiguration",
e);
}
catch (XmlException e) {
LOGGER.error("error while initializing capabilitiesConfiguration",
e);
}
}
});
}
protected void setServerParameters(){
//String endpoint = CapabilitiesConfiguration.ENDPOINT_URL;
String webapp = context.application().getContextPath().replace("//", "");
WPSConfig.getInstance().getWPSConfig().getServer().setWebappPath(webapp);
String host = WPSConfig.getInstance().getWPSConfig().getServer().getHostname();
String port = WPSConfig.getInstance().getWPSConfig().getServer().getHostport();
if (host==null || host.toLowerCase().equals("localhost")){
LOGGER.info("resolving hostname and port from container.xml");
host = context.container().configuration().hostname();
port = Integer.toString(context.container().configuration().port());
WPSConfig.getInstance().getWPSConfig().getServer().setHostname(host);
WPSConfig.getInstance().getWPSConfig().getServer().setHostport(port);
}
LOGGER.debug("Setting server parameters: Host: {}, Port: {} , Webapp: {} ", host, port,webapp);
//TODO: CHANGE the porotocol to enable https via conainer configuration
String webPath = "http://" + host + ":" + port + "/" + webapp + "/WebProcessingService";
if (CapabilitiesConfiguration.ENDPOINT_URL.contains("localhost"))
CapabilitiesConfiguration.ENDPOINT_URL=webPath;
}
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
try {
setServerParameters();
OutputStream out = res.getOutputStream(); // closed by res.flushBuffer();
@SuppressWarnings("unchecked")
RequestHandler handler = new RequestHandler((Map<String, String[]>) req.getParameterMap(), out, env);
String mimeType = handler.getResponseMimeType();
res.setContentType(mimeType);
handler.handle();
res.setStatus(HttpServletResponse.SC_OK);
}
catch (ExceptionReport e) {
handleException(e, res);
}
catch (RuntimeException e) {
ExceptionReport er = new ExceptionReport("Error handing request: " + e.getMessage(),
ExceptionReport.NO_APPLICABLE_CODE,
e);
handleException(er, res);
}
finally {
if (res != null) {
res.flushBuffer();
}
// out.flush();
// out.close();
}
}
public final static int MAXIMUM_REQUEST_SIZE = 128 << 20;
public final static String SPECIAL_XML_POST_VARIABLE = "request";
private static final String XML_CONTENT_TYPE = "text/xml";
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
setServerParameters();
BufferedReader reader = null;
LOGGER.debug("Incoming Post Request");
try {
String contentType = req.getContentType();
String characterEncoding = req.getCharacterEncoding();
if (characterEncoding == null || characterEncoding.length() == 0) {
characterEncoding = "UTF-8"; // default character encoding if unspecified
}
LOGGER.debug("Set Encoding to {}",characterEncoding);
int contentLength = req.getContentLength();
if (contentLength > MAXIMUM_REQUEST_SIZE) {
LOGGER.warn("POST request rejected, request size of " + contentLength + " too large.");
ExceptionReport er = new ExceptionReport("Request body too large, limited to " + MAXIMUM_REQUEST_SIZE
+ " bytes", ExceptionReport.NO_APPLICABLE_CODE);
handleException(er, res);
}
LOGGER.debug("Received POST: Content-Type = {}, Character-Encoding = {} , Content-Length = {} " , contentType, characterEncoding, contentLength);
int requestSize = 0;
StringWriter writer = contentLength > 0 ? new StringWriter(contentLength) : new StringWriter();
reader = req.getReader();
char[] buffer = new char[8192];
int read;
while ( (read = reader.read(buffer)) != -1 && requestSize < MAXIMUM_REQUEST_SIZE) {
writer.write(buffer, 0, read);
requestSize += read;
}
LOGGER.debug("POST request contained {} characters", requestSize);
// Protect against denial of service attacks.
if (requestSize >= MAXIMUM_REQUEST_SIZE && reader.read() > -1) {
LOGGER.warn("POST request rejected, request size of {} too large.",requestSize);
ExceptionReport er = new ExceptionReport("Request body too large, limited to " + MAXIMUM_REQUEST_SIZE
+ " bytes", ExceptionReport.NO_APPLICABLE_CODE);
handleException(er, res);
}
String documentString = writer.toString();
// Perform URL decoding, if necessary
// if ("application/x-www-form-urlencoded".equals(contentType)) {
if ( (contentType).startsWith("application/x-www-form-urlencoded")) {
if (documentString.startsWith(SPECIAL_XML_POST_VARIABLE + "=")) {
// This is a hack to permit xml to be easily submitted via a form POST.
// By convention, we are allowing users to post xml if they name it
// with a POST parameter "request" although this is not
// valid per the specification.
documentString = documentString.substring(SPECIAL_XML_POST_VARIABLE.length() + 1);
LOGGER.debug("POST request form variable removed");
}
documentString = URLDecoder.decode(documentString, characterEncoding);
LOGGER.debug("Decoded of POST:\n" + documentString + "\n");
}
else
LOGGER.info("This is a standard xml document");
RequestHandler handler = new RequestHandler(new ByteArrayInputStream(documentString.getBytes("UTF-8")),
res.getOutputStream(), env);
LOGGER.debug("POST Request Handler created");
String mimeType = handler.getResponseMimeType();
LOGGER.debug("Request mimeType: "+mimeType);
res.setContentType(mimeType);
LOGGER.debug("Handling document");
handler.handle();
LOGGER.debug("STATUS OK!");
res.setStatus(HttpServletResponse.SC_OK);
}
catch (ExceptionReport e) {
handleException(e, res);
}
catch (Exception e) {
ExceptionReport er = new ExceptionReport("Error handing request: " + e.getMessage(), ExceptionReport.NO_APPLICABLE_CODE, e);
handleException(er, res);
}
finally {
if (res != null) {
res.flushBuffer();
}
if (reader != null) {
reader.close();
}
LOGGER.trace("Flushing request");
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
if (SERVLET_PATH == null) {
req.getContextPath();
}
super.service(req, res);
}
private static void handleException(ExceptionReport exception, HttpServletResponse res) {
res.setContentType(XML_CONTENT_TYPE);
try {
LOGGER.debug(exception.toString());
// DO NOT MIX getWriter and getOuputStream!
exception.getExceptionDocument().save(res.getOutputStream(),
XMLBeansHelper.getXmlOptions());
res.setStatus(exception.getHTTPStatusCode());
}
catch (IOException e) {
LOGGER.warn("exception occured while writing ExceptionReport to stream");
try {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"error occured, while writing OWS Exception output");
}
catch (IOException ex) {
LOGGER.error("error while writing error code to client!");
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
@Override
public void destroy() {
super.destroy();
DatabaseFactory.getDatabase().shutdown();
}
}