001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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: mschneider $
138 *
139 * @version $Revision: 21632 $, $Date: 2009-12-23 10:31:16 +0100 (Mi, 23. Dez 2009) $
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 ).guessMissingTypeNameNamespaces( service );
186 performGetFeature( service, (GetFeature) request, httpResponse );
187 } else if ( request instanceof Transaction ) {
188 performTransaction( service, (Transaction) request, httpResponse );
189 } else if ( request instanceof LockFeature ) {
190 ( (LockFeature) request ).guessMissingNamespaces( service );
191 performLockFeature( service, (LockFeature) request, httpResponse );
192 } else if ( request instanceof GetGmlObject ) {
193 performGetGmlObject( service, (GetGmlObject) request, httpResponse );
194 } else {
195 assert false : "Unhandled WFS request type: '" + request.getClass().getName() + "'";
196 }
197 } catch ( OGCWebServiceException e ) {
198 LOG.logInfo( "Error while performing WFS request.", e );
199 sendVersionedException( httpResponse, e, "1.0.0".equals( request.getVersion() ) );
200 } catch ( Exception e ) {
201 LOG.logError( "Fatal error while performing WFS request.", e );
202 sendVersionedException( httpResponse, new OGCWebServiceException( getClass().getName(), e.getMessage() ),
203 "1.0.0".equals( request.getVersion() ) );
204 }
205 }
206
207 /**
208 * Performs a {@link WFSGetCapabilities} request and sends the response to the given {@link HttpServletResponse}
209 * object.
210 *
211 * @param service
212 * WFService instance to be used
213 * @param request
214 * GetCapabilities request to be performed
215 * @param httpResponse
216 * servlet response object to write to
217 * @throws OGCWebServiceException
218 */
219 private void performGetCapabilities( WFService service, WFSGetCapabilities request, HttpServletResponse httpResponse )
220 throws OGCWebServiceException {
221
222 WFSCapabilities capa = (WFSCapabilities) service.doService( request );
223
224 try {
225 httpResponse.setContentType( "application/xml" );
226 WFSCapabilitiesDocument document = null;
227 String version = request.getVersion();
228 boolean use_1_1_0 = true;
229 LOG.logDebug( "Version of incoming request is: " + version );
230 if ( "1.0.0".compareTo( version ) >= 0 ) {
231 use_1_1_0 = false;
232 }
233 if ( !use_1_1_0 ) {
234 document = XMLFactory_1_0_0.getInstance().export( (WFSConfiguration) capa );
235 } else {
236 document = XMLFactory.export( capa, request.getSections() );
237 }
238 OutputStream os = httpResponse.getOutputStream();
239 document.write( os );
240 os.close();
241 } catch ( IOException e ) {
242 LOG.logError( "Error sending GetCapabilities response to client.", e );
243 }
244 }
245
246 /**
247 * Performs a {@link DescribeFeatureType} request and sends the response to the given {@link HttpServletResponse}
248 * object.
249 *
250 * @param service
251 * WFService instance to be used
252 * @param request
253 * DescribeFeatureType request to be performed
254 * @param httpResponse
255 * servlet response object to write to
256 * @throws OGCWebServiceException
257 */
258 private void performDescribeFeatureType( WFService service, DescribeFeatureType request,
259 HttpServletResponse httpResponse )
260 throws OGCWebServiceException {
261
262 WFSConfiguration config = (WFSConfiguration) service.getCapabilities();
263 FormatType format = determineFormat( request, config, service );
264
265 XMLFragment schemaDoc = null;
266
267 if ( format.getSchemaLocation() != null ) {
268
269 // check for requested types <-> configured types
270 WFSFeatureType[] featureTypes = config.getFeatureTypeList().getFeatureTypes();
271 HashSet<String> set = new HashSet<String>( featureTypes.length );
272 set.addAll( CollectionUtils.map( featureTypes, new Mapper<String, WFSFeatureType>() {
273 public String apply( WFSFeatureType u ) {
274 return u.getName().getLocalName();
275 }
276 } ) );
277 for ( QualifiedName name : request.getTypeNames() ) {
278 if ( !set.contains( name.getLocalName() ) ) {
279 String msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", name.getLocalName() );
280 throw new OGCWebServiceException( this.getClass().getName(), msg );
281 }
282 }
283
284 // read special schema for virtual format
285 try {
286 schemaDoc = new XMLFragment( format.getSchemaLocation().toURL() );
287 } catch ( Exception e ) {
288 String msg = Messages.getMessage( "WFS_VIRTUAL_FORMAT_SCHEMA_READ_ERROR", format.getSchemaLocation(),
289 format.getValue(), e );
290 LOG.logError( msg, e );
291 throw new OGCWebServiceException( getClass().getName(), msg );
292 }
293 } else {
294 // get schema from WFService
295 FeatureTypeDescription ftDescription = (FeatureTypeDescription) service.doService( request );
296 schemaDoc = ftDescription.getFeatureTypeSchema();
297 }
298
299 httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
300 try {
301 schemaDoc.write( httpResponse.getOutputStream() );
302 } catch ( IOException e ) {
303 LOG.logError( "Error sending DescribeFeatureType response to client.", e );
304 }
305 }
306
307 /**
308 * Performs a {@link GetFeature} request and sends the response to the given {@link HttpServletResponse} object.
309 *
310 * @param service
311 * WFService instance to be used
312 * @param request
313 * GetFeature request to be performed
314 * @param httpResponse
315 * servlet response object to write to
316 * @throws OGCWebServiceException
317 */
318 private void performGetFeature( WFService service, GetFeature request, HttpServletResponse httpResponse )
319 throws OGCWebServiceException {
320
321 // hack: augment request if it was a KVP request with FEATUREID and no TYPENAME
322 if ( request instanceof AugmentableGetFeature ) {
323 ( (AugmentableGetFeature) request ).augment( (WFSConfiguration) service.getCapabilities() );
324 }
325
326 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
327 try {
328 XMLFragment xml = XMLFactory.export( request );
329 LOG.logDebug( xml.getAsPrettyString() );
330 } catch ( Exception e ) {
331 // nothing to do
332 }
333 }
334
335 WFSConfiguration config = (WFSConfiguration) service.getCapabilities();
336 FormatType formatType = determineFormat( request, config, service );
337
338 // perform pre-processing if necessary (XSLT)
339 if ( formatType.isVirtual() ) {
340 request = transformGetFeature( request, formatType );
341 }
342
343 request.guessAllMissingNamespaces( service );
344
345 // perform request on WFService
346 FeatureResult result = (FeatureResult) service.doService( request );
347 FeatureCollection fc = (FeatureCollection) result.getResponse();
348
349 String format = formatType.getValue();
350
351 if ( GetFeature.FORMAT_FEATURECOLLECTION.equals( format ) ) {
352 sendBinaryResponse( fc, httpResponse );
353 } else if ( AbstractWFSRequest.FORMAT_GML2_WFS100.equals( format )
354 || AbstractWFSRequest.FORMAT_XML.equals( format ) || format.startsWith( "text/xml; subtype=" ) ) {
355 String schemaURL = buildSchemaURL( service, request );
356 boolean suppressXLink = suppressXLinkOutput( fc );
357 int depth = request.getTraverseXLinkDepth();
358 if ( formatType.getOutFilter() != null ) {
359 sendTransformedResponse( fc, httpResponse, schemaURL, suppressXLink, formatType,
360 config.getDeegreeParams().printGeometryGmlIds(), depth, request );
361 } else {
362 sendGMLResponse( fc, httpResponse, schemaURL, suppressXLink,
363 config.getDeegreeParams().printGeometryGmlIds(), depth, request );
364 }
365 } else {
366 String msg = Messages.getMessage( "WFS_QUERY_UNSUPPORTED_FORMAT2", format );
367 throw new OGCWebServiceException( msg );
368 }
369 }
370
371 private void performGetGmlObject( WFService service, GetGmlObject request, HttpServletResponse httpResponse )
372 throws OGCWebServiceException, IOException, FeatureException, GeometryException {
373 GmlResult result = (GmlResult) service.doService( request );
374 OGCWebServiceException exception = result.getException();
375 if ( exception != null ) {
376 throw exception;
377 }
378 ServletOutputStream out = httpResponse.getOutputStream();
379 result.writeResult( out );
380 out.close();
381 }
382
383 /**
384 * Performs a {@link LockFeature} request and sends the response to the given {@link HttpServletResponse} object.
385 *
386 * @param service
387 * WFService instance to be used
388 * @param request
389 * LockFeature request to be performed
390 * @param httpResponse
391 * servlet response object to write to
392 * @throws OGCWebServiceException
393 */
394 private void performLockFeature( WFService service, LockFeature request, HttpServletResponse httpResponse )
395 throws OGCWebServiceException {
396
397 LockFeatureResponse response = (LockFeatureResponse) service.doService( request );
398 XMLFragment responseDoc;
399 try {
400 if ( request.getVersion().equals( "1.0.0" ) ) {
401 responseDoc = XMLFactory_1_0_0.export( response );
402 } else {
403 responseDoc = XMLFactory.export( response );
404 }
405 } catch ( Exception e ) {
406 LOG.logError( "Unknown error", e );
407 throw new OGCWebServiceException( this.getClass().getName(), e.getMessage() );
408 }
409
410 httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
411 try {
412 responseDoc.write( httpResponse.getOutputStream() );
413 } catch ( IOException e ) {
414 LOG.logError( "Error sending LockFeature response to client.", e );
415 }
416 }
417
418 /**
419 * Builds a KVP-encoded DescribeFeatureType-request that can be used to fetch the schemas for all feature types are
420 * that queried in the given {@link GetFeature} request.
421 *
422 * @param service
423 * @param request
424 * @return KVP-encoded DescribeFeatureType-request
425 */
426 private String buildSchemaURL( WFService service, GetFeature request ) {
427
428 String schemaURL = null;
429
430 WFSCapabilities capa = service.getCapabilities();
431 WFSOperationsMetadata opMetadata = (WFSOperationsMetadata) capa.getOperationsMetadata();
432 Operation describeFTOperation = opMetadata.getDescribeFeatureType();
433 DCPType[] dcpTypes = describeFTOperation.getDCPs();
434 if ( dcpTypes.length > 0 && dcpTypes[0].getProtocol() instanceof HTTP ) {
435 HTTP http = (HTTP) dcpTypes[0].getProtocol();
436 if ( http.getGetOnlineResources().length > 0 ) {
437 URL baseURL = http.getGetOnlineResources()[0];
438 String requestPart = buildDescribeFTRequest( request );
439 schemaURL = baseURL.toString() + requestPart;
440 }
441 }
442 return schemaURL;
443 }
444
445 /**
446 * Builds the parameter part for a KVP-encoded DescribeFeatureType-request that fetches the necessary schemas for
447 * all feature types that are queried in the given {@link GetFeature} request.
448 *
449 * @param request
450 * @return the URL-encoded parameter part of a KVP-DescribeFeatureType request
451 */
452 private String buildDescribeFTRequest( GetFeature request ) {
453
454 Set<QualifiedName> ftNames = new HashSet<QualifiedName>();
455 Map<String, URI> nsBindings = new HashMap<String, URI>();
456
457 // get all requested feature types
458 Query[] queries = request.getQuery();
459 for ( Query query : queries ) {
460 QualifiedName[] typeNames = query.getTypeNames();
461 for ( QualifiedName name : typeNames ) {
462 ftNames.add( name );
463 }
464 }
465 Iterator<QualifiedName> ftNameIter = ftNames.iterator();
466 QualifiedName qn = ftNameIter.next();
467 StringBuffer typeNameSb = new StringBuffer( qn.getPrefix() );
468 typeNameSb.append( ':' ).append( qn.getLocalName() );
469 while ( ftNameIter.hasNext() ) {
470 typeNameSb.append( ',' );
471 qn = ftNameIter.next();
472 typeNameSb.append( qn.getPrefix() );
473 typeNameSb.append( ':' ).append( qn.getLocalName() );
474 }
475
476 // get all used namespace bindings
477 for ( QualifiedName ftName : ftNames ) {
478 LOG.logDebug( "for featuretype: " + ftName.getLocalName() + " found namespace binding: "
479 + ftName.getNamespace() );
480 nsBindings.put( ftName.getPrefix(), ftName.getNamespace() );
481 }
482 StringBuffer nsParamSb = new StringBuffer( "xmlns(" );
483 Iterator<String> prefixIter = nsBindings.keySet().iterator();
484 String prefix = prefixIter.next();
485 nsParamSb.append( prefix );
486 nsParamSb.append( '=' );
487 nsParamSb.append( nsBindings.get( prefix ) );
488 while ( prefixIter.hasNext() ) {
489 nsParamSb.append( ',' );
490 prefix = prefixIter.next();
491 nsParamSb.append( prefix );
492 nsParamSb.append( '=' );
493 nsParamSb.append( nsBindings.get( prefix ) );
494 }
495 nsParamSb.append( ')' );
496
497 // build KVP-DescribeFeatureType-request
498 StringBuffer sb = new StringBuffer( "SERVICE=WFS" );
499 sb.append( "&VERSION=" + request.getVersion() );
500 // sb.append( "&VERSION=1.1.0" );
501 sb.append( "&REQUEST=DescribeFeatureType" );
502
503 // append TYPENAME parameter
504 sb.append( "&TYPENAME=" );
505 sb.append( typeNameSb );
506
507 // append NAMESPACE parameter
508 sb.append( "&NAMESPACE=" );
509 sb.append( nsParamSb.toString() );
510
511 return sb.toString();
512 }
513
514 /**
515 * Transforms a {@link GetFeature} request depending on the requested virtual format.
516 *
517 * @param request
518 * GetFeature request to be transformed
519 * @param format
520 * requested (virtual) output format
521 * @return transformed GetFeature requested
522 * @throws OGCWebServiceException
523 * if transformation script could not be loaded or transformation failed
524 */
525 private GetFeature transformGetFeature( GetFeature request, FormatType format )
526 throws OGCWebServiceException {
527
528 if ( request instanceof GetFeatureWithLock ) {
529 LOG.logDebug( "Not transforming GetFeatureWithLock request, it's not supported yet." );
530 return request;
531 }
532
533 LOG.logDebug( "Transforming GetFeature request." );
534 long start = System.currentTimeMillis();
535
536 URL inFilterURL = null;
537 try {
538 inFilterURL = format.getInFilter().toURL();
539 } catch ( MalformedURLException e1 ) {
540 // never happens
541 }
542 XSLTDocument xsl = xsltCache.get( inFilterURL );
543 if ( xsl == null ) {
544 xsl = new XSLTDocument();
545 try {
546 xsl.load( inFilterURL );
547 xsltCache.put( inFilterURL, xsl );
548 } catch ( Exception e ) {
549 String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_FILE_ERROR", format.getValue(),
550 format.getInFilter().toString(), e );
551 LOG.logError( msg, e );
552 throw new OGCWebServiceException( getClass().getName(), msg );
553 }
554 }
555
556 XMLFragment xml = null;
557 try {
558 xml = XMLFactory.export( request );
559 // nasty workaround to allow the Java methods called by XSL to access the namespace bindings
560 String nsp = getAllNamespaceDeclarations( xml.getRootElement().getOwnerDocument() );
561 Map<String, String> params = new HashMap<String, String>();
562 params.put( "NSP", nsp );
563 LOG.logDebug( "Namespace string given to XSL: " + nsp );
564 xml = xsl.transform( xml, format.getInFilter().toASCIIString(), null, params );
565 } catch ( Exception e ) {
566 String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_ERROR", format.getValue(), e );
567 LOG.logError( msg, e );
568 throw new OGCWebServiceException( getClass().getName(), msg );
569 }
570
571 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
572 LOG.logDebug( "Successfully transformed GetFeature request in " + ( System.currentTimeMillis() - start )
573 + " milliseconds." );
574 try {
575 LOG.logDebugXMLFile( "WFSHandler_GetFeature_transformed", xml );
576 } catch ( Exception e ) {
577 LOG.logError( e.getMessage(), e );
578 }
579 }
580 return GetFeature.create( request.getId(), xml.getRootElement() );
581 }
582
583 /**
584 * Sends the given {@link FeatureCollection} as GML to the given {@link HttpServletResponse} object.
585 *
586 * @param fc
587 * feature collection to send
588 * @param httpResponse
589 * servlet response object to write to
590 * @param schemaURL
591 * URL to schema document (DescribeFeatureType request)
592 * @param suppressXLinks
593 * true, if no XLinks must be used in the output, false otherwise
594 * @param depth
595 * the depth of xlinks to resolve
596 */
597 private void sendGMLResponse( FeatureCollection fc, HttpServletResponse httpResponse, String schemaURL,
598 boolean suppressXLinks, boolean sendGeometryIds, int depth, GetFeature request ) {
599
600 try {
601 httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
602 OutputStream os = httpResponse.getOutputStream();
603 GMLFeatureAdapter featureAdapter = new GMLFeatureAdapter( suppressXLinks, schemaURL, sendGeometryIds, depth );
604 List<PropertyPath[]> names = new LinkedList<PropertyPath[]>();
605 for ( Query q : request.getQuery() ) {
606 names.add( q.getPropertyNames() );
607 }
608 featureAdapter.setPropertyPaths( names );
609 if ( getGmlObjectUrl != null ) {
610 featureAdapter.setBaseURL( getGmlObjectUrl.toExternalForm() );
611 }
612 featureAdapter.export( fc, os, getSystemCharset() );
613 } catch ( Exception e ) {
614 LOG.logError( "Error sending GetFeature response (GML) to client.", e );
615 }
616 }
617
618 /**
619 * Sends the given {@link FeatureCollection} as a serialized Java object to the given {@link HttpServletResponse}
620 * object.
621 *
622 * @param fc
623 * feature collection to send
624 * @param httpResponse
625 * servlet response object to write to
626 */
627 private void sendBinaryResponse( FeatureCollection fc, HttpServletResponse httpResponse ) {
628 try {
629 OutputStream os = httpResponse.getOutputStream();
630 ObjectOutputStream oos = new ObjectOutputStream( os );
631 oos.writeObject( fc );
632 oos.flush();
633 } catch ( IOException e ) {
634 LOG.logError( "Error sending GetFeature response (binary) to client.", e );
635 }
636 }
637
638 /**
639 * Transforms a {@link FeatureCollection} to the given format using XSLT and sends it to the specified
640 * {@link HttpServletResponse} object.
641 *
642 * @param fc
643 * feature collection to send
644 * @param schemaURL
645 * URL to schema document (DescribeFeatureType request)
646 * @param httpResponse
647 * servlet response object to write to
648 * @param suppressXLinks
649 * true, if no XLinks must be used in the output, false otherwise
650 * @param format
651 * requested format
652 * @param sendGeometryIds
653 * whether to send geometry gml ids
654 * @param depth
655 * the xlink depth to resolve
656 */
657 private void sendTransformedResponse( FeatureCollection fc, HttpServletResponse httpResponse, String schemaURL,
658 boolean suppressXLinks, FormatType format, boolean sendGeometryIds,
659 int depth, GetFeature request )
660 throws OGCWebServiceException {
661
662 GMLFeatureCollectionDocument fcgml = null;
663 try {
664 // export result feature collection as GML to enable transformation
665 // into another (XML) format
666 GMLFeatureAdapter featureAdapter = null;
667 if ( "GML2".equals( format.getValue() ) ) {
668 String wfsSchemaBinding = "http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd";
669 featureAdapter = new GMLFeatureAdapter( suppressXLinks, schemaURL, wfsSchemaBinding, sendGeometryIds,
670 depth );
671 } else {
672 featureAdapter = new GMLFeatureAdapter( suppressXLinks, schemaURL, sendGeometryIds, depth );
673 }
674 List<PropertyPath[]> names = new LinkedList<PropertyPath[]>();
675 for ( Query q : request.getQuery() ) {
676 names.add( q.getPropertyNames() );
677 }
678 featureAdapter.setPropertyPaths( names );
679 fcgml = featureAdapter.export( fc );
680 } catch ( Exception e ) {
681 String msg = "Could not export feature collection to GML: " + e.getMessage();
682 LOG.logError( msg, e );
683 throw new OGCWebServiceException( msg );
684 }
685
686 LOG.logDebug( "Transforming GetFeature response." );
687 long start = System.currentTimeMillis();
688
689 // TODO: cache Transformer
690 XSLTDocument xsl = null;
691 try {
692 xsl = new XSLTDocument( format.getOutFilter().toURL() );
693 } catch ( Exception e ) {
694 String msg = Messages.getMessage( "WFS_POSTPROCESS_XSL_FILE_ERROR", format.getValue(),
695 format.getOutFilter().toString(), e );
696 LOG.logError( msg );
697 throw new OGCWebServiceException( msg );
698 }
699
700 try {
701 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
702 LOG.logDebugFile( "WFSHandler_GetFeature_result", ".xml", fcgml.getAsString() );
703 }
704
705 String type = format.getValue().split( ";" )[0];
706 httpResponse.setContentType( type + "; charset=" + CharsetUtils.getSystemCharset() );
707
708 OutputStream os = httpResponse.getOutputStream();
709 xsl.transform( fcgml, os );
710 os.close();
711 } catch ( Exception e ) {
712 String msg = Messages.getMessage( "WFS_POSTPROCESS_XSL_ERROR", format.getValue(), e );
713 LOG.logError( msg, e );
714 throw new OGCWebServiceException( getClass().getName(), msg );
715 }
716
717 LOG.logDebug( "Successfully transformed GetFeature response in " + ( System.currentTimeMillis() - start )
718 + " milliseconds." );
719 }
720
721 /**
722 * Performs a {@link Transaction} request and sends the response to the given {@link HttpServletResponse} object.
723 *
724 * @param service
725 * WFService instance to be used
726 * @param request
727 * Transaction request to be performed
728 * @param httpResponse
729 * servlet response object to write to
730 * @throws OGCWebServiceException
731 */
732 private void performTransaction( WFService service, Transaction request, HttpServletResponse httpResponse )
733 throws OGCWebServiceException {
734
735 WFSConfiguration config = (WFSConfiguration) service.getCapabilities();
736 FormatType format = determineFormat( request, config );
737
738 // perform pre-processing if necessary (XSLT)
739 if ( format.isVirtual() ) {
740 request = transformTransaction( request, format );
741 }
742
743 TransactionResponse response = (TransactionResponse) service.doService( request );
744
745 try {
746 httpResponse.setContentType( "text/xml; charset=" + CharsetUtils.getSystemCharset() );
747 XMLFragment document;
748 if ( request.getVersion().equals( "1.0.0" ) ) {
749 document = XMLFactory_1_0_0.export( response );
750 } else {
751 document = XMLFactory.export( response );
752 }
753 document.write( httpResponse.getOutputStream() );
754 } catch ( IOException e ) {
755 LOG.logError( "Error sending Transaction response to client.", e );
756 }
757 }
758
759 /**
760 * Transforms a {@link Transaction} request depending on the requested virtual format.
761 *
762 * @param request
763 * Transaction request to be transformed
764 * @param format
765 * requested (virtual) output format
766 * @return transformed Transaction
767 * @throws OGCWebServiceException
768 * if transformation script could not be loaded or transformation failed
769 */
770 private Transaction transformTransaction( Transaction request, FormatType format )
771 throws OGCWebServiceException {
772
773 LOG.logDebug( "Transforming Transaction request." );
774 long start = System.currentTimeMillis();
775
776 URL inFilterURL = null;
777 try {
778 inFilterURL = format.getInFilter().toURL();
779 } catch ( MalformedURLException e1 ) {
780 // never happens
781 }
782 XSLTDocument xsl = xsltCache.get( inFilterURL );
783 if ( xsl == null ) {
784 xsl = new XSLTDocument();
785 try {
786 LOG.logDebug( "Read Filter ... " );
787 xsl.load( inFilterURL );
788 xsltCache.put( inFilterURL, xsl );
789 } catch ( Exception e ) {
790 String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_FILE_ERROR", format.getValue(),
791 format.getInFilter().toString(), e );
792 LOG.logError( msg, e );
793 throw new OGCWebServiceException( getClass().getName(), msg );
794 }
795 }
796
797 XMLFragment xml = null;
798 try {
799 xml = request.getSourceDocument();
800 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
801 try {
802 LOG.logDebugXMLFile( "WFSHandler_Transaction_incoming", xml );
803 } catch ( Exception e ) {
804 LOG.logError( e.getMessage(), e );
805 }
806 }
807 } catch ( Exception e ) {
808 LOG.logError( e.getMessage(), e );
809 throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
810 }
811 // transform Transaction request
812 try {
813 LOG.logDebug( "start transform ..." );
814 xml = xsl.transform( xml, format.getInFilter().toASCIIString(), null, null );
815 LOG.logDebug( "end transform ..." );
816 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
817 try {
818 LOG.logDebugXMLFile( "WFSHandler_Transaction_transformed", xml );
819 } catch ( Exception e ) {
820 LOG.logError( e.getMessage(), e );
821 }
822 }
823 } catch ( Exception e ) {
824 String msg = Messages.getMessage( "WFS_PREPROCESS_XSL_ERROR", format.getInFilter().toString(), e );
825 LOG.logError( msg, e );
826 throw new OGCWebServiceException( getClass().getName(), msg );
827 }
828
829 try {
830 request = Transaction.create( request.getId(), xml.getRootElement() );
831 } catch ( Exception e ) {
832 LOG.logError( e.getMessage(), e );
833 throw new OGCWebServiceException( getClass().getName(), e.getMessage() );
834 }
835
836 LOG.logDebug( "Successfully transformed Transaction request in " + ( System.currentTimeMillis() - start )
837 + " milliseconds." );
838
839 return request;
840 }
841
842 /**
843 * Determines whether the response to the given {@link GetFeature} request may use XLinks or not.
844 * <p>
845 * The first feature of the collection is checked; if it's {@link MappedGMLSchema} requests the suppression of
846 * XLinks, xlinks are disabled, otherwise they are enabled.
847 *
848 * @param fc
849 * @return true, if the response document must not contain XLinks, false otherwise
850 */
851 private boolean suppressXLinkOutput( FeatureCollection fc ) {
852
853 boolean suppressXLinkOutput = false;
854
855 if ( fc instanceof FeatureTupleCollection ) {
856 suppressXLinkOutput = true;
857 } else if ( fc.size() > 0 ) {
858 if ( fc.getFeature( 0 ).getFeatureType() instanceof MappedFeatureType ) {
859 suppressXLinkOutput = ( (MappedFeatureType) fc.getFeature( 0 ).getFeatureType() ).getGMLSchema().suppressXLinkOutput();
860 }
861 }
862 return suppressXLinkOutput;
863 }
864
865 private FormatType determineFormat( GetFeature request, WFSConfiguration config, WFService service )
866 throws OGCWebServiceException {
867
868 Query firstQuery = request.getQuery()[0];
869 QualifiedName ftName = firstQuery.getTypeNames()[0];
870 WFSFeatureType wfsFT = config.getFeatureTypeList().getFeatureType( ftName );
871 if ( wfsFT == null ) {
872 MappedFeatureType ft = service.getMappedFeatureType( ftName );
873 String msg = null;
874 if ( ft == null ) {
875 msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", ftName );
876 } else {
877 assert !ft.isVisible();
878 msg = Messages.getMessage( "WFS_FEATURE_TYPE_INVISIBLE", ftName );
879 }
880 throw new OGCWebServiceException( getClass().getName(), msg );
881 }
882 String requestedFormat = request.getOutputFormat();
883 FormatType format = wfsFT.getOutputFormat( requestedFormat );
884 if ( format == null ) {
885 String msg = Messages.getMessage( "WFS_QUERY_UNSUPPORTED_FORMAT", requestedFormat, ftName );
886 throw new OGCWebServiceException( getClass().getName(), msg );
887 }
888 return format;
889 }
890
891 private FormatType determineFormat( DescribeFeatureType request, WFSConfiguration config, WFService service )
892 throws OGCWebServiceException {
893
894 // NOTE: this cannot cope with a mix of virtual and real features
895 QualifiedName ftName = null;
896 if ( request.getTypeNames().length > 0 ) {
897 ftName = request.getTypeNames()[0];
898 } else {
899 // use the first ft that is available in the requested format
900 for ( WFSFeatureType tp : config.getFeatureTypeList().getFeatureTypes() ) {
901 if ( tp.getOutputFormat( request.getOutputFormat() ) != null ) {
902 ftName = tp.getName();
903 break;
904 }
905 }
906 }
907 LOG.logDebug( "typeName: " + ftName );
908
909 WFSFeatureType wfsFT = config.getFeatureTypeList().getFeatureType( ftName );
910 if ( wfsFT == null ) {
911 MappedFeatureType ft = service.getMappedFeatureType( ftName );
912 String msg = null;
913 if ( ft == null ) {
914 msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", ftName );
915 } else {
916 assert !ft.isVisible();
917 msg = Messages.getMessage( "WFS_FEATURE_TYPE_INVISIBLE", ftName );
918 }
919 throw new OGCWebServiceException( getClass().getName(), msg );
920 }
921 String requestedFormat = request.getOutputFormat();
922 LOG.logDebug( "requested outputformat: " + requestedFormat );
923 FormatType format = wfsFT.getOutputFormat( requestedFormat );
924 if ( format == null ) {
925 String msg = Messages.getMessage( "WFS_QUERY_UNSUPPORTED_FORMAT", requestedFormat, ftName );
926 throw new OGCWebServiceException( getClass().getName(), msg );
927 }
928 return format;
929 }
930
931 private FormatType determineFormat( Transaction request, WFSConfiguration config )
932 throws OGCWebServiceException {
933
934 FormatType format = null;
935
936 WFSFeatureType wfsFT = config.getFeatureTypeList().getFeatureTypes()[0];
937
938 List<TransactionOperation> list = request.getOperations();
939 TransactionOperation op = list.get( 0 );
940 if ( op instanceof Insert ) {
941 QualifiedName qn = ( (Insert) op ).getAffectedFeatureTypes().get( 0 );
942 wfsFT = config.getFeatureTypeList().getFeatureType( qn );
943 if ( wfsFT == null ) {
944 throw new OGCWebServiceException( Messages.getMessage( "WFS_INSERT_UNSUPPORTED_FT", qn ) );
945 }
946 } else if ( op instanceof Update ) {
947 QualifiedName qn = ( (Update) op ).getAffectedFeatureTypes().get( 0 );
948 wfsFT = config.getFeatureTypeList().getFeatureType( qn );
949 if ( wfsFT == null ) {
950 throw new OGCWebServiceException( Messages.getMessage( "WFS_UPDATE_UNSUPPORTED_FT", qn ) );
951 }
952 } else if ( op instanceof Delete ) {
953 QualifiedName qn = ( (Delete) op ).getAffectedFeatureTypes().get( 0 );
954 wfsFT = config.getFeatureTypeList().getFeatureType( qn );
955 if ( wfsFT == null ) {
956 throw new OGCWebServiceException( Messages.getMessage( "WFS_DELETE_UNSUPPORTED_FT", qn ) );
957 }
958 }
959
960 FormatType[] formats = wfsFT.getOutputFormats();
961 for ( int i = 0; i < formats.length; i++ ) {
962 format = formats[i];
963 if ( format.getInFilter() != null ) {
964 break;
965 }
966 }
967 return format;
968 }
969
970 /**
971 * @param httpResponse
972 * @param serviceException
973 * @param is100
974 */
975 public void sendVersionedException( HttpServletResponse httpResponse, OGCWebServiceException serviceException,
976 boolean is100 ) {
977 try {
978 httpResponse.setContentType( "text/xml" );
979 XMLFragment reportDocument = is100 ? exportExceptionReportWFS100( serviceException )
980 : exportExceptionReportWFS( serviceException );
981 OutputStream os = httpResponse.getOutputStream();
982 reportDocument.write( os );
983 os.close();
984 } catch ( Exception e ) {
985 LOG.logError( "Error sending exception report: ", e );
986 }
987
988 }
989
990 @Override
991 public void sendException( HttpServletResponse httpResponse, OGCWebServiceException serviceException ) {
992 try {
993 httpResponse.setContentType( "text/xml" );
994 XMLFragment reportDocument = exportExceptionReportWFS( serviceException );
995 OutputStream os = httpResponse.getOutputStream();
996 reportDocument.write( os );
997 os.close();
998 } catch ( Exception e ) {
999 LOG.logError( "Error sending exception report: ", e );
1000 }
1001
1002 }
1003
1004 private String getAllNamespaceDeclarations( Document doc ) {
1005 Map<String, String> nsp = new HashMap<String, String>();
1006 nsp = collect( nsp, doc );
1007
1008 Iterator<String> iter = nsp.keySet().iterator();
1009 StringBuffer sb = new StringBuffer( 1000 );
1010 while ( iter.hasNext() ) {
1011 String s = iter.next();
1012 String val = nsp.get( s );
1013 sb.append( s ).append( ":" ).append( val );
1014 if ( iter.hasNext() ) {
1015 sb.append( ';' );
1016 }
1017 }
1018 return sb.toString();
1019 }
1020
1021 private Map<String, String> collect( Map<String, String> nsp, Node node ) {
1022 NamedNodeMap nnm = node.getAttributes();
1023 if ( nnm != null ) {
1024 for ( int i = 0; i < nnm.getLength(); i++ ) {
1025 String s = nnm.item( i ).getNodeName();
1026 if ( s.startsWith( "xmlns:" ) ) {
1027 nsp.put( s.substring( 6, s.length() ), nnm.item( i ).getNodeValue() );
1028 }
1029 }
1030 }
1031 NodeList nl = node.getChildNodes();
1032 if ( nl != null ) {
1033 for ( int i = 0; i < nl.getLength(); i++ ) {
1034 collect( nsp, nl.item( i ) );
1035 }
1036 }
1037 return nsp;
1038 }
1039
1040 }