001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }