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