org.geoserver.wps.Execute.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.wps.Execute.java

Source

/* Copyright (c) 2001 - 2007 TOPP - 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;

import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.datatype.XMLGregorianCalendar;

import net.opengis.ows11.BoundingBoxType;
import net.opengis.wcs11.GetCoverageType;
import net.opengis.wfs.FeatureCollectionType;
import net.opengis.wfs.GetFeatureType;
import net.opengis.wps10.ComplexDataType;
import net.opengis.wps10.DataType;
import net.opengis.wps10.DocumentOutputDefinitionType;
import net.opengis.wps10.ExecuteResponseType;
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 net.opengis.wps10.OutputDataType;
import net.opengis.wps10.OutputDefinitionType;
import net.opengis.wps10.OutputDefinitionsType;
import net.opengis.wps10.OutputReferenceType;
import net.opengis.wps10.ProcessBriefType;
import net.opengis.wps10.ProcessOutputsType1;
import net.opengis.wps10.Wps10Factory;

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.config.GeoServerInfo;
import org.geoserver.ows.KvpRequestReader;
import org.geoserver.ows.Ows11Util;
import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.KvpMap;
import org.geoserver.ows.util.KvpUtils;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.wcs.WebCoverageService100;
import org.geoserver.wcs.WebCoverageService111;
import org.geoserver.wfs.WebFeatureService;
import org.geoserver.wfs.kvp.GetFeatureKvpRequestReader;
import org.geoserver.wps.kvp.ExecuteKvpRequestReader;
import org.geoserver.wps.ppio.BinaryPPIO;
import org.geoserver.wps.ppio.BoundingBoxPPIO;
import org.geoserver.wps.ppio.CDataPPIO;
import org.geoserver.wps.ppio.ComplexPPIO;
import org.geoserver.wps.ppio.LiteralPPIO;
import org.geoserver.wps.ppio.ProcessParameterIO;
import org.geoserver.wps.ppio.ReferencePPIO;
import org.geoserver.wps.ppio.XMLPPIO;
import org.geotools.data.Parameter;
import org.geotools.process.Process;
import org.geotools.process.ProcessException;
import org.geotools.process.ProcessFactory;
import org.geotools.process.Processors;
import org.geotools.util.Converters;
import org.geotools.xml.EMFUtils;
import org.opengis.feature.type.Name;
import org.opengis.util.InternationalString;
import org.opengis.util.ProgressListener;
import org.springframework.context.ApplicationContext;

/**
 * Main class used to handle Execute requests
 * 
 * @author Lucas Reed, Refractions Research Inc
 * @author Andrea Aime, OpenGeo
 */
public class Execute {

    int connectionTimeout;

    WPSInfo wps;

    GeoServerInfo gs;

    ApplicationContext context;

    public Execute(WPSInfo wps, GeoServerInfo gs, ApplicationContext context) {
        this.wps = wps;
        this.gs = gs;
        this.context = context;
        double timeout = wps.getConnectionTimeout();

        // The specified timeout is in seconds. Convert it to milliseconds  
        if (timeout >= 0) {
            this.connectionTimeout = (int) (timeout * 1000);
        } else {
            // specified timeout == -1 represents infinite timeout.
            // by convention, for infinite URLConnection timeouts, we need to use zero. 
            this.connectionTimeout = 0;
        }
    }

