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 }