/** * Copyright (C) 2007 - 2016 52°North Initiative for Geospatial Open Source * Software GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.n52.wps.client; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.zip.GZIPInputStream; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; import net.opengis.ows.x11.ExceptionReportDocument; import net.opengis.ows.x11.OperationDocument.Operation; import net.opengis.wps.x100.CapabilitiesDocument; import net.opengis.wps.x100.ExecuteDocument; import net.opengis.wps.x100.ExecuteResponseDocument; import net.opengis.wps.x100.ProcessDescriptionType; import net.opengis.wps.x100.ProcessDescriptionsDocument; /** * Contains some convenient methods to access and manage WebProcessingSerivces in a very * generic way. * * This is implemented as a singleton. * @author foerster */ public class WPSClientSession { private static Logger LOGGER = LoggerFactory.getLogger(WPSClientSession.class); private static final String OGC_OWS_URI = "http://www.opengeospatial.net/ows"; private static String SUPPORTED_VERSION = "1.0.0"; private static WPSClientSession session; private Map<String, CapabilitiesDocument> loggedServices; private XmlOptions options = null; private boolean useConnectURL = false; private String connectURL; // a Map of <url, all available process descriptions> private Map<String, ProcessDescriptionsDocument> processDescriptions; /** * Initializes a WPS client session. * */ private WPSClientSession() { options = new XmlOptions(); options.setLoadStripWhitespace(); options.setLoadTrimTextBuffer(); loggedServices = new HashMap<String, CapabilitiesDocument>(); processDescriptions = new HashMap<String, ProcessDescriptionsDocument>(); } /* * @result An instance of a WPS Client session. */ public static WPSClientSession getInstance() { if(session == null) { session = new WPSClientSession(); } return session; } /** * This resets the WPSClientSession. This might be necessary, to get rid of old service entries/descriptions. However, the session has to be repopulated afterwards. */ public static void reset() { session = new WPSClientSession(); } /** * Connects to a WPS and retrieves Capabilities plus puts all available Descriptions into cache. * @param url the entry point for the service. This is used as id for further identification of the service. * @return true, if connect succeeded, false else. * @throws WPSClientException */ public boolean connect(String url) throws WPSClientException { return connect(url, false); } /** * Connects to a WPS and retrieves Capabilities plus puts all available Descriptions into cache. * @param url the entry point for the service. This is used as id for further identification of the service. * @param useConnectURL use the URL that was passed for communication with the WPS and not the URLs from the Operations Metadata * @return true, if connect succeeded, false else. * @throws WPSClientException */ public boolean connect(String url, boolean useConnectURL) throws WPSClientException { LOGGER.info("CONNECT"); this.useConnectURL = useConnectURL; this.connectURL = url; if(useConnectURL){ LOGGER.info("Use connect URL for further communication: " + connectURL); } if(loggedServices.containsKey(url)) { LOGGER.info("Service already registered: " + url); return false; } CapabilitiesDocument capsDoc = retrieveCapsViaGET(url); if(capsDoc != null) { loggedServices.put(url, retrieveCapsViaGET(url)); } ProcessDescriptionsDocument processDescs = describeAllProcesses(url); if(processDescs != null && capsDoc != null) { processDescriptions.put(url, processDescs); return true; } LOGGER.warn("retrieving caps failed, caps are null"); return false; } /** * removes a service from the session * @param url */ public void disconnect(String url) { if(loggedServices.containsKey(url)) { loggedServices.remove(url); processDescriptions.remove(url); LOGGER.info("service removed successfully: " + url); } } /** * returns the serverIDs of all loggedServices * @return */ public List<String> getLoggedServices() { return new ArrayList<String>(loggedServices.keySet()); } /** * informs you if the descriptions for the specified service is already in the session. * in normal case it should return true :) * @param serverID * @return success */ public boolean descriptionsAvailableInCache(String serverID) { return processDescriptions.containsKey(serverID); } /** * returns the cached processdescriptions of a service. * @param serverID * @return success * @throws IOException */ private ProcessDescriptionsDocument getProcessDescriptionsFromCache(String wpsUrl) throws IOException { if(! descriptionsAvailableInCache(wpsUrl)) { try{ connect(wpsUrl); } catch(WPSClientException e) { throw new IOException("Could not initialize WPS " + wpsUrl); } } return processDescriptions.get(wpsUrl); } /** * return the processDescription for a specific process from Cache. * @param serverID * @param processID * @return a ProcessDescription for a specific process from Cache. * @throws IOException */ public ProcessDescriptionType getProcessDescription(String serverID, String processID) throws IOException { ProcessDescriptionType[] processes = getProcessDescriptionsFromCache(serverID).getProcessDescriptions().getProcessDescriptionArray(); for(ProcessDescriptionType process : processes) { if(process.getIdentifier().getStringValue().equals(processID)) { return process; } } return null; } /** * Delivers all ProcessDescriptions from a WPS * * @param wpsUrl the URL of the WPS * @return An Array of ProcessDescriptions * @throws IOException */ public ProcessDescriptionType[] getAllProcessDescriptions(String wpsUrl) throws IOException{ return getProcessDescriptionsFromCache(wpsUrl).getProcessDescriptions().getProcessDescriptionArray(); } /** * looks up, if the service exists already in session. */ public boolean serviceAlreadyRegistered(String serverID) { return loggedServices.containsKey(serverID); } /** * provides you the cached capabilities for a specified service. * @param url * @return */ public CapabilitiesDocument getWPSCaps(String url) { return loggedServices.get(url); } /** * retrieves all current available ProcessDescriptions of a WPS. Mention: to get the current list * of all processes, which will be requested, the cached capabilities will be used. Please keep that in mind. * the retrieved descriptions will not be cached, so only transient information! * @param url * @return * @throws WPSClientException */ public ProcessDescriptionsDocument describeAllProcesses(String url) throws WPSClientException { CapabilitiesDocument doc = loggedServices.get(url); if(doc == null) { LOGGER.warn("serviceCaps are null, perhaps server does not exist"); return null; } String[] processIDs = new String[]{"all"}; return describeProcess(processIDs, url); } /** * retrieves the desired description for a service. the retrieved information will not be held in cache! * @param processIDs one or more processIDs * @param serverID * @throws WPSClientException */ public ProcessDescriptionsDocument describeProcess(String[] processIDs, String serverID) throws WPSClientException { String url = connectURL; if (!useConnectURL) { CapabilitiesDocument caps = this.loggedServices.get(serverID); Operation[] operations = caps.getCapabilities().getOperationsMetadata().getOperationArray(); for (Operation operation : operations) { if (operation.getName().equals("DescribeProcess")) { url = operation.getDCPArray()[0].getHTTP().getGetArray()[0].getHref(); } } if (url == null) { throw new WPSClientException("Capabilities do not contain any information about the entry point for DescribeProcess operation."); } } return retrieveDescriptionViaGET(processIDs, url); } /** * Executes a process at a WPS * * @param url url of server not the entry additionally defined in the caps. * @param execute Execute document * @return either an ExecuteResponseDocument or an InputStream if asked for RawData or an Exception Report */ private Object execute(String serverID, ExecuteDocument execute, boolean rawData) throws WPSClientException{ String url = connectURL; if (!useConnectURL) { CapabilitiesDocument caps = loggedServices.get(serverID); Operation[] operations = caps.getCapabilities().getOperationsMetadata().getOperationArray(); for (Operation operation : operations) { if (operation.getName().equals("Execute")) { url = operation.getDCPArray()[0].getHTTP().getPostArray()[0].getHref(); } } if (url == null) { throw new WPSClientException( "Capabilities do not contain any information about the entry point for Execute operation."); } } execute.getExecute().setVersion(SUPPORTED_VERSION); return retrieveExecuteResponseViaPOST(url, execute,rawData); } /** * Executes a process at a WPS * * @param url url of server not the entry additionally defined in the caps. * @param execute Execute document * @return either an ExecuteResponseDocument or an InputStream if asked for RawData or an Exception Report */ public Object execute(String serverID, ExecuteDocument execute) throws WPSClientException{ if(execute.getExecute().isSetResponseForm()==true && execute.getExecute().isSetResponseForm()==true && execute.getExecute().getResponseForm().isSetRawDataOutput()==true){ return execute(serverID, execute,true); }else{ return execute(serverID, execute,false); } } private CapabilitiesDocument retrieveCapsViaGET(String url) throws WPSClientException { ClientCapabiltiesRequest req = new ClientCapabiltiesRequest(); url = req.getRequest(url); try { URL urlObj = new URL(url); urlObj.getContent(); InputStream is = urlObj.openStream(); Document doc = checkInputStream(is); return CapabilitiesDocument.Factory.parse(doc, options); } catch (MalformedURLException e) { throw new WPSClientException("Capabilities URL seems to be unvalid: " + url, e); } catch (IOException e) { throw new WPSClientException("Error occured while retrieving capabilities from url: " + url, e); } catch (XmlException e) { throw new WPSClientException("Error occured while parsing XML", e); } } private ProcessDescriptionsDocument retrieveDescriptionViaGET(String[] processIDs, String url) throws WPSClientException{ ClientDescribeProcessRequest req = new ClientDescribeProcessRequest(); req.setIdentifier(processIDs); String requestURL = req.getRequest(url); try { URL urlObj = new URL(requestURL); InputStream is = urlObj.openStream(); Document doc = checkInputStream(is); return ProcessDescriptionsDocument.Factory.parse(doc, options); } catch (MalformedURLException e) { throw new WPSClientException("URL seems not to be valid", e); } catch (IOException e) { throw new WPSClientException("Error occured while receiving data", e); } catch(XmlException e) { throw new WPSClientException("Error occured while parsing ProcessDescription document", e); } } private InputStream retrieveDataViaPOST(XmlObject obj, String urlString) throws WPSClientException{ try { URL url = new URL(urlString); URLConnection conn = url.openConnection(); conn.setRequestProperty("Accept-Encoding", "gzip"); conn.setRequestProperty("Content-Type", "text/xml"); conn.setDoOutput(true); obj.save(conn.getOutputStream()); InputStream input = null; String encoding = conn.getContentEncoding(); if(encoding != null && encoding.equalsIgnoreCase("gzip")) { input = new GZIPInputStream(conn.getInputStream()); } else { input = conn.getInputStream(); } return input; } catch (MalformedURLException e) { throw new WPSClientException("URL seems to be unvalid", e); } catch (IOException e) { throw new WPSClientException("Error while transmission", e); } } private Document checkInputStream(InputStream is) throws WPSClientException { DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); fac.setNamespaceAware(true); try { Document doc = fac.newDocumentBuilder().parse(is); if(getFirstElementNode(doc.getFirstChild()).getLocalName().equals("ExceptionReport") && getFirstElementNode(doc.getFirstChild()).getNamespaceURI().equals(OGC_OWS_URI)) { try { ExceptionReportDocument exceptionDoc = ExceptionReportDocument.Factory.parse(doc); LOGGER.debug(exceptionDoc.xmlText(options)); throw new WPSClientException("Error occured while executing query", exceptionDoc); } catch(XmlException e) { throw new WPSClientException("Error while parsing ExceptionReport retrieved from server", e); } } return doc; } catch (SAXException e) { throw new WPSClientException("Error while parsing input.", e); } catch (IOException e) { throw new WPSClientException("Error occured while transfer", e); } catch (ParserConfigurationException e) { throw new WPSClientException("Error occured, parser is not correctly configured", e); } } private Node getFirstElementNode(Node node) { if(node == null) { return null; } if(node.getNodeType() == Node.ELEMENT_NODE) { return node; } else { return getFirstElementNode(node.getNextSibling()); } } /** * either an ExecuteResponseDocument or an InputStream if asked for RawData or an Exception Report * @param url * @param doc * @param rawData * @return * @throws WPSClientException */ private Object retrieveExecuteResponseViaPOST(String url, ExecuteDocument doc, boolean rawData) throws WPSClientException{ InputStream is = retrieveDataViaPOST(doc, url); if(rawData) { return is; } Document documentObj = checkInputStream(is); ExceptionReportDocument erDoc = null; try { return ExecuteResponseDocument.Factory.parse(documentObj); } catch(XmlException e) { try { erDoc = ExceptionReportDocument.Factory.parse(documentObj); } catch (XmlException e1) { throw new WPSClientException("Error occured while parsing executeResponse", e); } return erDoc; } } public String[] getProcessNames(String url) throws IOException { ProcessDescriptionType[] processes = getProcessDescriptionsFromCache(url).getProcessDescriptions().getProcessDescriptionArray(); String[] processNames = new String[processes.length]; for(int i = 0; i<processNames.length; i++){ processNames[i] = processes[i].getIdentifier().getStringValue(); } return processNames; } /** * Executes a process at a WPS * * @param url url of server not the entry additionally defined in the caps. * @param executeAsGETString KVP Execute request * @return either an ExecuteResponseDocument or an InputStream if asked for RawData or an Exception Report */ public Object executeViaGET(String url, String executeAsGETString) throws WPSClientException { url = url + executeAsGETString; try { URL urlObj = new URL(url); InputStream is = urlObj.openStream(); if(executeAsGETString.toUpperCase().contains("RAWDATA")){ return is; } Document doc = checkInputStream(is); ExceptionReportDocument erDoc = null; try { return ExecuteResponseDocument.Factory.parse(doc); } catch(XmlException e) { try { erDoc = ExceptionReportDocument.Factory.parse(doc); } catch (XmlException e1) { throw new WPSClientException("Error occured while parsing executeResponse", e); } throw new WPSClientException("Error occured while parsing executeResponse", erDoc); } } catch (MalformedURLException e) { throw new WPSClientException("Capabilities URL seems to be unvalid: " + url, e); } catch (IOException e) { throw new WPSClientException("Error occured while retrieving capabilities from url: " + url, e); } } }