    /**
     * Main method for performing decoding, execution, and response
     * 
     * @param object
     * @param output
     * @throws IllegalArgumentException
     */
    public ExecuteResponseType run(ExecuteType request) {
        // note the current time
        Date started = Calendar.getInstance().getTime();

        // perform the execution and grab the results
        Map<String, ProcessOutput> outputMap = executeInternal(request);

        // build the response
        Wps10Factory f = Wps10Factory.eINSTANCE;
        ExecuteResponseType response = f.createExecuteResponseType();
        response.setLang("en");
        if (request.getBaseUrl() != null) {
            response.setServiceInstance(ResponseUtils.appendQueryString(
                    ResponseUtils.buildURL(request.getBaseUrl(), "ows", null, URLType.SERVICE), ""));
        }

        // process
        Name processName = Ows11Util.name(request.getIdentifier());
        ProcessFactory pf = Processors.createProcessFactory(processName);
        final ProcessBriefType process = f.createProcessBriefType();
        response.setProcess(process);
        process.setIdentifier(request.getIdentifier());
        process.setProcessVersion(pf.getVersion(processName));
        process.setTitle(Ows11Util.languageString(pf.getTitle(processName)));
        process.setAbstract(Ows11Util.languageString(pf.getDescription(processName)));

        // status
        response.setStatus(f.createStatusType());
        response.getStatus().setCreationTime(Converters.convert(started, XMLGregorianCalendar.class));
        response.getStatus().setProcessSucceeded("Process succeeded.");

        // inputs
        response.setDataInputs(f.createDataInputsType1());
        for (Iterator i = request.getDataInputs().getInput().iterator(); i.hasNext();) {
            InputType input = (InputType) i.next();
            response.getDataInputs().getInput().add(EMFUtils.clone(input, f, true));
        }

        // output definitions
        OutputDefinitionsType outputs = f.createOutputDefinitionsType();
        response.setOutputDefinitions(outputs);

        Map<String, Parameter<?>> outs = pf.getResultInfo(processName, null);
        Map<String, ProcessParameterIO> ppios = new HashMap();

        for (String key : outputMap.keySet()) {
            Parameter p = pf.getResultInfo(processName, null).get(key);
            if (p == null) {
                throw new WPSException("No such output: " + key);
            }

            // find the ppio
            String mime = outputMap.get(key).definition.getMimeType();
            ProcessParameterIO ppio = ProcessParameterIO.find(p, context, mime);
            if (ppio == null) {
                throw new WPSException("Unable to encode output: " + p.key);
            }
            ppios.put(p.key, ppio);

            DocumentOutputDefinitionType output = f.createDocumentOutputDefinitionType();
            outputs.getOutput().add(output);

            output.setIdentifier(Ows11Util.code(p.key));
            if (ppio instanceof ComplexPPIO) {
                output.setMimeType(((ComplexPPIO) ppio).getMimeType());
                if (ppio instanceof BinaryPPIO) {
                    output.setEncoding("base64");
                } else if (ppio instanceof XMLPPIO) {
                    output.setEncoding("utf-8");
                }
            }

            // TODO: better encoding handling + schema
        }

        // process outputs
        ProcessOutputsType1 processOutputs = f.createProcessOutputsType1();
        response.setProcessOutputs(processOutputs);

        for (String key : outputMap.keySet()) {
            OutputDataType output = f.createOutputDataType();
            output.setIdentifier(Ows11Util.code(key));
            output.setTitle(Ows11Util.languageString(pf.getResultInfo(processName, null).get(key).description));
            processOutputs.getOutput().add(output);

            final Object o = outputMap.get(key).object;
            ProcessParameterIO ppio = ppios.get(key);

            if (ppio instanceof ReferencePPIO) {
                // encode as a reference
                OutputReferenceType ref = f.createOutputReferenceType();
                output.setReference(ref);

                ref.setMimeType(outputMap.get(key).definition.getMimeType());
                ref.setHref(((ReferencePPIO) ppio).encode(o).toString());
            } else {
                // encode as data
                DataType data = f.createDataType();
                output.setData(data);

                try {
                    if (ppio instanceof LiteralPPIO) {
                        LiteralDataType literal = f.createLiteralDataType();
                        data.setLiteralData(literal);

                        literal.setValue(((LiteralPPIO) ppio).encode(o));
                    } else if (ppio instanceof BoundingBoxPPIO) {
                        BoundingBoxType bbox = ((BoundingBoxPPIO) ppio).encode(o);
                        data.setBoundingBoxData(bbox);
                    } else if (ppio instanceof ComplexPPIO) {
                        ComplexDataType complex = f.createComplexDataType();
                        data.setComplexData(complex);

                        ComplexPPIO cppio = (ComplexPPIO) ppio;
                        complex.setMimeType(cppio.getMimeType());

                        if (cppio instanceof XMLPPIO) {
                            // encode directly
                            complex.getData().add(new XMLEncoderDelegate((XMLPPIO) cppio, o));
                        } else if (cppio instanceof CDataPPIO) {
                            complex.getData().add(new CDataEncoderDelegate((CDataPPIO) cppio, o));
                        } else if (cppio instanceof BinaryPPIO) {
                            complex.getData().add(new BinaryEncoderDelegate((BinaryPPIO) cppio, o));
                        } else {
                            throw new WPSException("Don't know how to encode an output whose PPIO is " + cppio);
                        }
                    }
                } catch (Exception e) {
                    throw new WPSException("Failed to encode the " + key + " output", e);
                }
            }
        }

        return response;
    }

