    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }