//$Header$ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53115 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.portal.portlet.modules.wfs.actions.portlets; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.jetspeed.portal.Portlet; import org.deegree.datatypes.Types; import org.deegree.enterprise.control.RPCException; import org.deegree.enterprise.control.RPCFactory; import org.deegree.enterprise.control.RPCMethodCall; import org.deegree.enterprise.control.RPCParameter; import org.deegree.enterprise.control.RPCStruct; import org.deegree.enterprise.control.RPCUtils; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.XMLFragment; import org.deegree.framework.xml.XSLTDocument; import org.deegree.model.crs.CRSFactory; import org.deegree.model.crs.CoordinateSystem; import org.deegree.model.crs.GeoTransformer; import org.deegree.model.crs.IGeoTransformer; import org.deegree.model.crs.UnknownCRSException; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureCollection; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.GMLFeatureCollectionDocument; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.spatialschema.Geometry; import org.deegree.ogcwebservices.OGCWebServiceException; import org.deegree.ogcwebservices.OWSUtils; import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException; import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument; import org.deegree.ogcwebservices.wfs.operation.GetFeature; import org.deegree.portal.PortalException; import org.deegree.portal.portlet.modules.actions.IGeoPortalPortletPerform; /** * * * @version $Revision$ * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version 1.0. $Revision$, $Date$ * * @since 2.0 */ public class WFSClientPortletPerform extends IGeoPortalPortletPerform { private static final ILogger LOG = LoggerFactory.getLogger( WFSClientPortletPerform.class ); protected static final String INIT_TARGETSRS = "TARGETSRS"; protected static final String INIT_XSLT = "XSLT"; private static Map<String,WFSCapabilities> capaMap = new HashMap<String,WFSCapabilities>(); /** * @param request * @param portlet */ public WFSClientPortletPerform( HttpServletRequest request, Portlet portlet, ServletContext servletContext ) { super( request, portlet, servletContext ); } /** * * @return */ protected void doGetfeature() throws PortalException, OGCWebServiceException { RPCParameter[] rpcParams = extractRPCParameters(); Map<String,FeatureCollection> allFCs = new HashMap<String,FeatureCollection>(); for ( int i = 1; i < rpcParams.length; i++ ) { // first field will be skipped because it contains informations // about the desired result format RPCStruct struct = (RPCStruct)rpcParams[i].getValue(); String tmp = RPCUtils.getRpcPropertyAsString( struct, "featureTypes" ); String[] arr = StringTools.toArray( tmp, ",", true ); String[] xmlns = new String[arr.length]; String[] featureTypes = new String[arr.length]; for ( int j = 0; j < arr.length; j++ ) { int p = arr[j].lastIndexOf( ':' ); xmlns[j] = arr[j].substring( 0, p ); featureTypes[j] = arr[j].substring( p+1, arr[j].length() ); } for ( int j = 0; j < featureTypes.length; j++ ) { String query = createQuery( struct, xmlns, featureTypes ); LOG.logDebug( "queried feature type: " + xmlns[j] + featureTypes[j] ); LOG.logDebug( "Query: \n" + query ); Map<String,FeatureCollection>fcs = performQuery( featureTypes[j], xmlns[j], query ); if ( getInitParam( INIT_TARGETSRS ) != null ) { Iterator iter = fcs.keySet().iterator(); while ( iter.hasNext() ) { String key = (String) iter.next(); FeatureCollection tmpFc = fcs.get( key ); fcs.put( key, transformGeometries( tmpFc ) ); } } allFCs.putAll( fcs ); } } writeGetFeatureResult( allFCs, (String)rpcParams[0].getValue() ); } /** * creates a WFS query depending on requested construction type * @param struct * @param xmlns * @param featureTypes * @return * @throws PortalException */ private String createQuery( RPCStruct struct, String[] xmlns, String[] featureTypes ) throws PortalException { String query = null; String template = RPCUtils.getRpcPropertyAsString( struct, "queryTemplate" ); if ( template != null ) { RPCParameter[] filterProps = null; if ( struct.getMember( "filterProperties" ) != null ) { filterProps = (RPCParameter[])struct.getMember( "filterProperties" ).getValue(); } query = createQueryFromTemplate( template, filterProps ); } else if ( parameter.get( "FILTER" ) != null ) { String filter = (String) parameter.get( "FILTER" ); query = createQueryFromFilter( featureTypes, xmlns, filter ); } else { String filter = createFilterFromProperties(); query = createQueryFromFilter( featureTypes, xmlns, filter ); } return query; } /** * extracts the @see RPCParameter array from the RPC method call * @return * @throws PortalException */ protected RPCParameter[] extractRPCParameters() throws PortalException { String tmp = (String) parameter.get( "RPC" ); StringReader sr = new StringReader( tmp ); RPCMethodCall rpcMethod = null; try { rpcMethod = RPCFactory.createRPCMethodCall( sr ); } catch ( RPCException e ) { LOG.logError( e.getMessage(), e ); throw new PortalException( e.getMessage() ); } RPCParameter[] rpcParams = rpcMethod.getParameters(); return rpcParams; } /** * performs a transaction against a WFS-T or a database. The backend type * to be used by a transaction depends on a portlets initParameters. * @throws PortalException */ public void doTransaction() throws PortalException { System.out.println( parameter ); } /** * writes the result into the forwarded request object * @param xml * @param fc * @throws PortalException */ private void writeGetFeatureResult( Map<String,FeatureCollection> fcs, String format ) throws PortalException { if ( "XML".equals( format ) ) { XMLFragment xml = new XMLFragment(); /* if ( fcs != null ) { ByteArrayOutputStream bos = new ByteArrayOutputStream( 100000 ); try { new GMLFeatureAdapter().export( fc, bos ); xml.load( new ByteArrayInputStream( bos.toByteArray() ), XMLFragment.DEFAULT_URL ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new PortalException( "could not export feature collection as GML", e ); } } if ( getInitParam( INIT_XSLT ) != null ) { xml = transform( xml ); } request.setAttribute( "RESULT", xml ); */ } else { request.setAttribute( "RESULT", fcs ); } } /** * transforms the result of a WFS request using the XSLT script defined * by an init parameter * @param xml * @return * @throws PortalException */ private XMLFragment transform( XMLFragment xml ) throws PortalException { String xslF = getInitParam( INIT_XSLT ); File file = new File( xslF ); if ( !file.isAbsolute() ) { file = new File( sc.getRealPath( xslF ) ); } XSLTDocument xslt = new XSLTDocument(); try { xslt.load( file.toURL() ); xml = xslt.transform( xml ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new PortalException( "could not transform result of WFS request", e ); } return xml; } /** * transforms the geometry properties of the features contained in the * passed feature collection into the target CRS given by an init parameter * @param fc * @return * @throws PortalException */ private FeatureCollection transformGeometries( FeatureCollection fc ) throws PortalException { String cs = getInitParam( INIT_TARGETSRS ); CoordinateSystem crs; try { crs = CRSFactory.create( cs ); } catch ( UnknownCRSException e1 ) { throw new PortalException( e1.getMessage(), e1 ); } if ( crs == null ) { throw new PortalException( "CRS: " + cs + " is not known by deegree" ); } try { IGeoTransformer gt = new GeoTransformer( crs ); for ( int i = 0; i < fc.size(); i++ ) { Feature feature = fc.getFeature( i ); FeatureType ft = feature.getFeatureType(); FeatureProperty[] fp = feature.getProperties(); for ( int j = 0; j < fp.length; j++ ) { if ( ft.getProperty( fp[j].getName() ).getType() == Types.GEOMETRY ) { Geometry geom = (Geometry) fp[j].getValue(); if ( !crs.equals( geom.getCoordinateSystem() ) ) { geom = gt.transform( geom ); fp[j].setValue( geom ); } } } } } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new PortalException( "could not transform geometries to target CRS: " + cs, e ); } return fc; } /** * performs a GetFeature query against one or more WFS's * @param featureType * @param namespace * @param query * @return * @throws OGCWebServiceException */ private Map<String,FeatureCollection> performQuery( String featureType, String namespace, String query ) throws OGCWebServiceException { // WFS to contact String addr = getInitParam( namespace + ':' + featureType ); if ( addr == null ) { // if a client does not send the name of the target WFS // 'WFS' will be used to get the target WFS address from // the portlets init-parameter addr = getInitParam( "WFS" ); } if ( addr == null ) { throw new OGCWebServiceException( "WFS: " + namespace + ':' + featureType + " is not known by the portal" ); } // a featuretype may be assigned to more than one WFS String[] addresses = StringTools.toArray( addr, ",", false ); Map<String,FeatureCollection> docs = new HashMap<String,FeatureCollection>(); for ( int i = 0; i < addresses.length; i++ ) { if ( capaMap.get( addresses[i] ) == null ) { // if the WFS Capabilities has not already been read from this // address it will be done now. The result will be stored in the // static Map 'capaMap' to be available at the next call loadWFSCapabilities( addresses[i] ); } URL url = OWSUtils.getHTTPPostOperationURL( capaMap.get( addresses[i] ), GetFeature.class ); LOG.logDebug( "performing query: ", query ); StringRequestEntity re = new StringRequestEntity( query ); PostMethod post = new PostMethod( url.toExternalForm() ); post.setRequestEntity( re ); InputStream is = null; try { HttpClient client = new HttpClient(); client.executeMethod( post ); is = post.getResponseBodyAsStream(); } catch ( IOException e ) { LOG.logError( e.getMessage(), e ); throw new OGCWebServiceException( "could not perform query against the WFS: " + namespace + ':' + featureType ); } try { GMLFeatureCollectionDocument xml = new GMLFeatureCollectionDocument(); xml.load( is, addresses[i] ); // put the result on a Map that will be forced to the client // which is responsible for what to do with it. Because the keys // of the Map are the WFS addresses the client is able to reconstruct // the source of the result parts docs.put( addresses[i], xml.parse() ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new OGCWebServiceException( "could not parse response from WFS: " + namespace + ':' + featureType + " as XML" ); } } return docs; } /** * performs a GetCapabilities request against the passed address * and stores the result (if it is a valid WFS capabilities document) * in a static Map. * * @param addr * @throws OGCWebServiceException * @throws InvalidCapabilitiesException */ private void loadWFSCapabilities( String addr ) throws OGCWebServiceException, InvalidCapabilitiesException { LOG.logDebug( "reading capabilities from: ", addr ); WFSCapabilitiesDocument doc = new WFSCapabilitiesDocument(); try { doc.load( new URL( OWSUtils.validateHTTPGetBaseURL(addr) + "version=1.1.0&service=WFS&request=GetCapabilities" ) ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new OGCWebServiceException( "could not read capabilities from WFS: " + addr ); } WFSCapabilities capa = (WFSCapabilities)doc.parseCapabilities(); capaMap.put( addr, capa ); } /** * creates a WFS GetFeature query from a named template and * a set of KVP-encoded properties * @param queryTemplate * @param filterProps * @return * @throws PortalException */ private String createQueryFromTemplate(String queryTemplate, RPCParameter[] filterProps) throws PortalException { queryTemplate = getInitParam( queryTemplate ); if ( !( new File( queryTemplate ).isAbsolute() ) ) { queryTemplate = sc.getRealPath( queryTemplate ); } StringBuffer template = new StringBuffer( 10000 ); try { BufferedReader br = new BufferedReader( new FileReader( queryTemplate ) ); String line = null; while ( ( line = br.readLine() ) != null ) { template.append( line ); } br.close(); } catch ( IOException e ) { LOG.logError( e.getMessage(), e ); throw new PortalException( "could not read query template: " + parameter.get( "TEMPLATE" ) ); } String query = template.toString(); if ( filterProps != null ) { for ( int i = 0; i < filterProps.length; i++ ) { RPCStruct struct = (RPCStruct)filterProps[i].getValue(); String name = RPCUtils.getRpcPropertyAsString( struct, "propertyName" ); String value = RPCUtils.getRpcPropertyAsString( struct, "value" ); value = StringTools.replace( value, "XXX", "%", true ); query = StringTools.replace( query, '$' + name, value, true ); } } return query; } /** * creates a WFS GetFeature query from a OGC filter expression * send from a client * * @return * @throws PortalException */ private String createQueryFromFilter( String[] featureTypes, String[] xmlns, String filter ) { StringBuffer query = new StringBuffer( 20000 ); String format = "text/xml; subtype=gml/3.1.1"; int maxFeatures = -1; String resultType = "results"; if ( parameter.get( "OUTPUTFORMAT" ) != null ) { format = (String) parameter.get( "OUTPUTFORMAT" ); } if ( parameter.get( "MAXFEATURE" ) != null ) { maxFeatures = Integer.parseInt( (String) parameter.get( "MAXFEATURE" ) ); } if ( parameter.get( "RESULTTYPE" ) != null ) { resultType = (String) parameter.get( "RESULTTYPE" ); } query.append( "<wfs:GetFeature outputFormat='" ).append( format ); query.append( "' maxFeatures='" ).append( maxFeatures ).append( "' " ); query.append( " resultType='" ).append( resultType ).append( "' " ); for ( int i = 0; i < xmlns.length; i++ ) { String[] tmp = StringTools.toArray( xmlns[i], "=", false ); query.append( "xmlns:" ).append( tmp[0] ).append( "='" ); query.append( tmp[1] ).append( "' " ); } query.append( "xmlns:wfs='http://www.opengis.net/wfs' " ); query.append( "xmlns:ogc='http://www.opengis.net/ogc' " ); query.append( "xmlns:gml='http://www.opengis.net/gml' " ); query.append( ">" ); query.append( "<wfs:Query " ); for ( int i = 0; i < featureTypes.length; i++ ) { query.append( "typeName='" ).append( featureTypes[i] ); if ( i < featureTypes.length - 1 ) { query.append( "," ); } } query.append( "'>" ); query.append( filter ); query.append( "</wfs:Query></wfs:GetFeature>" ); return query.toString(); } /** * creates an OGC FE filter from a set of KVP-encode properties and * logical opertaions * @return */ private String createFilterFromProperties() { String tmp = (String) parameter.get( "FILTERPROPERTIES" ); if ( tmp != null ) { String[] properties = StringTools.extractStrings( tmp, "{", "}" ); String logOp = (String) parameter.get( "LOGICALOPERATOR" ); StringBuffer filter = new StringBuffer( 10000 ); filter.append( "<ogc:Filter>" ); if ( properties.length > 1 ) { filter.append( "<ogc:" ).append( logOp ).append( '>' ); } for ( int i = 0; i < properties.length; i++ ) { String[] prop = StringTools.extractStrings( tmp, "[", "]" ); if ( "!=".equals( prop[1] ) || "NOT LIKE".equals( prop[1] ) ) { filter.append( "<ogc:Not>" ); } if ( "=".equals( prop[1] ) || "!=".equals( prop[1] ) ) { filter.append( "<ogc:PropertyIsEqualTo>" ); filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); filter.append( "</ogc:PropertyIsEqualTo>" ); } else if ( ">=".equals( prop[1] ) ) { filter.append( "<ogc:PropertyIsGreaterThanOrEqualTo>" ); filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); filter.append( "</ogc:PropertyIsGreaterThanOrEqualTo>" ); } else if ( ">".equals( prop[1] ) ) { filter.append( "<ogc:PropertyIsGreaterThan>" ); filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); filter.append( "</ogc:PropertyIsGreaterThan>" ); } else if ( "<=".equals( prop[1] ) ) { filter.append( "<ogc:PropertyIsLessThanOrEqualTo>" ); filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); filter.append( "</ogc:PropertyIsLessThanOrEqualTo>" ); } else if ( "<".equals( prop[1] ) ) { filter.append( "<ogc:PropertyIsLessThan>" ); filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); filter.append( "</ogc:PropertyIsLessThan>" ); } else if ( "LIKE".equals( prop[1] ) || "NOT LIKE".equals( prop[1] ) ) { filter.append( "<ogc:PropertyIsLike wildCard='%' singleChar='#' escape='!'>" ); filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); filter.append( "</ogc:PropertyIsLike>" ); } if ( "!=".equals( prop[1] ) || "NOT LIKE".equals( prop[1] ) ) { filter.append( "</ogc:Not>" ); } } if ( properties.length > 1 ) { filter.append( "</ogc:" ).append( logOp ).append( '>' ); } filter.append( "</ogc:Filter>" ); return filter.toString(); } return ""; } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log$ Revision 1.16 2006/11/27 09:07:52 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.15 2006/10/17 20:31:20 poth *** empty log message *** Revision 1.14 2006/09/18 15:41:06 poth *** empty log message *** Revision 1.13 2006/08/29 19:54:14 poth footer corrected Revision 1.12 2006/05/03 20:09:52 poth *** empty log message *** Revision 1.11 2006/05/01 20:15:27 poth *** empty log message *** Revision 1.10 2006/04/18 18:22:55 poth *** empty log message *** Revision 1.9 2006/04/18 18:20:26 poth *** empty log message *** Revision 1.8 2006/04/06 20:25:30 poth *** empty log message *** Revision 1.7 2006/03/30 21:20:28 poth *** empty log message *** Revision 1.6 2006/03/22 21:19:41 poth *** empty log message *** Revision 1.5 2006/03/06 17:51:46 poth *** empty log message *** Revision 1.4 2006/03/04 20:36:18 poth *** empty log message *** Revision 1.3 2006/02/27 16:14:11 poth *** empty log message *** Revision 1.2 2006/02/23 07:45:24 poth *** empty log message *** Revision 1.1 2006/02/07 19:52:44 poth *** empty log message *** Revision 1.1 2006/02/07 13:13:57 poth *** empty log message *** Revision 1.1 2006/02/05 09:30:11 poth *** empty log message *** Revision 1.3 2005/10/05 20:45:11 ap *** empty log message *** Revision 1.2 2005/09/16 09:38:18 ap *** empty log message *** Revision 1.1 2005/09/16 07:06:30 ap *** empty log message *** Revision 1.1 2005/09/15 09:45:48 ap *** empty log message *** ********************************************************************** */