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 }