    Map<String, ProcessOutput> executeInternal(ExecuteType request) {
        // load the process factory
        Name processName = Ows11Util.name(request.getIdentifier());
        ProcessFactory pf = Processors.createProcessFactory(processName);
        if (pf == null) {
            throw new WPSException("No such process: " + processName);
        }

        // parse the inputs into in memory representations the process can handle
        Map<String, Object> inputs = parseProcessInputs(request, processName, pf);

        // execute the process
        Map<String, Object> result = null;
        ProcessListener listener = new ProcessListener();
        Throwable exception = null;
        try {
            Process p = pf.create(processName);
            result = p.execute(inputs, listener);
        } catch (Exception e) {
            exception = e;
        }

        // if no direct exception, check if failure occurred from the listener
        if (exception == null) {
            exception = listener.exception;
        }
        // if we got any exception report back with a service exception
        if (exception != null) {
            if (exception instanceof WPSException) {
                throw (WPSException) exception;
            } else if (exception instanceof ProcessException) {
                throw new WPSException("Process returned with an exception", exception);
            } else {
                throw new WPSException("InternalError: " + exception.getMessage(), exception);
            }
        }

        // filter out the results we have not been asked about
        // and create a direct map between required outputs and
        // the gt process outputs
        Map<String, ProcessOutput> outputMap = new HashMap<String, ProcessOutput>();
        if (request.getResponseForm().getRawDataOutput() != null) {
            // only one output in raw form
            OutputDefinitionType od = request.getResponseForm().getRawDataOutput();
            String outputName = od.getIdentifier().getValue();
            outputMap.put(outputName, new ProcessOutput(od, result.get(outputName)));
        } else {
            for (Iterator it = request.getResponseForm().getResponseDocument().getOutput().iterator(); it
                    .hasNext();) {
                OutputDefinitionType od = (OutputDefinitionType) it.next();
                String outputName = od.getIdentifier().getValue();
                outputMap.put(outputName, new ProcessOutput(od, result.get(outputName)));
            }
        }

        return outputMap;
    }

