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