001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/enterprise/servlet/WFSHandler.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    
037    package org.deegree.enterprise.servlet;
038    
039    import static org.deegree.framework.util.CharsetUtils.getSystemCharset;
040    import static org.deegree.owscommon.XMLFactory.exportExceptionReportWFS;
041    import static org.deegree.owscommon.XMLFactory.exportExceptionReportWFS100;
042    
043    import java.io.IOException;
044    import java.io.ObjectOutputStream;
045    import java.io.OutputStream;
046    import java.net.MalformedURLException;
047    import java.net.URI;
048    import java.net.URL;
049    import java.util.HashMap;
050    import java.util.HashSet;
051    import java.util.Iterator;
052    import java.util.LinkedList;
053    import java.util.List;
054    import java.util.Map;
055    import java.util.Set;
056    
057    import javax.servlet.ServletOutputStream;
058    import javax.servlet.http.HttpServletResponse;
059    
060    import org.deegree.datatypes.QualifiedName;
061    import org.deegree.enterprise.ServiceException;
062    import org.deegree.framework.log.ILogger;
063    import org.deegree.framework.log.LoggerFactory;
064    import org.deegree.framework.util.CharsetUtils;
065    import org.deegree.framework.util.CollectionUtils;
066    import org.deegree.framework.util.CollectionUtils.Mapper;
067    import org.deegree.framework.xml.XMLFragment;
068    import org.deegree.framework.xml.XSLTDocument;
069    import org.deegree.i18n.Messages;
070    import org.deegree.io.datastore.schema.MappedFeatureType;
071    import org.deegree.io.datastore.schema.MappedGMLSchema;
072    import org.deegree.model.feature.FeatureCollection;
073    import org.deegree.model.feature.FeatureException;
074    import org.deegree.model.feature.FeatureTupleCollection;
075    import org.deegree.model.feature.GMLFeatureAdapter;
076    import org.deegree.model.feature.GMLFeatureCollectionDocument;
077    import org.deegree.model.spatialschema.GeometryException;
078    import org.deegree.ogcbase.PropertyPath;
079    import org.deegree.ogcwebservices.OGCWebServiceException;
080    import org.deegree.ogcwebservices.OGCWebServiceRequest;
081    import org.deegree.ogcwebservices.getcapabilities.DCPType;
082    import org.deegree.ogcwebservices.getcapabilities.HTTP;
083    import org.deegree.ogcwebservices.getcapabilities.Operation;
084    import org.deegree.ogcwebservices.wfs.WFService;
085    import org.deegree.ogcwebservices.wfs.WFServiceFactory;
086    import org.deegree.ogcwebservices.wfs.XMLFactory;
087    import org.deegree.ogcwebservices.wfs.XMLFactory_1_0_0;
088    import org.deegree.ogcwebservices.wfs.capabilities.FormatType;
089    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
090    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument;
091    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
092    import org.deegree.ogcwebservices.wfs.capabilities.WFSOperationsMetadata;
093    import org.deegree.ogcwebservices.wfs.configuration.WFSConfiguration;
094    import org.deegree.ogcwebservices.wfs.operation.AbstractWFSRequest;
095    import org.deegree.ogcwebservices.wfs.operation.AugmentableGetFeature;
096    import org.deegree.ogcwebservices.wfs.operation.DescribeFeatureType;
097    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
098    import org.deegree.ogcwebservices.wfs.operation.FeatureTypeDescription;
099    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
100    import org.deegree.ogcwebservices.wfs.operation.GetFeatureWithLock;
101    import org.deegree.ogcwebservices.wfs.operation.GetGmlObject;
102    import org.deegree.ogcwebservices.wfs.operation.GmlResult;
103    import org.deegree.ogcwebservices.wfs.operation.LockFeature;
104    import org.deegree.ogcwebservices.wfs.operation.LockFeatureResponse;
105    import org.deegree.ogcwebservices.wfs.operation.Query;
106    import org.deegree.ogcwebservices.wfs.operation.WFSGetCapabilities;
107    import org.deegree.ogcwebservices.wfs.operation.transaction.Delete;
108    import org.deegree.ogcwebservices.wfs.operation.transaction.Insert;
109    import org.deegree.ogcwebservices.wfs.operation.transaction.Transaction;
110    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionOperation;
111    import org.deegree.ogcwebservices.wfs.operation.transaction.TransactionResponse;
112    import org.deegree.ogcwebservices.wfs.operation.transaction.Update;
113    import org.w3c.dom.Document;
114    import org.w3c.dom.NamedNodeMap;
115    import org.w3c.dom.Node;
116    import org.w3c.dom.NodeList;
117    
118    /**
119     * Web servlet client for WFS.
120     * <p>
121     * NOTE: Currently, the <code>WFSHandler</code> is responsible for the pre- and postprocessing of virtual feature types.
122     * For virtual feature types, requests and responses are transformed using an XSL-script. Virtual feature types can also
123     * provide their own schema document that is sent as a response to {@link DescribeFeatureType} requests.
124     * <p>
125     * The heuristics that determines whether pre- or postprocessing is necessary, is not very accurate; check the methods:
126     * <ul>
127     * <li><code>#determineFormat(DescribeFeatureType, WFSConfiguration)</code></li>
128     * <li><code>#determineFormat(GetFeature, WFSConfiguration)</code></li>
129     * <li><code>#determineFormat(Transaction, WFSConfiguration)</code></li>
130     * </ul>
131     * <p>
132     * The code for the handling of virtual features should probably be moved to the {@link WFService} class.
133     * 
134     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
135     * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
136     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
137     * @author last edited by: $Author: aionita $
138     * 
139     * @version $Revision: 23794 $, $Date: 2010-04-23 15:05:33 +0200 (Fr, 23 Apr 2010) $
140     */
141    class WFSHandler extends AbstractOWServiceHandler {
142    
143        private static ILogger LOG = LoggerFactory.getLogger( WFSHandler.class );
144    
145        private static Map<URL, XSLTDocument> xsltCache;
146        static {
147            if ( xsltCache == null ) {
148                xsltCache = new HashMap<URL, XSLTDocument>();
149            }
150        }
151    
152        private URL getGmlObjectUrl;
153    
154        public WFSHandler() {
155            for ( Operation o : WFServiceFactory.getConfiguration().getOperationsMetadata().getOperations() ) {
156                if ( o.getName().equals( "GetGmlObject" ) ) {
157                    getGmlObjectUrl = ( (HTTP) o.getDCPs()[0].getProtocol() ).getGetOnlineResources()[0];
158                }
159            }
160        }
161    
162        /**
163         * Performs the given {@link OGCWebServiceRequest} on the {@link WFService} and sends the response to the given
164         * {@link HttpServletResponse} object.
165         * 
166         * @param request
167         *            OGCWebServiceRequest to be performed
168         * @param httpResponse
169         *            servlet response object to write to
170         * @throws ServiceException
171         */
172        public void perform( OGCWebServiceRequest request, HttpServletResponse httpResponse )
173                                throws ServiceException {
174    
175            LOG.logDebug( "Performing request: " + request.toString() );
176    
177            try {
178                WFService service = WFServiceFactory.createInstance();
179                if ( request instanceof WFSGetCapabilities ) {
180                    performGetCapabilities( service, (WFSGetCapabilities) request, httpResponse );
181                } else if ( request instanceof DescribeFeatureType ) {
182                    ( (DescribeFeatureType) request ).guessMissingNamespaces( service );
183                    performDescribeFeatureType( service, (DescribeFeatureType) request, httpResponse );
184                } else if ( request instanceof GetFeature ) {
185                    ( (GetFeature) request ).guessAllMissingNamespaces( service );
186                    performGetFeature( service, (GetFeature) request, httpResponse );
187                } else if ( request instanceof Transaction ) {
188                    ( (Transaction) request ).guessMissingNamespaces( service );
189                    performTransaction( service, (Transaction) request, httpResponse );
190                } else if ( request instanceof LockFeature ) {
191                    ( (LockFeature) request ).guessMissingNamespaces( service );
192                    performLockFeature( service, (LockFeature) request, httpResponse );
193                } else if ( request instanceof GetGmlObject ) {
194                    performGetGmlObject( service, (GetGmlObject) request, httpResponse );
195                } else {
196                    assert false : "Unhandled WFS request type: '" + request.getClass().getName() + "'";
197                }
198            } catch ( OGCWebServiceException e ) {
199                LOG.logInfo( "Error while performing WFS request.", e );
200                sendVersionedException( httpResponse, e, "1.0.0".equals( request.getVersion() ) );
201            } catch ( Exception e ) {
202                LOG.logError( "Fatal error while performing WFS request.", e );
203                sendVersionedException( httpResponse, new OGCWebServiceException( getClass().getName(), e.getMessage() ),
204                                        "1.0.0".equals( request.getVersion() ) );
205            }
206        }
207    
208        /**
209         * Performs a {@link WFSGetCapabilities} request and sends the response to the given {@link HttpServletResponse}
210         * object.
211         * 
212         * @param service
213         *            WFService instance to be used
214         * @param request
215         *            GetCapabilities request to be performed
216         * @param httpResponse
217         *            servlet response object to write to
218         * @throws OGCWebServiceException
219         */
220        private void performGetCapabilities( WFService service, WFSGetCapabilities request, HttpServletResponse httpResponse )
221                                throws OGCWebServiceException {
222    
223            WFSCapabilities capa = (WFSCapabilities) service.doService( request );
224    
225            try {
226                httpResponse.setContentType( "application/xml" );
227                WFSCapabilitiesDocument document = null;
228                String version = request.getVersion();
229                boolean use_1_1_0 = true;
230                LOG.logDebug( "Version of incoming request is: " + version );
231                if ( "1.0.0".compareTo( version ) >= 0 ) {
232                    use_1_1_0 = false;
233                }
234                if ( !use_1_1_0 ) {
235                    document = XMLFactory_1_0_0.getInstance().export( (WFSConfiguration) capa );
236                } else {
237                    document = XMLFactory.export( capa, request.getSections() );
238                }
239                OutputStream os = httpResponse.getOutputStream();
240                document.write( os );
241                os.close();
242            } catch ( IOException e ) {
243                LOG.logError( "Error sending GetCapabilities response to client.", e );
244            }
245        }
246    
247        /**
248         * Performs a {@link DescribeFeatureType} request and sends the response to the given {@link HttpServletResponse}
249         * object.
250         * 
251         * @param service
252         *            WFService instance to be used
253         * @param request
254         *            DescribeFeatureType request to be performed
255         * @param httpResponse
256         *            servlet response object to write to
257         * @throws OGCWebServiceException
258         */
259        private void performDescribeFeatureType( WFService service, DescribeFeatureType request,
260                                                 HttpServletResponse httpResponse )
261                                throws OGCWebServiceException {
262    
263            WFSConfiguration config = (WFSConfiguration) service.getCapabilities();
264            FormatType format = determineFormat( request, config, service );
265    
266            XMLFragment schemaDoc = null;
267    
268            if ( format.getSchemaLocation() != null ) {
269    
270                // check for requested types <-> configured types
271                WFSFeatureType[] featureTypes = config.getFeatureTypeList().getFeatureTypes();
272                HashSet<String> set = new HashSet<String>( featureTypes.length );
273                set.addAll( CollectionUtils.map( featureTypes, new Mapper<String, WFSFeatureType>() {
274                    public String apply( WFSFeatureType u ) {
275                        return u.getName().getLocalName();
276                    }
277                } ) );
278                for ( QualifiedName name : request.getTypeNames() ) {
279                    if ( !set.contains( name.getLocalName() ) ) {
280                        String msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", name.getLocalName() );
281                        throw new OGCWebServiceException( this.getClass().getName(), msg );
282                    }
283                }
284    
285                // read special schema for virtual format
286                try {
287                    schemaDoc = new XMLFragment( format.getSchemaLocation().toURL() );
288                } catch ( Exception e ) {
289                    String msg = Messages.getMessage( "WFS_VIRTUAL_FORMAT_SCHEMA_READ_ERROR", format.getSchemaLocation(),
290                                                      format.getValue(), e );
291                    LOG.logError( msg, e );
292                    throw new OGCWebServiceException( getClass().getName(), msg );
293                }
294            } else {
295                // get schema from WFService
296                FeatureTypeDescription ftDescription = (FeatureTypeDescription) service.doService( request );
297                schemaDoc = ftDescription.getFeatureTypeSchema();
298            }
299    
300            httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
301            try {
302                schemaDoc.write( httpResponse.getOutputStream() );
303            } catch ( IOException e ) {
304                LOG.logError( "Error sending DescribeFeatureType response to client.", e );
305            }
306        }
307    
308        /**
309         * Performs a {@link GetFeature} request and sends the response to the given {@link HttpServletResponse} object.
310         * 
311         * @param service
312         *            WFService instance to be used
313         * @param request
314         *            GetFeature request to be performed
315         * @param httpResponse
316         *            servlet response object to write to
317         * @throws OGCWebServiceException
318         */
319        private void performGetFeature( WFService service, GetFeature request, HttpServletResponse httpResponse )
320                                throws OGCWebServiceException {
321    
322            // hack: augment request if it was a KVP request with FEATUREID and no TYPENAME
323            if ( request instanceof AugmentableGetFeature ) {
324                ( (AugmentableGetFeature) request ).augment( (WFSConfiguration) service.getCapabilities() );
325            }
326    
327            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
328                try {
329                    XMLFragment xml = XMLFactory.export( request );
330                    LOG.logDebug( xml.getAsPrettyString() );
331                } catch ( Exception e ) {
332                    // nothing to do
333                }
334            }
335    
336            WFSConfiguration config = (WFSConfiguration) service.getCapabilities();
337            FormatType formatType = determineFormat( request, config, service );
338    
339            // perform pre-processing if necessary (XSLT)
340            if ( formatType.isVirtual() ) {
341                request = transformGetFeature( request, formatType );
342            }
343    
344            request.guessAllMissingNamespaces( service );
345    
346            // perform request on WFService
347            FeatureResult result = (FeatureResult) service.doService( request );
348            FeatureCollection fc = (FeatureCollection) result.getResponse();
349    
350            String format = formatType.getValue();
351    
352            if ( GetFeature.FORMAT_FEATURECOLLECTION.equals( format ) ) {
353                sendBinaryResponse( fc, httpResponse );
354            } else if ( AbstractWFSRequest.FORMAT_GML2_WFS100.equals( format )
355                        || AbstractWFSRequest.FORMAT_XML.equals( format ) || format.startsWith( "text/xml; subtype=" ) ) {
356                String schemaURL = buildSchemaURL( service, request );
357                boolean suppressXLink = suppressXLinkOutput( fc );
358                int depth = request.getTraverseXLinkDepth();
359                if ( formatType.getOutFilter() != null ) {
360                    sendTransformedResponse( fc, httpResponse, schemaURL, suppressXLink, formatType,
361                                             config.getDeegreeParams().printGeometryGmlIds(), depth, request );
362                } else {
363                    sendGMLResponse( fc, httpResponse, schemaURL, suppressXLink,
364                                     config.getDeegreeParams().printGeometryGmlIds(), depth, request );
365                }
366            } else {
367                String msg = Messages.getMessage( "WFS_QUERY_UNSUPPORTED_FORMAT2", format );
368                throw new OGCWebServiceException( msg );
369            }
370        }
371    
372        private void performGetGmlObject( WFService service, GetGmlObject request, HttpServletResponse httpResponse )
373                                throws OGCWebServiceException, IOException, FeatureException, GeometryException {
374            GmlResult result = (GmlResult) service.doService( request );
375            OGCWebServiceException exception = result.getException();
376            if ( exception != null ) {
377                throw exception;
378            }
379            ServletOutputStream out = httpResponse.getOutputStream();
380            result.writeResult( out );
381            out.close();
382        }
383    
384        /**
385         * Performs a {@link LockFeature} request and sends the response to the given {@link HttpServletResponse} object.
386         * 
387         * @param service
388         *            WFService instance to be used
389         * @param request
390         *            LockFeature request to be performed
391         * @param httpResponse
392         *            servlet response object to write to
393         * @throws OGCWebServiceException
394         */
395        private void performLockFeature( WFService service, LockFeature request, HttpServletResponse httpResponse )
396                                throws OGCWebServiceException {
397    
398            LockFeatureResponse response = (LockFeatureResponse) service.doService( request );
399            XMLFragment responseDoc;
400            try {
401                if ( request.getVersion().equals( "1.0.0" ) ) {
402                    responseDoc = XMLFactory_1_0_0.export( response );
403                } else {
404                    responseDoc = XMLFactory.export( response );
405                }
406            } catch ( Exception e ) {
407                LOG.logError( "Unknown error", e );
408                throw new OGCWebServiceException( this.getClass().getName(), e.getMessage() );
409            }
410    
411            httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
412            try {
413                responseDoc.write( httpResponse.getOutputStream() );
414            } catch ( IOException e ) {
415                LOG.logError( "Error sending LockFeature response to client.", e );
416            }
417        }
418    
419        /**
420         * Builds a KVP-encoded DescribeFeatureType-request that can be used to fetch the schemas for all feature types are
421         * that queried in the given {@link GetFeature} request.
422         * 
423         * @param service
424         * @param request
425         * @return KVP-encoded DescribeFeatureType-request
426         */
427        private String buildSchemaURL( WFService service, GetFeature request ) {
428    
429            String schemaURL = null;
430    
431            WFSCapabilities capa = service.getCapabilities();
432            WFSOperationsMetadata opMetadata = (WFSOperationsMetadata) capa.getOperationsMetadata();
433            Operation describeFTOperation = opMetadata.getDescribeFeatureType();
434            DCPType[] dcpTypes = describeFTOperation.getDCPs();
435            if ( dcpTypes.length > 0 && dcpTypes[0].getProtocol() instanceof HTTP ) {
436                HTTP http = (HTTP) dcpTypes[0].getProtocol();
437                if ( http.getGetOnlineResources().length > 0 ) {
438                    URL baseURL = http.getGetOnlineResources()[0];
439                    String requestPart = buildDescribeFTRequest( request );
440                    schemaURL = baseURL.toString() + requestPart;
441                }
442            }
443            return schemaURL;
444        }
445    
446        /**
447         * Builds the parameter part for a KVP-encoded DescribeFeatureType-request that fetches the necessary schemas for
448         * all feature types that are queried in the given {@link GetFeature} request.
449         * 
450         * @param request
451         * @return the URL-encoded parameter part of a KVP-DescribeFeatureType request
452         */
453        private String buildDescribeFTRequest( GetFeature request ) {
454    
455            Set<QualifiedName> ftNames = new HashSet<QualifiedName>();
456            Map<String, URI> nsBindings = new HashMap<String, URI>();
457    
458            // get all requested feature types
459            Query[] queries = request.getQuery();
460            for ( Query query : queries ) {
461                QualifiedName[] typeNames = query.getTypeNames();
462                for ( QualifiedName name : typeNames ) {
463                    ftNames.add( name );
464                }
465            }
466            Iterator<QualifiedName> ftNameIter = ftNames.iterator();
467            QualifiedName qn = ftNameIter.next();
468            StringBuffer typeNameSb = new StringBuffer( qn.getPrefix() );
469            typeNameSb.append( ':' ).append( qn.getLocalName() );
470            while ( ftNameIter.hasNext() ) {
471                typeNameSb.append( ',' );
472                qn = ftNameIter.next();
473                typeNameSb.append( qn.getPrefix() );
474                typeNameSb.append( ':' ).append( qn.getLocalName() );
475            }
476    
477            // get all used namespace bindings
478            for ( QualifiedName ftName : ftNames ) {
479                LOG.logDebug( "for featuretype: " + ftName.getLocalName() + " found namespace binding: "
480                              + ftName.getNamespace() );
481                nsBindings.put( ftName.getPrefix(), ftName.getNamespace() );
482            }
483            StringBuffer nsParamSb = new StringBuffer( "xmlns(" );
484            Iterator<String> prefixIter = nsBindings.keySet().iterator();
485            String prefix = prefixIter.next();
486            nsParamSb.append( prefix );
487            nsParamSb.append( '=' );
488            nsParamSb.append( nsBindings.get( prefix ) );
489            while ( prefixIter.hasNext() ) {
490                nsParamSb.append( ',' );
491                prefix = prefixIter.next();
492                nsParamSb.append( prefix );
493                nsParamSb.append( '=' );
494                nsParamSb.append( nsBindings.get( prefix ) );
495            }
496            nsParamSb.append( ')' );
497    
498            // build KVP-DescribeFeatureType-request
499            StringBuffer sb = new StringBuffer( "SERVICE=WFS" );
500            sb.append( "&VERSION=" + request.getVersion() );
501            // sb.append( "&VERSION=1.1.0" );
502            sb.append( "&REQUEST=DescribeFeatureType" );
503    
504            // append TYPENAME parameter
505            sb.append( "&TYPENAME=" );
506            sb.append( typeNameSb );
507    
508            // append NAMESPACE parameter
509            sb.append( "&NAMESPACE=" );
510            sb.append( nsParamSb.toString() );
511    
512            return sb.toString();
513        }
514    
515        /**
516         * Transforms a {@link GetFeature} request depending on the requested virtual format.
517         * 
518         * @param request
519         *            GetFeature request to be transformed
520         * @param format
521         *            requested (virtual) output format
522         * @return transformed GetFeature requested
523         * @throws OGCWebServiceException
524         *             if transformation script could not be loaded or transformation failed
525         */
526        private GetFeature transformGetFeature( GetFeature request, FormatType format )
527                                throws OGCWebServiceException {
528    
529            if ( request instanceof GetFeatureWithLock ) {
530                LOG.logDebug( "Not transforming GetFeatureWithLock request, it's not supported yet." );
531                return request;
532            }
533    
534            LOG.logDebug( "Transforming GetFeature request." );
535            long start = System.currentTimeMillis();
536    
537            URL inFilterURL = null;
538            try {
539                inFilterURL = format.getInFilter().toURL();
540            } catch ( MalformedURLException e1 ) {
541                // never happens
542            }
543            XSLTDocument xsl = xsltCache.get( inFilterURL );
544            if ( xsl == null ) {
545                xsl = new XSLTDocument();
546                try {
547                    xsl.load( inFilterURL );
548                    xsltCache.put( inFilterURL, xsl );
549                } catch ( Exception e ) {
550                    String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_FILE_ERROR", format.getValue(),
551                                                      format.getInFilter().toString(), e );
552                    LOG.logError( msg, e );
553                    throw new OGCWebServiceException( getClass().getName(), msg );
554                }
555            }
556    
557            XMLFragment xml = null;
558            try {
559                xml = XMLFactory.export( request );
560                // nasty workaround to allow the Java methods called by XSL to access the namespace bindings
561                String nsp = getAllNamespaceDeclarations( xml.getRootElement().getOwnerDocument() );
562                Map<String, String> params = new HashMap<String, String>();
563                params.put( "NSP", nsp );
564                LOG.logDebug( "Namespace string given to XSL: " + nsp );
565                xml = xsl.transform( xml, format.getInFilter().toASCIIString(), null, params );
566            } catch ( Exception e ) {
567                String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_ERROR", format.getValue(), e );
568                LOG.logError( msg, e );
569                throw new OGCWebServiceException( getClass().getName(), msg );
570            }
571    
572            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
573                LOG.logDebug( "Successfully transformed GetFeature request in " + ( System.currentTimeMillis() - start )
574                              + " milliseconds." );
575                try {
576                    LOG.logDebugXMLFile( "WFSHandler_GetFeature_transformed", xml );
577                } catch ( Exception e ) {
578                    LOG.logError( e.getMessage(), e );
579                }
580            }
581            return GetFeature.create( request.getId(), xml.getRootElement() );
582        }
583    
584        /**
585         * Sends the given {@link FeatureCollection} as GML to the given {@link HttpServletResponse} object.
586         * 
587         * @param fc
588         *            feature collection to send
589         * @param httpResponse
590         *            servlet response object to write to
591         * @param schemaURL
592         *            URL to schema document (DescribeFeatureType request)
593         * @param suppressXLinks
594         *            true, if no XLinks must be used in the output, false otherwise
595         * @param depth
596         *            the depth of xlinks to resolve
597         */
598        private void sendGMLResponse( FeatureCollection fc, HttpServletResponse httpResponse, String schemaURL,
599                                      boolean suppressXLinks, boolean sendGeometryIds, int depth, GetFeature request ) {
600    
601            try {
602                httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
603                OutputStream os = httpResponse.getOutputStream();
604                GMLFeatureAdapter featureAdapter = new GMLFeatureAdapter( suppressXLinks, schemaURL, sendGeometryIds, depth );
605                List<PropertyPath[]> names = new LinkedList<PropertyPath[]>();
606                for ( Query q : request.getQuery() ) {
607                    names.add( q.getPropertyNames() );
608                }
609                featureAdapter.setPropertyPaths( names );
610                if ( getGmlObjectUrl != null ) {
611                    featureAdapter.setBaseURL( getGmlObjectUrl.toExternalForm() );
612                }
613                featureAdapter.export( fc, os, getSystemCharset() );
614            } catch ( Exception e ) {
615                LOG.logError( "Error sending GetFeature response (GML) to client.", e );
616            }
617        }
618    
619        /**
620         * Sends the given {@link FeatureCollection} as a serialized Java object to the given {@link HttpServletResponse}
621         * object.
622         * 
623         * @param fc
624         *            feature collection to send
625         * @param httpResponse
626         *            servlet response object to write to
627         */
628        private void sendBinaryResponse( FeatureCollection fc, HttpServletResponse httpResponse ) {
629            try {
630                OutputStream os = httpResponse.getOutputStream();
631                ObjectOutputStream oos = new ObjectOutputStream( os );
632                oos.writeObject( fc );
633                oos.flush();
634            } catch ( IOException e ) {
635                LOG.logError( "Error sending GetFeature response (binary) to client.", e );
636            }
637        }
638    
639        /**
640         * Transforms a {@link FeatureCollection} to the given format using XSLT and sends it to the specified
641         * {@link HttpServletResponse} object.
642         * 
643         * @param fc
644         *            feature collection to send
645         * @param schemaURL
646         *            URL to schema document (DescribeFeatureType request)
647         * @param httpResponse
648         *            servlet response object to write to
649         * @param suppressXLinks
650         *            true, if no XLinks must be used in the output, false otherwise
651         * @param format
652         *            requested format
653         * @param sendGeometryIds
654         *            whether to send geometry gml ids
655         * @param depth
656         *            the xlink depth to resolve
657         */
658        private void sendTransformedResponse( FeatureCollection fc, HttpServletResponse httpResponse, String schemaURL,
659                                              boolean suppressXLinks, FormatType format, boolean sendGeometryIds,
660                                              int depth, GetFeature request )
661                                throws OGCWebServiceException {
662    
663            GMLFeatureCollectionDocument fcgml = null;
664            try {
665                // export result feature collection as GML to enable transformation
666                // into another (XML) format
667                GMLFeatureAdapter featureAdapter = null;
668                if ( "GML2".equals( format.getValue() ) ) {
669                    String wfsSchemaBinding = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd";
670                    featureAdapter = new GMLFeatureAdapter( suppressXLinks, schemaURL, wfsSchemaBinding, sendGeometryIds,
671                                                            depth );
672                } else {
673                    featureAdapter = new GMLFeatureAdapter( suppressXLinks, schemaURL, sendGeometryIds, depth );
674                }
675                List<PropertyPath[]> names = new LinkedList<PropertyPath[]>();
676                for ( Query q : request.getQuery() ) {
677                    names.add( q.getPropertyNames() );
678                }
679                featureAdapter.setPropertyPaths( names );
680                fcgml = featureAdapter.export( fc );
681            } catch ( Exception e ) {
682                String msg = "Could not export feature collection to GML: " + e.getMessage();
683                LOG.logError( msg, e );
684                throw new OGCWebServiceException( msg );
685            }
686    
687            LOG.logDebug( "Transforming GetFeature response." );
688            long start = System.currentTimeMillis();
689    
690            // TODO: cache Transformer
691            XSLTDocument xsl = null;
692            try {
693                xsl = new XSLTDocument( format.getOutFilter().toURL() );
694            } catch ( Exception e ) {
695                String msg = Messages.getMessage( "WFS_POSTPROCESS_XSL_FILE_ERROR", format.getValue(),
696                                                  format.getOutFilter().toString(), e );
697                LOG.logError( msg );
698                throw new OGCWebServiceException( msg );
699            }
700    
701            try {
702                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
703                    LOG.logDebugFile( "WFSHandler_GetFeature_result", ".xml", fcgml.getAsString() );
704                }
705    
706                String type = format.getValue().split( ";" )[0];
707                httpResponse.setContentType( type + "; charset=" + CharsetUtils.getSystemCharset() );
708    
709                OutputStream os = httpResponse.getOutputStream();
710                xsl.transform( fcgml, os );
711                os.close();
712            } catch ( Exception e ) {
713                String msg = Messages.getMessage( "WFS_POSTPROCESS_XSL_ERROR", format.getValue(), e );
714                LOG.logError( msg, e );
715                throw new OGCWebServiceException( getClass().getName(), msg );
716            }
717    
718            LOG.logDebug( "Successfully transformed GetFeature response in " + ( System.currentTimeMillis() - start )
719                          + " milliseconds." );
720        }
721    
722        /**
723         * Performs a {@link Transaction} request and sends the response to the given {@link HttpServletResponse} object.
724         * 
725         * @param service
726         *            WFService instance to be used
727         * @param request
728         *            Transaction request to be performed
729         * @param httpResponse
730         *            servlet response object to write to
731         * @throws OGCWebServiceException
732         */
733        private void performTransaction( WFService service, Transaction request, HttpServletResponse httpResponse )
734                                throws OGCWebServiceException {
735    
736            WFSConfiguration config = (WFSConfiguration) service.getCapabilities();
737            FormatType format = determineFormat( request, config );
738    
739            // perform pre-processing if necessary (XSLT)
740            if ( format.isVirtual() ) {
741                request = transformTransaction( request, format );
742            }
743    
744            TransactionResponse response = (TransactionResponse) service.doService( request );
745    
746            try {
747                httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
748                XMLFragment document;
749                if ( request.getVersion().equals( "1.0.0" ) ) {
750                    document = XMLFactory_1_0_0.export( response );
751                } else {
752                    document = XMLFactory.export( response );
753                }
754                document.write( httpResponse.getOutputStream() );
755            } catch ( IOException e ) {
756                LOG.logError( "Error sending Transaction response to client.", e );
757            }
758        }
759    
760        /**
761         * Transforms a {@link Transaction} request depending on the requested virtual format.
762         * 
763         * @param request
764         *            Transaction request to be transformed
765         * @param format
766         *            requested (virtual) output format
767         * @return transformed Transaction
768         * @throws OGCWebServiceException
769         *             if transformation script could not be loaded or transformation failed
770         */
771        private Transaction transformTransaction( Transaction request, FormatType format )
772                                throws OGCWebServiceException {
773    
774            LOG.logDebug( "Transforming Transaction request." );
775            long start = System.currentTimeMillis();
776    
777            URL inFilterURL = null;
778            try {
779                inFilterURL = format.getInFilter().toURL();
780            } catch ( MalformedURLException e1 ) {
781                // never happens
782            }
783            XSLTDocument xsl = xsltCache.get( inFilterURL );
784            if ( xsl == null ) {
785                xsl = new XSLTDocument();
786                try {
787                    LOG.logDebug( "Read Filter ... " );
788                    xsl.load( inFilterURL );
789                    xsltCache.put( inFilterURL, xsl );
790                } catch ( Exception e ) {
791                    String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_FILE_ERROR", format.getValue(),
792                                                      format.getInFilter().toString(), e );
793                    LOG.logError( msg, e );
794                    throw new OGCWebServiceException( getClass().getName(), msg );
795                }
796            }
797    
798            XMLFragment xml = null;
799            try {
800                xml = request.getSourceDocument();
801                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
802                    try {
803                        LOG.logDebugXMLFile( "WFSHandler_Transaction_incoming", xml );
804                    } catch ( Exception e ) {
805                        LOG.logError( e.getMessage(), e );
806                    }
807                }
808            } catch ( Exception e ) {
809                LOG.logError( e.getMessage(), e );
810                throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
811            }
812            // transform Transaction request
813            try {
814                LOG.logDebug( "start transform ..." );
815                xml = xsl.transform( xml, format.getInFilter().toASCIIString(), null, null );
816                LOG.logDebug( "end transform ..." );
817                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
818                    try {
819                        LOG.logDebugXMLFile( "WFSHandler_Transaction_transformed", xml );
820                    } catch ( Exception e ) {
821                        LOG.logError( e.getMessage(), e );
822                    }
823                }
824            } catch ( Exception e ) {
825                String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_ERROR", format.getInFilter().toString(), e );
826                LOG.logError( msg, e );
827                throw new OGCWebServiceException( getClass().getName(), msg );
828            }
829    
830            try {
831                request = Transaction.create( request.getId(), xml.getRootElement() );
832            } catch ( Exception e ) {
833                LOG.logError( e.getMessage(), e );
834                throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
835            }
836    
837            LOG.logDebug( "Successfully transformed Transaction request in " + ( System.currentTimeMillis() - start )
838                          + " milliseconds." );
839    
840            return request;
841        }
842    
843        /**
844         * Determines whether the response to the given {@link GetFeature} request may use XLinks or not.
845         * <p>
846         * The first feature of the collection is checked; if it's {@link MappedGMLSchema} requests the suppression of
847         * XLinks, xlinks are disabled, otherwise they are enabled.
848         * 
849         * @param fc
850         * @return true, if the response document must not contain XLinks, false otherwise
851         */
852        private boolean suppressXLinkOutput( FeatureCollection fc ) {
853    
854            boolean suppressXLinkOutput = false;
855    
856            if ( fc instanceof FeatureTupleCollection ) {
857                suppressXLinkOutput = true;
858            } else if ( fc.size() > 0 ) {
859                if ( fc.getFeature( 0 ).getFeatureType() instanceof MappedFeatureType ) {
860                    suppressXLinkOutput = ( (MappedFeatureType) fc.getFeature( 0 ).getFeatureType() ).getGMLSchema().suppressXLinkOutput();
861                }
862            }
863            return suppressXLinkOutput;
864        }
865    
866        private FormatType determineFormat( GetFeature request, WFSConfiguration config, WFService service )
867                                throws OGCWebServiceException {
868    
869            Query firstQuery = request.getQuery()[0];
870            QualifiedName ftName = firstQuery.getTypeNames()[0];
871            WFSFeatureType wfsFT = config.getFeatureTypeList().getFeatureType( ftName );
872            if ( wfsFT == null ) {
873                MappedFeatureType ft = service.getMappedFeatureType( ftName );
874                String msg = null;
875                if ( ft == null ) {
876                    msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", ftName );
877                } else {
878                    assert !ft.isVisible();
879                    msg = Messages.getMessage( "WFS_FEATURE_TYPE_INVISIBLE", ftName );
880                }
881                throw new OGCWebServiceException( getClass().getName(), msg );
882            }
883            String requestedFormat = request.getOutputFormat();
884            FormatType format = wfsFT.getOutputFormat( requestedFormat );
885            if ( format == null ) {
886                String msg = Messages.getMessage( "WFS_QUERY_UNSUPPORTED_FORMAT", requestedFormat, ftName );
887                throw new OGCWebServiceException( getClass().getName(), msg );
888            }
889            return format;
890        }
891    
892        private FormatType determineFormat( DescribeFeatureType request, WFSConfiguration config, WFService service )
893                                throws OGCWebServiceException {
894    
895            // NOTE: this cannot cope with a mix of virtual and real features
896            QualifiedName ftName = null;
897            if ( request.getTypeNames().length > 0 ) {
898                ftName = request.getTypeNames()[0];
899            } else {
900                // use the first ft that is available in the requested format
901                for ( WFSFeatureType tp : config.getFeatureTypeList().getFeatureTypes() ) {
902                    if ( tp.getOutputFormat( request.getOutputFormat() ) != null ) {
903                        ftName = tp.getName();
904                        break;
905                    }
906                }
907            }
908            LOG.logDebug( "typeName: " + ftName );
909    
910            WFSFeatureType wfsFT = config.getFeatureTypeList().getFeatureType( ftName );
911            if ( wfsFT == null ) {
912                MappedFeatureType ft = service.getMappedFeatureType( ftName );
913                String msg = null;
914                if ( ft == null ) {
915                    msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", ftName );
916                } else {
917                    assert !ft.isVisible();
918                    msg = Messages.getMessage( "WFS_FEATURE_TYPE_INVISIBLE", ftName );
919                }
920                throw new OGCWebServiceException( getClass().getName(), msg );
921            }
922            String requestedFormat = request.getOutputFormat();
923            LOG.logDebug( "requested outputformat: " + requestedFormat );
924            FormatType format = wfsFT.getOutputFormat( requestedFormat );
925            if ( format == null ) {
926                String msg = Messages.getMessage( "WFS_QUERY_UNSUPPORTED_FORMAT", requestedFormat, ftName );
927                throw new OGCWebServiceException( getClass().getName(), msg );
928            }
929            return format;
930        }
931    
932        private FormatType determineFormat( Transaction request, WFSConfiguration config )
933                                throws OGCWebServiceException {
934    
935            FormatType format = null;
936    
937            WFSFeatureType wfsFT = config.getFeatureTypeList().getFeatureTypes()[0];
938    
939            List<TransactionOperation> list = request.getOperations();
940            TransactionOperation op = list.get( 0 );
941            if ( op instanceof Insert ) {
942                QualifiedName qn = ( (Insert) op ).getAffectedFeatureTypes().get( 0 );
943                wfsFT = config.getFeatureTypeList().getFeatureType( qn );
944                if ( wfsFT == null ) {
945                    throw new OGCWebServiceException( Messages.getMessage( "WFS_INSERT_UNSUPPORTED_FT", qn ) );
946                }
947            } else if ( op instanceof Update ) {
948                QualifiedName qn = ( (Update) op ).getAffectedFeatureTypes().get( 0 );
949                wfsFT = config.getFeatureTypeList().getFeatureType( qn );
950                if ( wfsFT == null ) {
951                    throw new OGCWebServiceException( Messages.getMessage( "WFS_UPDATE_UNSUPPORTED_FT", qn ) );
952                }
953            } else if ( op instanceof Delete ) {
954                QualifiedName qn = ( (Delete) op ).getAffectedFeatureTypes().get( 0 );
955                wfsFT = config.getFeatureTypeList().getFeatureType( qn );
956                if ( wfsFT == null ) {
957                    throw new OGCWebServiceException( Messages.getMessage( "WFS_DELETE_UNSUPPORTED_FT", qn ) );
958                }
959            }
960    
961            FormatType[] formats = wfsFT.getOutputFormats();
962            for ( int i = 0; i < formats.length; i++ ) {
963                format = formats[i];
964                if ( format.getInFilter() != null ) {
965                    break;
966                }
967            }
968            return format;
969        }
970    
971        /**
972         * @param httpResponse
973         * @param serviceException
974         * @param is100
975         */
976        public void sendVersionedException( HttpServletResponse httpResponse, OGCWebServiceException serviceException,
977                                            boolean is100 ) {
978            try {
979                httpResponse.setContentType( "text/xml" );
980                XMLFragment reportDocument = is100 ? exportExceptionReportWFS100( serviceException )
981                                                  : exportExceptionReportWFS( serviceException );
982                OutputStream os = httpResponse.getOutputStream();
983                reportDocument.write( os );
984                os.close();
985            } catch ( Exception e ) {
986                LOG.logError( "Error sending exception report: ", e );
987            }
988    
989        }
990    
991        @Override
992        public void sendException( HttpServletResponse httpResponse, OGCWebServiceException serviceException ) {
993            try {
994                httpResponse.setContentType( "text/xml" );
995                XMLFragment reportDocument = exportExceptionReportWFS( serviceException );
996                OutputStream os = httpResponse.getOutputStream();
997                reportDocument.write( os );
998                os.close();
999            } catch ( Exception e ) {
1000                LOG.logError( "Error sending exception report: ", e );
1001            }
1002    
1003        }
1004    
1005        private String getAllNamespaceDeclarations( Document doc ) {
1006            Map<String, String> nsp = new HashMap<String, String>();
1007            nsp = collect( nsp, doc );
1008    
1009            Iterator<String> iter = nsp.keySet().iterator();
1010            StringBuffer sb = new StringBuffer( 1000 );
1011            while ( iter.hasNext() ) {
1012                String s = iter.next();
1013                String val = nsp.get( s );
1014                sb.append( s ).append( ":" ).append( val );
1015                if ( iter.hasNext() ) {
1016                    sb.append( ';' );
1017                }
1018            }
1019            return sb.toString();
1020        }
1021    
1022        private Map<String, String> collect( Map<String, String> nsp, Node node ) {
1023            NamedNodeMap nnm = node.getAttributes();
1024            if ( nnm != null ) {
1025                for ( int i = 0; i < nnm.getLength(); i++ ) {
1026                    String s = nnm.item( i ).getNodeName();
1027                    if ( s.startsWith( "xmlns:" ) ) {
1028                        nsp.put( s.substring( 6, s.length() ), nnm.item( i ).getNodeValue() );
1029                    }
1030                }
1031            }
1032            NodeList nl = node.getChildNodes();
1033            if ( nl != null ) {
1034                for ( int i = 0; i < nl.getLength(); i++ ) {
1035                    collect( nsp, nl.item( i ) );
1036                }
1037            }
1038            return nsp;
1039        }
1040    
1041    }