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 &quote;query&quote; and &quote;present&quote; 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 &quote;query&quote; and &quote;present&quote; 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    }