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