001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/csw/discovery/Discovery.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 package org.deegree.ogcwebservices.csw.discovery; 037 038 import java.io.ByteArrayInputStream; 039 import java.io.ByteArrayOutputStream; 040 import java.io.IOException; 041 import java.io.StringReader; 042 import java.io.StringWriter; 043 import java.net.MalformedURLException; 044 import java.net.URI; 045 import java.net.URISyntaxException; 046 import java.net.URL; 047 import java.util.HashMap; 048 import java.util.Iterator; 049 import java.util.List; 050 import java.util.Map; 051 052 import javax.xml.transform.TransformerException; 053 054 import org.deegree.datatypes.QualifiedName; 055 import org.deegree.enterprise.servlet.OGCServletController; 056 import org.deegree.framework.log.ILogger; 057 import org.deegree.framework.log.LoggerFactory; 058 import org.deegree.framework.util.StringTools; 059 import org.deegree.framework.util.TimeTools; 060 import org.deegree.framework.xml.XMLFragment; 061 import org.deegree.framework.xml.XMLParsingException; 062 import org.deegree.framework.xml.XSLTDocument; 063 import org.deegree.framework.xml.schema.XSDocument; 064 import org.deegree.i18n.Messages; 065 import org.deegree.io.datastore.PropertyPathResolvingException; 066 import org.deegree.model.feature.Feature; 067 import org.deegree.model.feature.FeatureCollection; 068 import org.deegree.model.feature.FeatureException; 069 import org.deegree.model.feature.FeatureProperty; 070 import org.deegree.model.feature.GMLFeatureAdapter; 071 import org.deegree.model.filterencoding.ComplexFilter; 072 import org.deegree.model.filterencoding.Expression; 073 import org.deegree.model.filterencoding.Literal; 074 import org.deegree.model.filterencoding.OperationDefines; 075 import org.deegree.model.filterencoding.PropertyIsCOMPOperation; 076 import org.deegree.model.filterencoding.PropertyName; 077 import org.deegree.ogcbase.ExceptionCode; 078 import org.deegree.ogcbase.PropertyPath; 079 import org.deegree.ogcbase.PropertyPathFactory; 080 import org.deegree.ogcwebservices.InvalidParameterValueException; 081 import org.deegree.ogcwebservices.OGCWebServiceException; 082 import org.deegree.ogcwebservices.csw.CSWExceptionCode; 083 import org.deegree.ogcwebservices.csw.capabilities.CatalogueOperationsMetadata; 084 import org.deegree.ogcwebservices.csw.configuration.CatalogueConfiguration; 085 import org.deegree.ogcwebservices.csw.configuration.CatalogueConfigurationDocument; 086 import org.deegree.ogcwebservices.csw.configuration.CatalogueOutputSchemaParameter; 087 import org.deegree.ogcwebservices.csw.configuration.CatalogueOutputSchemaValue; 088 import org.deegree.ogcwebservices.csw.configuration.CatalogueTypeNameSchemaParameter; 089 import org.deegree.ogcwebservices.csw.configuration.CatalogueTypeNameSchemaValue; 090 import org.deegree.ogcwebservices.csw.discovery.GetRecords.RESULT_TYPE; 091 import org.deegree.ogcwebservices.wfs.WFService; 092 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; 093 import org.deegree.ogcwebservices.wfs.operation.FeatureResult; 094 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 095 import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument; 096 import org.deegree.ogcwebservices.wfs.operation.Query; 097 import org.w3c.dom.Document; 098 import org.w3c.dom.NamedNodeMap; 099 import org.w3c.dom.Node; 100 import org.w3c.dom.NodeList; 101 import org.xml.sax.SAXException; 102 103 /** 104 * The Discovery class allows clients to discover resources registered in a catalogue, by providing four operations 105 * named <code>query</code>,<code>present</code>, <code>describeRecordType</code>, and <code>getDomain</code>. 106 * This class has a required association from the Catalogue Service class, and is thus always implemented by all 107 * Catalogue Service implementations. The Session class can be included with the Discovery class, in associations with 108 * the Catalogue Service class. The "e;query"e; and "e;present"e; operations may be executed in a 109 * session or stateful context. If a session context exists, the dynamic model uses internal states of the session and 110 * the allowed transitions between states. When the "e;query"e; and "e;present"e; state does not include 111 * a session between a server and a client, any memory or shared information between the client and the server may be 112 * based on private understandings or features available in the protocol binding. The describeRecordType and getDomain 113 * operations do not require a session context. 114 * 115 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a> 116 * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a> 117 * 118 * @author last edited by: $Author: mschneider $ 119 * 120 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $ 121 * 122 */ 123 public class Discovery { 124 125 private static final ILogger LOG = LoggerFactory.getLogger( Discovery.class ); 126 127 // Keys are Strings, values are XSLDocuments 128 private static final Map<String, XSLTDocument> IN_XSL = new HashMap<String, XSLTDocument>(); 129 130 // Keys are Strings, values are XSLDocuments 131 private static final Map<String, XSLTDocument> OUT_XSL = new HashMap<String, XSLTDocument>(); 132 133 // Keys are Strings, values are URLs 134 private static final Map<String, URL> SCHEMA_URLS = new HashMap<String, URL>(); 135 136 // Keys are Strings, values are XMLFragments 137 private static final Map<String, XSDocument> SCHEMA_DOCS = new HashMap<String, XSDocument>(); 138 139 private static final String DEFAULT_SCHEMA = "DublinCore"; 140 141 private static final String OGC_CORE_SCHEMA = "OGCCORE"; 142 143 private CatalogueConfiguration cswConfiguration = null; 144 145 /** 146 * The complete data access of a catalog service is managed by one instances of WFService. 147 */ 148 private WFService wfsResource; // single instance only for this CSW 149 150 /** 151 * @param wfsService 152 * to contact 153 * @param cswConfiguration 154 * of this service 155 */ 156 public Discovery( WFService wfsService, CatalogueConfiguration cswConfiguration ) { 157 this.wfsResource = wfsService; 158 this.cswConfiguration = cswConfiguration; 159 try { 160 CatalogueOperationsMetadata catalogMetadata = (CatalogueOperationsMetadata) cswConfiguration.getOperationsMetadata(); 161 CatalogueOutputSchemaParameter outputSchemaParameter = (CatalogueOutputSchemaParameter) catalogMetadata.getGetRecords().getParameter( 162 "outputSchema" ); 163 164 CatalogueConfigurationDocument document = new CatalogueConfigurationDocument(); 165 document.setSystemId( cswConfiguration.getSystemId() ); 166 CatalogueOutputSchemaValue[] values = outputSchemaParameter.getSpecializedValues(); 167 for ( int i = 0; i < values.length; i++ ) { 168 CatalogueOutputSchemaValue value = values[i]; 169 String schemaName = value.getValue().toUpperCase(); 170 171 URL fileURL = document.resolve( value.getInXsl() ); 172 LOG.logInfo( StringTools.concat( 300, "Input schema '", schemaName, 173 "' is processed using XSLT-sheet from URL '", fileURL, "'" ) ); 174 XSLTDocument inXSLSheet = new XSLTDocument(); 175 inXSLSheet.load( fileURL ); 176 IN_XSL.put( schemaName, inXSLSheet ); 177 178 fileURL = document.resolve( value.getOutXsl() ); 179 LOG.logInfo( StringTools.concat( 300, "Output schema '", schemaName, 180 "' is processed using XSLT-sheet from URL '", fileURL, "'" ) ); 181 XSLTDocument outXSLSheet = new XSLTDocument(); 182 outXSLSheet.load( fileURL ); 183 OUT_XSL.put( schemaName, outXSLSheet ); 184 185 } 186 187 // read and store schema definitions 188 // each type(Name) provided by a CS-W is assigned to one schema 189 CatalogueTypeNameSchemaParameter outputTypeNameParameter = (CatalogueTypeNameSchemaParameter) catalogMetadata.getGetRecords().getParameter( 190 "typeName" ); 191 CatalogueTypeNameSchemaValue[] tn_values = outputTypeNameParameter.getSpecializedValues(); 192 for ( int i = 0; i < tn_values.length; i++ ) { 193 CatalogueTypeNameSchemaValue value = tn_values[i]; 194 URL fileURL = document.resolve( value.getSchema() ); 195 XSDocument schemaDoc = new XSDocument(); 196 schemaDoc.load( fileURL ); 197 String typeName = value.getValue().toUpperCase(); 198 LOG.logInfo( StringTools.concat( 300, "Schema for type '", typeName, 199 "' is defined in XSD-file at URL '", fileURL, "'" ) ); 200 SCHEMA_URLS.put( typeName, fileURL ); 201 SCHEMA_DOCS.put( typeName, schemaDoc ); 202 } 203 } catch ( Exception e ) { 204 e.printStackTrace(); 205 LOG.logError( "Error while creating CSW Discovery: " + e.getMessage(), e ); 206 } 207 WFSCapabilities capa = wfsResource.getCapabilities(); 208 LOG.logInfo( "CSW Discovery initialized with WFS resource, wfs version: " + capa.getVersion() ); 209 } 210 211 /** 212 * Performs the submitted <code>DescribeRecord</code> -request. 213 * 214 * TODO: Check output schema & Co. 215 * 216 * @param request 217 * @return The DescribeRecordResult created from the given request 218 * @throws OGCWebServiceException 219 */ 220 public DescribeRecordResult describeRecordType( DescribeRecord request ) 221 throws OGCWebServiceException { 222 223 // requested output format must be 'text/xml' 224 if ( !( "text/xml".equals( request.getOutputFormat() ) || "application/xml".equals( request.getOutputFormat() ) ) ) { 225 String s = Messages.getMessage( "CSW_DESCRIBERECORD_INVALID_FORMAT", request.getOutputFormat() ); 226 throw new OGCWebServiceException( getClass().getName(), s, ExceptionCode.INVALID_FORMAT ); 227 } 228 229 // requested schema language must be 'XMLSCHEMA' 230 if ( !( "XMLSCHEMA".equals( request.getSchemaLanguage().toString() ) || "http://www.w3.org/XML/Schema".equals( request.getSchemaLanguage().toString() ) ) ) { 231 String s = Messages.getMessage( "CSW_DESCRIBERECORD_INVALID_SCHEMA", request.getSchemaLanguage() ); 232 throw new InvalidParameterValueException( s ); 233 } 234 235 // if no type names are specified, describe all known types 236 String[] typeNames = request.getTypeNames(); 237 if ( typeNames == null || typeNames.length == 0 ) { 238 typeNames = SCHEMA_DOCS.keySet().toArray( new String[SCHEMA_DOCS.keySet().size()] ); 239 } 240 241 SchemaComponent[] schemaComponents = new SchemaComponent[typeNames.length]; 242 243 for ( int i = 0; i < typeNames.length; i++ ) { 244 XSDocument doc = SCHEMA_DOCS.get( typeNames[i].toUpperCase() ); 245 if ( doc == null ) { 246 LOG.logDebug( "Discovery.describeRecord, no key found for: " + typeNames[i].toUpperCase() 247 + " trying again with added 'RIM:' prefix" ); 248 doc = SCHEMA_DOCS.get( "RIM:" + typeNames[i].toUpperCase() ); 249 } 250 if ( doc == null ) { 251 String msg = Messages.getMessage( "CSW_DESCRIBERECORD_UNSUPPORTED_TN", typeNames[i] ); 252 throw new OGCWebServiceException( getClass().getName(), msg ); 253 } 254 try { 255 schemaComponents[i] = new SchemaComponent( doc, doc.getTargetNamespace(), null, new URI( "XMLSCHEMA" ) ); 256 } catch ( URISyntaxException e ) { 257 throw new OGCWebServiceException( this.getClass().getName(), e.getMessage() ); 258 } catch ( XMLParsingException e ) { 259 throw new OGCWebServiceException( this.getClass().getName(), e.getMessage() ); 260 } 261 } 262 263 return new DescribeRecordResult( request, "2.0.0", schemaComponents ); 264 } 265 266 /** 267 * @param request 268 * which is not handled 269 * @return just a new empty DomainValues instance. 270 * @todo not implemented, yet 271 */ 272 public DomainValues getDomain( @SuppressWarnings("unused") 273 GetDomain request ) { 274 return new DomainValues(); 275 } 276 277 private String normalizeOutputSchema( String outputSchema ) 278 throws InvalidParameterValueException { 279 LOG.logDebug( "Normalizing following outputschema: " + outputSchema ); 280 if ( outputSchema == null ) { 281 LOG.logDebug( "Setting the outputSchema to: " + DEFAULT_SCHEMA ); 282 outputSchema = DEFAULT_SCHEMA; 283 } else if ( outputSchema.equalsIgnoreCase( OGC_CORE_SCHEMA ) ) { 284 LOG.logDebug( "Setting the outputSchema to: " + DEFAULT_SCHEMA ); 285 outputSchema = DEFAULT_SCHEMA; 286 } 287 outputSchema = outputSchema.toUpperCase(); 288 if ( IN_XSL.get( outputSchema ) == null ) { 289 String msg = "Unsupported output schema '" + outputSchema + "' requested. Supported schemas are: "; 290 Iterator<String> it = IN_XSL.keySet().iterator(); 291 while ( it.hasNext() ) { 292 msg += it.next(); 293 if ( it.hasNext() ) { 294 msg += ", "; 295 } else { 296 msg += "."; 297 } 298 } 299 throw new InvalidParameterValueException( msg ); 300 } 301 return outputSchema; 302 } 303 304 private String getAllNamespaceDeclarations( Document doc ) { 305 Map<String, String> nsp = new HashMap<String, String>(); 306 nsp = collect( nsp, doc ); 307 308 Iterator<String> iter = nsp.keySet().iterator(); 309 StringBuffer sb = new StringBuffer( 1000 ); 310 while ( iter.hasNext() ) { 311 String s = iter.next(); 312 String val = nsp.get( s ); 313 sb.append( s ).append( ":" ).append( val ); 314 if ( iter.hasNext() ) { 315 sb.append( ';' ); 316 } 317 } 318 return sb.toString(); 319 } 320 321 private Map<String, String> collect( Map<String, String> nsp, Node node ) { 322 NamedNodeMap nnm = node.getAttributes(); 323 if ( nnm != null ) { 324 for ( int i = 0; i < nnm.getLength(); i++ ) { 325 String s = nnm.item( i ).getNodeName(); 326 if ( s.startsWith( "xmlns:" ) ) { 327 nsp.put( s.substring( 6, s.length() ), nnm.item( i ).getNodeValue() ); 328 } 329 } 330 } 331 NodeList nl = node.getChildNodes(); 332 if ( nl != null ) { 333 for ( int i = 0; i < nl.getLength(); i++ ) { 334 collect( nsp, nl.item( i ) ); 335 } 336 } 337 return nsp; 338 } 339 340 /** 341 * Performs a <code>GetRecords</code> request. 342 * <p> 343 * This involves the following steps: 344 * <ul> 345 * <li><code>GetRecords</code>-><code>GetRecordsDocument</code></li> 346 * <li><code>GetRecordsDocument</code>-><code>GetFeatureDocument</code> using XSLT</li> 347 * <li><code>GetFeatureDocument</code>-><code>GetFeature</code></li> 348 * <li><code>GetFeature</code> request is performed against the underlying WFS</li> 349 * <li>WFS answers with a <code>FeatureResult</code> object (which contains a <code>FeatureCollection</code>)</li> 350 * <li><code>FeatureCollection</code>-> GMLFeatureCollectionDocument (as a String)</li> 351 * <li>GMLFeatureCollectionDocument</code>-><code>GetRecordsResultDocument</code> using XSLT</li> 352 * <li><code>GetRecordsResultDocument</code>-><code>GetRecordsResult</code></li> 353 * </ul> 354 * </p> 355 * 356 * @param getRecords 357 * @return GetRecordsResult 358 * @throws OGCWebServiceException 359 */ 360 public GetRecordsResult query( GetRecords getRecords ) 361 throws OGCWebServiceException { 362 GetFeature getFeature = null; 363 XMLFragment getFeatureDocument = null; 364 Object wfsResponse = null; 365 GetRecordsResult cswResponse = null; 366 String outputSchema = normalizeOutputSchema( getRecords.getOutputSchema() ); 367 368 // TODO remove this (only necessary because determineRecordsMatched changes the resultType) 369 String resultType = getRecords.getResultTypeAsString(); 370 371 XMLFragment getRecordsDocument = new XMLFragment( XMLFactory.export( getRecords ).getRootElement() ); 372 try { 373 String nsp = getAllNamespaceDeclarations( getRecordsDocument.getRootElement().getOwnerDocument() ); 374 // incoming GetRecord request must be transformed to a GetFeature 375 // request because the underlying 'data engine' of the CSW is a WFS 376 XSLTDocument xslSheet = IN_XSL.get( outputSchema ); 377 synchronized ( xslSheet ) { 378 Map<String, String> param = new HashMap<String, String>(); 379 param.put( "NSP", nsp ); 380 if ( LOG.isDebug() ) { 381 LOG.logDebug( "Input GetRecords request:\n" + getRecordsDocument.getAsPrettyString() ); 382 } 383 try { 384 getFeatureDocument = xslSheet.transform( getRecordsDocument, XMLFragment.DEFAULT_URL, null, param ); 385 } catch ( MalformedURLException e ) { 386 LOG.logError( e.getMessage(), e ); 387 } 388 if ( LOG.isDebug() ) { 389 LOG.logDebugXMLFile( "first", getFeatureDocument ); 390 // LOG.logDebug( "*****First Generated WFS GetFeature request:\n" 391 // + getFeatureDocument.getAsPrettyString() ); 392 } 393 xslSheet.notifyAll(); 394 } 395 396 } catch ( TransformerException e ) { 397 String msg = "Can't transform GetRecord request to WFS GetFeature request: " + e.getMessage(); 398 LOG.logError( msg, e ); 399 throw new OGCWebServiceException( msg ); 400 } 401 402 // if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 403 // StringWriter sw = new StringWriter( 5000 ); 404 // try { 405 // getFeatureDocument.prettyPrint( sw ); 406 // } catch ( TransformerException e ) { 407 // getFeatureDocument.write( sw ); 408 // } 409 // LOG.logDebug( sw.getBuffer().toString() ); 410 // } 411 412 try { 413 LOG.logDebug( "Creating the GetFeature bean from the transformed GetRecordsDocument" ); 414 getFeature = GetFeature.create( getRecords.getId(), getFeatureDocument.getRootElement() ); 415 } catch ( Exception e ) { 416 String msg = "Cannot generate object representation for GetFeature request: " + e.getMessage(); 417 LOG.logError( msg, e ); 418 throw new OGCWebServiceException( msg ); 419 } 420 421 try { 422 LOG.logDebug( "Sending the GetFeature Request to the local wfs" ); 423 wfsResponse = wfsResource.doService( getFeature ); 424 } catch ( OGCWebServiceException e ) { 425 String msg = "Generated WFS GetFeature request failed: " + e.getMessage(); 426 LOG.logError( msg, e ); 427 throw new OGCWebServiceException( msg ); 428 } 429 430 // theoretical it is possible the result of a GetFeature request is not 431 // an instance of FeatureResult; but this never should happen 432 if ( !( wfsResponse instanceof FeatureResult ) ) { 433 String msg = "Unexpected result type '" + wfsResponse.getClass().getName() 434 + "' from WFS (must be FeatureResult)." + " Maybe a FeatureType is not correctly registered!?"; 435 LOG.logError( msg ); 436 throw new OGCWebServiceException( msg ); 437 } 438 439 FeatureResult featureResult = (FeatureResult) wfsResponse; 440 441 // this never should happen too - but it is possible 442 if ( !( featureResult.getResponse() instanceof FeatureCollection ) ) { 443 String msg = "Unexpected reponse type: '" + featureResult.getResponse().getClass().getName() + " " 444 + featureResult.getResponse().getClass() 445 + "' in FeatureResult of WFS (must be a FeatureCollection)."; 446 LOG.logError( msg ); 447 throw new OGCWebServiceException( msg ); 448 } 449 FeatureCollection featureCollection = (FeatureCollection) featureResult.getResponse(); 450 451 try { 452 int numberOfRecordsReturned = featureCollection.size(); 453 int numberOfMatchedRecords = 0; 454 if ( getRecords.getResultType().equals( RESULT_TYPE.HITS ) ) { 455 numberOfMatchedRecords = Integer.parseInt( featureCollection.getAttribute( "numberOfFeatures" ) ); 456 } else { 457 // if result type does not equal 'HITS', a separate request must 458 // be created and performed to determine how many records match 459 // the query 460 LOG.logDebug( "Going to determine the number of matched records" ); 461 numberOfMatchedRecords = determineRecordsMatched( getRecords ); 462 } 463 464 int startPosition = getRecords.getStartPosition(); 465 if ( startPosition < 1 ) 466 startPosition = 1; 467 int nextRecord = startPosition + featureCollection.size(); 468 469 HashMap<String, String> params = new HashMap<String, String>(); 470 params.put( "REQUEST_ID", getRecords.getId() ); 471 if ( numberOfRecordsReturned != 0 ) { 472 params.put( "SEARCH_STATUS", "complete" ); 473 } else { 474 params.put( "SEARCH_STATUS", "none" ); 475 } 476 params.put( "TIMESTAMP", TimeTools.getISOFormattedTime() ); 477 List<QualifiedName> typenames = getRecords.getQuery().getTypeNamesAsList(); 478 // this is a bit critical because 479 // a) not the complete result can be validated but just single records 480 // b) it is possible that several different record types are part 481 // of a response that must be validated against different schemas 482 String s = null; 483 String version = getRecords.getVersion(); 484 if ( version == null || "".equals( version.trim() ) ) { 485 version = GetRecords.DEFAULT_VERSION; 486 } 487 if ( "2.0.0".equals( version ) ) { 488 s = StringTools.concat( 300, OGCServletController.address, "?service=CSW&version=2.0.0&", 489 "request=DescribeRecord&typeName=", typenames.get( 0 ).getPrefix(), ":", 490 typenames.get( 0 ).getLocalName() ); 491 } else { 492 s = StringTools.concat( 300, OGCServletController.address, "?service=CSW&version=" + version + "&", 493 "request=DescribeRecord&typeName=", typenames.get( 0 ).getFormattedString() ); 494 } 495 params.put( "VERSION", version ); 496 params.put( "RECORD_SCHEMA", s ); 497 params.put( "RECORDS_MATCHED", "" + numberOfMatchedRecords ); 498 params.put( "RECORDS_RETURNED", "" + numberOfRecordsReturned ); 499 params.put( "NEXT_RECORD", "" + nextRecord ); 500 String elementSet = getRecords.getQuery().getElementSetName(); 501 if ( elementSet == null ) { 502 elementSet = "brief"; 503 } 504 params.put( "ELEMENT_SET", elementSet.toLowerCase() ); 505 params.put( "RESULT_TYPE", resultType ); 506 params.put( "REQUEST_NAME", "GetRecords" ); 507 508 ByteArrayOutputStream bos = new ByteArrayOutputStream( 50000 ); 509 GMLFeatureAdapter ada = new GMLFeatureAdapter( true ); 510 511 ada.export( featureCollection, bos ); 512 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 513 s = new String( bos.toByteArray() ); 514 LOG.logDebug( s ); 515 LOG.logDebugFile( "CSW_GetRecord_FC", "xml", s ); 516 } 517 518 // vice versa to request transforming the feature collection being result 519 // to the GetFeature request must be transformed into a GetRecords result 520 ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() ); 521 XSLTDocument xslSheet = OUT_XSL.get( outputSchema ); 522 XMLFragment resultDocument = xslSheet.transform( bis, null, null, params ); 523 GetRecordsResultDocument cswResponseDocument = new GetRecordsResultDocument(); 524 cswResponseDocument.setRootElement( resultDocument.getRootElement() ); 525 cswResponse = cswResponseDocument.parseGetRecordsResponse( getRecords ); 526 } catch ( IOException e ) { 527 String msg = "Can't transform WFS response (FeatureCollection) to CSW response: " + e.getMessage(); 528 LOG.logError( msg, e ); 529 throw new OGCWebServiceException( msg ); 530 531 } catch ( FeatureException e ) { 532 String msg = "Can't transform WFS response (FeatureCollection) to CSW response: " + e.getMessage(); 533 LOG.logError( msg, e ); 534 throw new OGCWebServiceException( msg ); 535 536 } catch ( TransformerException e ) { 537 String msg = "Can't transform WFS response (FeatureCollection) to CSW response: " + e.getMessage(); 538 LOG.logError( msg, e ); 539 throw new OGCWebServiceException( msg ); 540 541 } 542 543 return cswResponse; 544 } 545 546 /** 547 * Returns the number of records matching a GetRecords request. 548 * 549 * @param getRecords 550 * @return the number of records matching a GetRecords request 551 * @throws OGCWebServiceException 552 */ 553 private int determineRecordsMatched( GetRecords getRecords ) 554 throws OGCWebServiceException { 555 getRecords.setResultType( GetRecords.RESULT_TYPE.HITS ); 556 GetFeature getFeature = null; 557 XMLFragment getFeatureDocument = null; 558 Object wfsResponse = null; 559 String outputSchema = normalizeOutputSchema( getRecords.getOutputSchema() ); 560 561 XMLFragment getRecordsDocument = new XMLFragment( XMLFactory.export( getRecords ).getRootElement() ); 562 try { 563 LOG.logDebug( "Getting the xslt sheet for the determination of the number of matched records" ); 564 String nsp = getAllNamespaceDeclarations( getRecordsDocument.getRootElement().getOwnerDocument() ); 565 XSLTDocument xslSheet = IN_XSL.get( outputSchema ); 566 567 synchronized ( xslSheet ) { 568 Map<String, String> param = new HashMap<String, String>(); 569 param.put( "NSP", nsp ); 570 try { 571 getFeatureDocument = xslSheet.transform( getRecordsDocument, XMLFragment.DEFAULT_URL, null, param ); 572 } catch ( MalformedURLException e ) { 573 LOG.logError( e.getMessage(), e ); 574 } 575 LOG.logDebug( "*****Second Generated WFS GetFeature request (to determine records matched):\n" 576 + getFeatureDocument.getAsPrettyString() ); 577 xslSheet.notifyAll(); 578 } 579 // getFeatureDocument = xslSheet.transform( getRecordsDocument ); 580 // LOG.logDebug( "Generated WFS GetFeature request (HITS):\n" + getFeatureDocument ); 581 } catch ( TransformerException e ) { 582 e.printStackTrace(); 583 String msg = "Can't transform GetRecord request to WFS GetFeature request: " + e.getMessage(); 584 LOG.logError( msg, e ); 585 throw new OGCWebServiceException( msg ); 586 } 587 588 try { 589 getFeature = GetFeature.create( getRecords.getId(), getFeatureDocument.getRootElement() ); 590 } catch ( Exception e ) { 591 String msg = "Cannot generate object representation for GetFeature request: " + e.getMessage(); 592 LOG.logError( msg, e ); 593 throw new OGCWebServiceException( msg ); 594 } 595 596 try { 597 wfsResponse = wfsResource.doService( getFeature ); 598 } catch ( OGCWebServiceException e ) { 599 String msg = "Generated WFS GetFeature request failed: " + e.getMessage(); 600 LOG.logError( msg, e ); 601 throw new OGCWebServiceException( msg ); 602 } 603 604 if ( !( wfsResponse instanceof FeatureResult ) ) { 605 String msg = "Unexpected result type '" + wfsResponse.getClass().getName() 606 + "' from WFS (must be FeatureResult)." + " Maybe a FeatureType is not correctly registered!?"; 607 LOG.logError( msg ); 608 throw new OGCWebServiceException( msg ); 609 } 610 611 FeatureResult featureResult = (FeatureResult) wfsResponse; 612 613 if ( !( featureResult.getResponse() instanceof FeatureCollection ) ) { 614 String msg = "Unexpected reponse type: '" + featureResult.getResponse().getClass().getName() + " " 615 + featureResult.getResponse().getClass() 616 + "' in FeatureResult of WFS (must be a FeatureCollection)."; 617 LOG.logError( msg ); 618 throw new OGCWebServiceException( msg ); 619 } 620 FeatureCollection featureCollection = (FeatureCollection) featureResult.getResponse(); 621 622 return Integer.parseInt( featureCollection.getAttribute( "numberOfFeatures" ) ); 623 } 624 625 /** 626 * Performs a <code>GetRecordById</code> request. 627 * <p> 628 * This involves the following steps: 629 * <ul> 630 * <li><code>GetRecordById</code>-><code>GetRecordByIdDocument</code></li> 631 * <li><code>GetRecordByIdDocument</code>-><code>GetFeatureDocument</code> using XSLT</li> 632 * <li><code>GetFeatureDocument</code>-><code>GetFeature</code></li> 633 * <li><code>GetFeature</code> request is performed against the underlying WFS</li> 634 * <li>WFS answers with a <code>FeatureResult</code> object (which contains a <code>FeatureCollection</code>)</li> 635 * <li><code>FeatureCollection</code>-> GMLFeatureCollectionDocument (as a String)</li> 636 * <li>GMLFeatureCollectionDocument</code>-><code>GetRecordsResultDocument</code> using XSLT</li> 637 * <li><code>GetRecordsResultDocument</code>-><code>GetRecordsResult</code></li> 638 * </ul> 639 * </p> 640 * 641 * @param getRecordById 642 * @return The GetRecordByIdResult created from teh given GetRecordById 643 * @throws OGCWebServiceException 644 */ 645 public GetRecordByIdResult query( GetRecordById getRecordById ) 646 throws OGCWebServiceException { 647 648 GetFeature getFeature = null; 649 XMLFragment getFeatureDocument = null; 650 Object wfsResponse = null; 651 GetRecordByIdResult cswResponse = null; 652 String outputSchema = cswConfiguration.getDeegreeParams().getDefaultOutputSchema(); 653 654 XMLFragment getRecordsDocument = new XMLFragment( XMLFactory.export( getRecordById ).getRootElement() ); 655 try { 656 XSLTDocument xslSheet = IN_XSL.get( outputSchema.toUpperCase() ); 657 getFeatureDocument = xslSheet.transform( getRecordsDocument ); 658 LOG.logDebug( "Generated WFS GetFeature request:\n" + getFeatureDocument ); 659 } catch ( TransformerException e ) { 660 String msg = "Can't transform GetRecordById request to WFS GetFeature request: " + e.getMessage(); 661 LOG.logError( msg, e ); 662 throw new OGCWebServiceException( msg ); 663 } 664 665 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 666 StringWriter sw = new StringWriter( 5000 ); 667 getFeatureDocument.write( sw ); 668 LOG.logDebug( sw.getBuffer().toString() ); 669 } 670 671 try { 672 getFeature = GetFeature.create( getRecordById.getId(), getFeatureDocument.getRootElement() ); 673 } catch ( Exception e ) { 674 String msg = "Cannot generate object representation for GetFeature request: " + e.getMessage(); 675 LOG.logError( msg, e ); 676 throw new OGCWebServiceException( msg ); 677 } 678 679 try { 680 wfsResponse = wfsResource.doService( getFeature ); 681 } catch ( OGCWebServiceException e ) { 682 String msg = "Generated WFS GetFeature request failed: " + e.getMessage(); 683 LOG.logError( msg, e ); 684 throw new OGCWebServiceException( msg ); 685 } 686 687 if ( !( wfsResponse instanceof FeatureResult ) ) { 688 String msg = "Unexpected result type '" + wfsResponse.getClass().getName() 689 + "' from WFS (must be FeatureResult)." + " Maybe a FeatureType is not correctly registered!?"; 690 LOG.logError( msg ); 691 throw new OGCWebServiceException( msg ); 692 } 693 694 FeatureResult featureResult = (FeatureResult) wfsResponse; 695 696 if ( !( featureResult.getResponse() instanceof FeatureCollection ) ) { 697 String msg = "Unexpected reponse type: '" + featureResult.getResponse().getClass().getName() + " " 698 + featureResult.getResponse().getClass() 699 + "' in FeatureResult of WFS (must be a FeatureCollection)."; 700 LOG.logError( msg ); 701 throw new OGCWebServiceException( msg ); 702 } 703 FeatureCollection featureCollection = (FeatureCollection) featureResult.getResponse(); 704 705 try { 706 int numberOfMatchedRecords = featureCollection == null ? 0 : featureCollection.size(); 707 int startPosition = 1; 708 long maxRecords = Integer.MAX_VALUE; 709 long numberOfRecordsReturned = startPosition + maxRecords < numberOfMatchedRecords ? maxRecords 710 : numberOfMatchedRecords 711 - startPosition + 1; 712 long nextRecord = numberOfRecordsReturned + startPosition > numberOfMatchedRecords ? 0 713 : numberOfRecordsReturned 714 + startPosition; 715 716 HashMap<String, String> params = new HashMap<String, String>(); 717 params.put( "REQUEST_ID", getRecordById.getId() ); 718 if ( numberOfRecordsReturned != 0 ) { 719 params.put( "SEARCH_STATUS", "complete" ); 720 } else { 721 params.put( "SEARCH_STATUS", "none" ); 722 } 723 params.put( "TIMESTAMP", TimeTools.getISOFormattedTime() ); 724 String s = OGCServletController.address + "?service=CSW&version=2.0.0&request=DescribeRecord"; 725 params.put( "RECORD_SCHEMA", s ); 726 params.put( "RECORDS_MATCHED", "" + numberOfMatchedRecords ); 727 params.put( "RECORDS_RETURNED", "" + numberOfRecordsReturned ); 728 params.put( "NEXT_RECORD", "" + nextRecord ); 729 params.put( "ELEMENT_SET", "full" ); 730 params.put( "REQUEST_NAME", "GetRecordById" ); 731 732 featureCollection.setAttribute( "byID", "true" ); 733 ByteArrayOutputStream bos = new ByteArrayOutputStream( 50000 ); 734 GMLFeatureAdapter ada = new GMLFeatureAdapter( true ); 735 ada.export( featureCollection, bos ); 736 737 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 738 LOG.logDebug( new String( bos.toByteArray() ) ); 739 } 740 741 ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() ); 742 XSLTDocument xslSheet = OUT_XSL.get( outputSchema.toUpperCase() ); 743 XMLFragment resultDocument = xslSheet.transform( bis, null, null, params ); 744 GetRecordByIdResultDocument cswResponseDocument = new GetRecordByIdResultDocument(); 745 cswResponseDocument.setRootElement( resultDocument.getRootElement() ); 746 cswResponse = cswResponseDocument.parseGetRecordByIdResponse( getRecordById ); 747 } catch ( Exception e ) { 748 e.printStackTrace(); 749 String msg = "Can't transform WFS response (FeatureCollection) " + "to CSW response: " + e.getMessage(); 750 LOG.logError( msg, e ); 751 throw new OGCWebServiceException( msg ); 752 } 753 754 return cswResponse; 755 } 756 757 /** 758 * Contacts the wfsResource to find a rim:ExtrinsicObject which contains the 759 * {@link GetRepositoryItem#getRepositoryItemID()} and retrieves it's 760 * app:RegistryObject/app:extrinsicObject/app:ExtrinsicObject/app:object. The value in this property will then be 761 * written to the response stream (e.g. sent to the requester). 762 * 763 * @param request 764 * the created OGCRequest 765 * @return the repository item response 766 * @throws OGCWebServiceException 767 */ 768 public GetRepositoryItemResponse guery( GetRepositoryItem request ) 769 throws OGCWebServiceException { 770 // Some properterypaths which are used for the creation of a complex filter. 771 URI appURI = URI.create( "http://www.deegree.org/app" ); 772 773 QualifiedName registryObject = new QualifiedName( "app", "RegistryObject", appURI ); 774 Expression iduriExpr = new PropertyName( new QualifiedName( "app", "iduri", appURI ) ); 775 Expression idLiteral = new Literal( request.getRepositoryItemID().toString() ); 776 PropertyIsCOMPOperation idOperator = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO, 777 iduriExpr, idLiteral ); 778 ComplexFilter idFilter = new ComplexFilter( idOperator ); 779 780 FeatureCollection featureCollectionOnId = null; 781 try { 782 FeatureResult fr = sendWFSGetFeature( registryObject, idFilter ); 783 if ( fr != null ) { 784 featureCollectionOnId = (FeatureCollection) fr.getResponse(); 785 } 786 } catch ( OGCWebServiceException e ) { 787 throw new OGCWebServiceException( "The requested item " + request.getRepositoryItemID() 788 + " could not be retrieved from the csw backend: " + e.getMessage(), 789 CSWExceptionCode.WRS_NOTFOUND ); 790 } 791 if ( featureCollectionOnId == null ) { 792 throw new OGCWebServiceException( "The requested item " + request.getRepositoryItemID() 793 + " could not be retrieved from the csw backend.", 794 CSWExceptionCode.WRS_NOTFOUND ); 795 } 796 String numbOfFeatures = featureCollectionOnId.getAttribute( "numberOfFeatures" ); 797 int featureCount = 0; 798 try { 799 featureCount = Integer.parseInt( numbOfFeatures ); 800 LOG.logDebug( "the number of features in the GetFeature was: " + featureCount ); 801 } catch ( NumberFormatException nfe ) { 802 // nottin 803 } 804 805 GetRepositoryItemResponse response = null; 806 // Check the number of hits we've found, if the id allready exists it means we want to set the status of the 807 // object to invalid. 808 // String newID = id; 809 if ( featureCount > 1 ) { 810 throw new OGCWebServiceException( "The id : " + request.getRepositoryItemID() 811 + " is not unique. This repositoryItem can therefore not be retrieved.", 812 CSWExceptionCode.WRS_NOTFOUND ); 813 } else if ( featureCount == 0 ) { 814 throw new OGCWebServiceException( 815 "The id: " 816 + request.getRepositoryItemID() 817 + " corresponds to no rim:ExtrinsicObject. This repositoryItem can therefore not be retrieved.", 818 CSWExceptionCode.WRS_NOTFOUND ); 819 820 } else { 821 Feature f = featureCollectionOnId.getFeature( 0 ); 822 if ( f != null ) { 823 PropertyPath pp = PropertyPathFactory.createPropertyPath( registryObject ); 824 pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "extrinsicObject", 825 appURI ) ) ); 826 pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "ExtrinsicObject", 827 appURI ) ) ); 828 pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "object", appURI ) ) ); 829 FeatureProperty retrievedObject = null; 830 try { 831 retrievedObject = f.getDefaultProperty( pp ); 832 } catch ( PropertyPathResolvingException ppre ) { 833 throw new OGCWebServiceException( 834 "The id: " 835 + request.getRepositoryItemID() 836 + " has no repository item stored, there is nothing to be retrieved.", 837 CSWExceptionCode.WRS_NOTFOUND ); 838 839 } 840 if ( retrievedObject == null || retrievedObject.getValue() == null ) { 841 throw new OGCWebServiceException( 842 "The id: " 843 + request.getRepositoryItemID() 844 + " has no repository item stored, there is nothing to be retrieved.", 845 CSWExceptionCode.WRS_NOTFOUND ); 846 } 847 848 String repositoryItem = (String) retrievedObject.getValue(); 849 LOG.logDebug( "found the repositoryItem: " + repositoryItem ); 850 851 pp = PropertyPathFactory.createPropertyPath( registryObject ); 852 pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "extrinsicObject", 853 appURI ) ) ); 854 pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "ExtrinsicObject", 855 appURI ) ) ); 856 pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "mimeType", appURI ) ) ); 857 FeatureProperty mimeType = null; 858 try { 859 mimeType = f.getDefaultProperty( pp ); 860 } catch ( PropertyPathResolvingException ppre ) { 861 LOG.logError( "The mimetype value (of the GetRepositoryItem: " + request.getRepositoryItemID() 862 + ") was not set, setting content header to 'application/xml' " ); 863 } 864 if ( mimeType == null || mimeType.getValue() == null ) { 865 LOG.logError( "The mimetype value (of the GetRepositoryItem: " + request.getRepositoryItemID() 866 + ") was not set, setting content header to 'application/xml' " ); 867 } 868 869 try { 870 XMLFragment itemFrag = new XMLFragment( new StringReader( repositoryItem ), XMLFragment.DEFAULT_URL ); 871 response = new GetRepositoryItemResponse( request.getId(), request.getRepositoryItemID(), itemFrag ); 872 } catch ( SAXException e ) { 873 LOG.logError( e.getLocalizedMessage(), e ); 874 throw new OGCWebServiceException( null, "The resulting repository item was not of type xml: " 875 + e.getLocalizedMessage(), 876 CSWExceptionCode.NOAPPLICABLECODE ); 877 } catch ( IOException e ) { 878 LOG.logError( e.getLocalizedMessage(), e ); 879 throw new OGCWebServiceException( null, "The resulting repository item was not of type xml: " 880 + e.getLocalizedMessage(), 881 CSWExceptionCode.NOAPPLICABLECODE ); 882 } 883 } 884 } 885 return response; 886 } 887 888 /** 889 * Generates and sends a GetFeature to the wfsResource. 890 * 891 * @param registryObject 892 * the QName of the registryObject e.g. app:RegistryObject (xmlns:app="http://www.deegree.org/app") 893 * @param filter 894 * a ogc:Filter representation containing the (app:iduri isequal requestID) mapping. 895 * @return the FeatureResult of the given filter or <code>null</code> if something went wrong. 896 * @throws OGCWebServiceException 897 * thrown if the wfsResource encounters any problems 898 */ 899 private FeatureResult sendWFSGetFeature( QualifiedName registryObject, ComplexFilter filter ) 900 throws OGCWebServiceException { 901 Query q = Query.create( registryObject, filter ); 902 GetFeature gfwl = GetFeature.create( "1.1.0", "0", 903 org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE.RESULTS, 904 "text/xml; subtype=gml/3.1.1", "no_handle", -1, 0, -1, -1, 905 new Query[] { q } ); 906 // GetFeature gfwl = GetFeature.create( "1.1.0", "0", RESULT_TYPE.RESULTS, "text/xml; subtype=gml/3.1.1", 907 // "no_handle", -1, 0, -1, -1, new Query[] { q } ); 908 if ( LOG.isDebug() ) { 909 try { 910 GetFeatureDocument gd = org.deegree.ogcwebservices.wfs.XMLFactory.export( gfwl ); 911 LOG.logDebug( " The getFeature:\n" + gd.getAsPrettyString() ); 912 } catch ( IOException e ) { 913 LOG.logError( "CSW (Ebrim) GetRepositoryItem-Filter: An error occurred while trying to get a debugging output for the generated GetFeatureDocument: " 914 + e.getMessage() ); 915 } catch ( XMLParsingException e ) { 916 LOG.logError( "CSW (Ebrim) GetRepositoryItem-Filter: An error occurred while trying to get a debugging output for the generated GetFeatureDocument: " 917 + e.getMessage() ); 918 } 919 } 920 921 Object response = wfsResource.doService( gfwl ); 922 if ( response instanceof FeatureResult ) { 923 return (FeatureResult) response; 924 } 925 throw new OGCWebServiceException( "No valid response from the backend while retrieving GetRepositoryItem." ); 926 // LOG.logDebug( "Got no valid response from the wfsResource, returning null" ); 927 // return null; 928 } 929 }