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