/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wps.executor;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.opengis.wcs11.GetCoverageType;
import net.opengis.wfs.GetFeatureType;
import net.opengis.wps10.ComplexDataType;
import net.opengis.wps10.DataType;
import net.opengis.wps10.ExecuteType;
import net.opengis.wps10.HeaderType;
import net.opengis.wps10.InputReferenceType;
import net.opengis.wps10.InputType;
import net.opengis.wps10.LiteralDataType;
import net.opengis.wps10.MethodType;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.SimpleHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.geoserver.ows.Dispatcher;
import org.geoserver.ows.KvpRequestReader;
import org.geoserver.ows.Request;
import org.geoserver.ows.util.CaseInsensitiveMap;
import org.geoserver.ows.util.KvpMap;
import org.geoserver.ows.util.KvpUtils;
import org.geoserver.wcs.WebCoverageService100;
import org.geoserver.wcs.WebCoverageService111;
import org.geoserver.wfs.WebFeatureService;
import org.geoserver.wfs.kvp.GetFeatureKvpRequestReader;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wps.WPSException;
import org.geoserver.wps.kvp.ExecuteKvpRequestReader;
import org.geoserver.wps.ppio.BoundingBoxPPIO;
import org.geoserver.wps.ppio.ComplexPPIO;
import org.geoserver.wps.ppio.LiteralPPIO;
import org.geoserver.wps.ppio.ProcessParameterIO;
import org.geoserver.wps.ppio.RawDataPPIO;
import org.geoserver.wps.process.StringRawData;
import org.geoserver.wps.resource.GridCoverageResource;
import org.opengis.coverage.grid.GridCoverage;
import org.springframework.context.ApplicationContext;
/**
* Performs lazy parsing of a specific input
*
* @author Andrea Aime - GeoSolutions
*
*/
class SimpleInputProvider implements InputProvider {
InputType input;
ProcessParameterIO ppio;
Object value;
ApplicationContext context;
WPSExecutionManager executor;
String inputId;
public SimpleInputProvider(InputType input, ProcessParameterIO ppio, WPSExecutionManager executor,
ApplicationContext context) {
this.input = input;
this.ppio = ppio;
this.context = context;
this.executor = executor;
this.inputId = input.getIdentifier().getValue();
}
public String getInputId() {
return inputId;
}
public boolean longParse() {
if(input.getReference() == null) {
return false;
} else {
InputReferenceType ref = input.getReference();
// grab the location and method
String href = ref.getHref();
if (href.startsWith("http://geoserver/wfs")) {
// we get a collection almost instantly
return false;
} else if (href.startsWith("http://geoserver/wcs")) {
// same here, most of the time we get a coverage reference almost instantly
return false;
} else {
return true;
}
}
}
public Object getValue() throws Exception {
if (value == null) {
if (input.getReference() != null) {
// this is a reference
InputReferenceType ref = input.getReference();
// grab the location and method
String href = ref.getHref();
if (href.startsWith("http://geoserver/wfs")) {
value = handleAsInternalWFS(ppio, ref);
} else if (href.startsWith("http://geoserver/wcs")) {
value = handleAsInternalWCS(ppio, ref);
} else if (href.startsWith("http://geoserver/wps")) {
value = handleAsInternalWPS(ppio, ref);
} else {
value = executeRemoteRequest(ref, (ComplexPPIO) ppio, inputId);
}
} else {
// actual data, figure out which type
DataType data = input.getData();
if (data.getLiteralData() != null) {
LiteralDataType literal = data.getLiteralData();
value = ((LiteralPPIO) ppio).decode(literal.getValue());
} else if (data.getComplexData() != null) {
ComplexDataType complex = data.getComplexData();
if (ppio instanceof RawDataPPIO) {
String content = complex.getData().get(0).toString();
return new StringRawData(content, complex.getMimeType());
} else {
value = ((ComplexPPIO) ppio).decode(complex.getData().get(0));
}
} else if (data.getBoundingBoxData() != null) {
value = ((BoundingBoxPPIO) ppio).decode(data.getBoundingBoxData());
}
}
if(value instanceof GridCoverage) {
executor.getResourceManager().addResource(new GridCoverageResource((GridCoverage) value));
}
// release the input, it's not needed anymore
input = null;
}
return value;
}
/**
* Process the request as an internal one, without going through GML encoding/decoding
*
* @param ppio
* @param ref
* @param method
* @return
* @throws Exception
*/
Object handleAsInternalWFS(ProcessParameterIO ppio, InputReferenceType ref) throws Exception {
WebFeatureService wfs = (WebFeatureService) context.getBean("wfsServiceTarget");
GetFeatureType gft = null;
if (ref.getMethod() == MethodType.POST_LITERAL) {
gft = (GetFeatureType) ref.getBody();
} else {
GetFeatureKvpRequestReader reader = (GetFeatureKvpRequestReader) context
.getBean("getFeatureKvpReader");
gft = (GetFeatureType) kvpParse(ref.getHref(), reader);
}
FeatureCollectionResponse featureCollectionType = wfs.getFeature(gft);
// this will also deal with axis order issues
return ((ComplexPPIO) ppio).decode(featureCollectionType.getAdaptee());
}
/**
* Process the request as an internal one, without going through GML encoding/decoding
*
* @param ppio
* @param ref
* @param method
* @return
* @throws Exception
*/
Object handleAsInternalWPS(ProcessParameterIO ppio, InputReferenceType ref) throws Exception {
ExecuteType request = null;
if (ref.getMethod() == MethodType.POST_LITERAL) {
request = (ExecuteType) ref.getBody();
} else {
ExecuteKvpRequestReader reader = (ExecuteKvpRequestReader) context
.getBean("executeKvpRequestReader");
request = (ExecuteType) kvpParse(ref.getHref(), reader);
}
Map<String, Object> results = executor.submitChained(new ExecuteRequest(request));
Object obj = results.values().iterator().next();
if (obj != null && !ppio.getType().isInstance(obj)) {
throw new WPSException(
"The process output is incompatible with the input target type, was expecting "
+ ppio.getType().getName() + " and got " + obj.getClass().getName());
}
return obj;
}
/**
* Process the request as an internal one, without going through GML encoding/decoding
*
* @param ppio
* @param ref
* @param method
* @return
* @throws Exception
*/
Object handleAsInternalWCS(ProcessParameterIO ppio, InputReferenceType ref) throws Exception {
// first parse the request, it might be a WCS 1.0 or a WCS 1.1 one
Object getCoverage = null;
if (ref.getMethod() == MethodType.POST_LITERAL) {
getCoverage = ref.getBody();
} else {
// what WCS version?
String version = getVersion(ref.getHref());
KvpRequestReader reader;
if (version.equals("1.0.0") || version.equals("1.0")) {
reader = (KvpRequestReader) context.getBean("wcs100GetCoverageRequestReader");
} else {
reader = (KvpRequestReader) context.getBean("wcs111GetCoverageRequestReader");
}
getCoverage = kvpParse(ref.getHref(), reader);
}
// perform GetCoverage
if (getCoverage instanceof GetCoverageType) {
WebCoverageService111 wcs = (WebCoverageService111) context
.getBean("wcs111ServiceTarget");
return wcs.getCoverage((net.opengis.wcs11.GetCoverageType) getCoverage)[0];
} else if (getCoverage instanceof net.opengis.wcs10.GetCoverageType) {
WebCoverageService100 wcs = (WebCoverageService100) context
.getBean("wcs100ServiceTarget");
return wcs.getCoverage((net.opengis.wcs10.GetCoverageType) getCoverage)[0];
} else {
throw new WPSException("Unrecognized request type " + getCoverage);
}
}
/**
* Executes
*
* @param ref
* @return
*/
Object executeRemoteRequest(InputReferenceType ref, ComplexPPIO ppio, String inputId)
throws Exception {
URL destination = new URL(ref.getHref());
HttpMethod method = null;
GetMethod refMethod = null;
InputStream input = null;
InputStream refInput = null;
// execute the request
try {
if ("http".equalsIgnoreCase(destination.getProtocol())) {
// setup the client
HttpClient client = new HttpClient();
// setting timeouts (30 seconds, TODO: make this configurable)
HttpConnectionManagerParams params = new HttpConnectionManagerParams();
params.setSoTimeout(executor.getConnectionTimeout());
params.setConnectionTimeout(executor.getConnectionTimeout());
// TODO: make the http client a well behaved http client, no more than x connections
// per server (x admin configurable maybe), persistent connections and so on
HttpConnectionManager manager = new SimpleHttpConnectionManager();
manager.setParams(params);
client.setHttpConnectionManager(manager);
// prepare either a GET or a POST request
if (ref.getMethod() == null || ref.getMethod() == MethodType.GET_LITERAL) {
GetMethod get = new GetMethod(ref.getHref());
get.setFollowRedirects(true);
method = get;
} else {
String encoding = ref.getEncoding();
if (encoding == null) {
encoding = "UTF-8";
}
PostMethod post = new PostMethod(ref.getHref());
Object body = ref.getBody();
if (body == null) {
if (ref.getBodyReference() != null) {
URL refDestination = new URL(ref.getBodyReference().getHref());
if ("http".equalsIgnoreCase(refDestination.getProtocol())) {
// open with commons http client
refMethod = new GetMethod(ref.getBodyReference().getHref());
refMethod.setFollowRedirects(true);
client.executeMethod(refMethod);
refInput = refMethod.getResponseBodyAsStream();
} else {
// open with the built-in url management
URLConnection conn = refDestination.openConnection();
conn.setConnectTimeout(executor.getConnectionTimeout());
conn.setReadTimeout(executor.getConnectionTimeout());
refInput = conn.getInputStream();
}
post.setRequestEntity(new InputStreamRequestEntity(refInput, ppio
.getMimeType()));
} else {
throw new WPSException("A POST request should contain a non empty body");
}
} else if (body instanceof String) {
post.setRequestEntity(new StringRequestEntity((String) body, ppio
.getMimeType(), encoding));
} else {
throw new WPSException(
"The request body should be contained in a CDATA section, "
+ "otherwise it will get parsed as XML instead of being preserved as is");
}
method = post;
}
// add eventual extra headers
if (ref.getHeader() != null) {
for (Iterator it = ref.getHeader().iterator(); it.hasNext();) {
HeaderType header = (HeaderType) it.next();
method.setRequestHeader(header.getKey(), header.getValue());
}
}
int code = client.executeMethod(method);
if (code == 200) {
input = method.getResponseBodyAsStream();
} else {
throw new WPSException("Error getting remote resources from " + ref.getHref()
+ ", http error " + code + ": " + method.getStatusText());
}
} else {
// use the normal url connection methods then...
URLConnection conn = destination.openConnection();
conn.setConnectTimeout(executor.getConnectionTimeout());
conn.setReadTimeout(executor.getConnectionTimeout());
input = conn.getInputStream();
}
// actually parse teh data
if (input != null) {
return ppio.decode(input);
} else {
throw new WPSException("Could not find a mean to read input " + inputId);
}
} finally {
// make sure to close the connection and streams no matter what
if (input != null) {
input.close();
}
if (method != null) {
method.releaseConnection();
}
if (refMethod != null) {
refMethod.releaseConnection();
}
}
}
/**
* Simulates what the Dispatcher is doing when parsing a KVP request
*
* @param href
* @param reader
* @return
*/
Object kvpParse(String href, KvpRequestReader reader) throws Exception {
Map original = new KvpMap(KvpUtils.parseQueryString(href));
KvpUtils.normalize(original);
Map parsed = new KvpMap(original);
List<Throwable> errors = KvpUtils.parse(parsed);
if (errors.size() > 0) {
throw new WPSException("Failed to parse KVP request", errors.get(0));
}
// hack to allow wcs filters to work... we should really upgrade the WCS models instead...
Request r = Dispatcher.REQUEST.get();
if (r != null) {
Map kvp = new HashMap(r.getKvp());
r.setKvp(new CaseInsensitiveMap(parsed));
}
return reader.read(reader.createRequest(), parsed, original);
}
/**
* Returns the version from the kvp request
*
* @param href
* @return
*/
String getVersion(String href) {
return (String) new KvpMap(KvpUtils.parseQueryString(href)).get("VERSION");
}
@Override
public boolean resolved() {
return value != null;
}
}