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