org.geoserver.wps.executor.SimpleInputProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.wps.executor.SimpleInputProvider.java

Source

/* 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.CoverageCleanerCallback;
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.resource.GridCoverageResource;
import org.opengis.coverage.Coverage;
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();
                    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;
    }
}