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 }