    /**
     * Parses the process inputs into a {@link Map} using the various {@link ProcessParameterIO}
     * implementations
     * 
     * @param request
     * @param processName
     * @param pf
     */
    Map<String, Object> parseProcessInputs(ExecuteType request, Name processName, ProcessFactory pf) {
        Map<String, Object> inputs = new HashMap<String, Object>();
        final Map<String, Parameter<?>> parameters = pf.getParameterInfo(processName);
        for (Iterator i = request.getDataInputs().getInput().iterator(); i.hasNext();) {
            InputType input = (InputType) i.next();
            String inputId = input.getIdentifier().getValue();

            // locate the parameter for this request
            Parameter p = parameters.get(inputId);
            if (p == null) {
                throw new WPSException("No such parameter: " + inputId);
            }

            // find the ppio
            String mime = null;
            if (input.getData() != null && input.getData().getComplexData() != null) {
                mime = input.getData().getComplexData().getMimeType();
            } else if (input.getReference() != null) {
                mime = input.getReference().getMimeType();
            }
            ProcessParameterIO ppio = ProcessParameterIO.find(p, context, mime);
            if (ppio == null) {
                throw new WPSException("Unable to decode input: " + inputId);
            }

            // read the data
            Object decoded = null;
            try {
                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")) {
                        decoded = handleAsInternalWFS(ppio, ref);
                    } else if (href.startsWith("http://geoserver/wcs")) {
                        decoded = handleAsInternalWCS(ppio, ref);
                    } else if (href.startsWith("http://geoserver/wps")) {
                        decoded = handleAsInternalWPS(ppio, ref);
                    } else {
                        decoded = executeRemoteRequest(ref, (ComplexPPIO) ppio, inputId);
                    }

                } else {
                    // actual data, figure out which type
                    DataType data = input.getData();

                    if (data.getLiteralData() != null) {
                        LiteralDataType literal = data.getLiteralData();
                        decoded = ((LiteralPPIO) ppio).decode(literal.getValue());
                    } else if (data.getComplexData() != null) {
                        ComplexDataType complex = data.getComplexData();
                        decoded = ((ComplexPPIO) ppio).decode(complex.getData().get(0));
                    } else if (data.getBoundingBoxData() != null) {
                        decoded = ((BoundingBoxPPIO) ppio).decode(data.getBoundingBoxData());
                    }

                }
            } catch (Exception e) {
                throw new WPSException("Unable to decode input: " + inputId, e);
            }

            // store the input
            if (p.maxOccurs > 1) {
                Collection values = (Collection) inputs.get(p.key);
                if (values == null) {
                    values = new ArrayList();
                }
                values.add(decoded);
                inputs.put(p.key, values);
            } else {
                inputs.put(p.key, decoded);
            }

        }

        return inputs;
    }

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

        FeatureCollectionType featureCollectionType = wfs.getFeature(gft);
        // this will also deal with axis order issues
        return ((ComplexPPIO) ppio).decode(featureCollectionType);
    }

    /**
     * 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, ProcessOutput> results = executeInternal(request);
        Object obj = results.values().iterator().next().object;
        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) ref.getBody())[0];
        } else if (getCoverage instanceof net.opengis.wcs10.GetCoverageType) {
            WebCoverageService100 wcs = (WebCoverageService100) context.getBean("wcs100ServiceTarget");
            return wcs.getCoverage((net.opengis.wcs10.GetCoverageType) ref.getBody())[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(connectionTimeout);
                params.setConnectionTimeout(connectionTimeout);
                // 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(connectionTimeout);
                                conn.setReadTimeout(connectionTimeout);
                                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(connectionTimeout);
                conn.setReadTimeout(connectionTimeout);
                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));
        }

        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");
    }

    static class ProcessOutput {
        OutputDefinitionType definition;
        Object object;

        public ProcessOutput(OutputDefinitionType definition, Object object) {
            super();
            this.definition = definition;
            this.object = object;
        }
    }

    /**
     * A process listener. For the moment just used to check if the process execution failed
     * @author Andrea Aime - OpenGeo
     *
     */
    static class ProcessListener implements ProgressListener {
        Throwable exception;

        public void complete() {
            // TODO Auto-generated method stub

        }

        public void dispose() {
            // TODO Auto-generated method stub

        }

        public void exceptionOccurred(Throwable exception) {
            this.exception = exception;

        }

        public String getDescription() {
            // TODO Auto-generated method stub
            return null;
        }

        public float getProgress() {
            // TODO Auto-generated method stub
            return 0;
        }

        public InternationalString getTask() {
            // TODO Auto-generated method stub
            return null;
        }

        public boolean isCanceled() {
            // TODO Auto-generated method stub
            return false;
        }

        public void progress(float percent) {
            // TODO Auto-generated method stub

        }

        public void setCanceled(boolean cancel) {
            // TODO Auto-generated method stub

        }

        public void setDescription(String description) {
            // TODO Auto-generated method stub

        }

        public void setTask(InternationalString task) {
            // TODO Auto-generated method stub

        }

        public void started() {
            // TODO Auto-generated method stub

        }

        public void warningOccurred(String source, String location, String warning) {
            // TODO Auto-generated method stub

        }

    }

}