001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/csw/discovery/Discovery.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042    
043     ---------------------------------------------------------------------------*/
044    package org.deegree.ogcwebservices.csw.discovery;
045    
046    import java.io.ByteArrayInputStream;
047    import java.io.ByteArrayOutputStream;
048    import java.io.IOException;
049    import java.io.StringWriter;
050    import java.net.URI;
051    import java.net.URISyntaxException;
052    import java.net.URL;
053    import java.util.HashMap;
054    import java.util.Iterator;
055    import java.util.List;
056    import java.util.Map;
057    
058    import javax.xml.transform.TransformerException;
059    
060    import org.deegree.datatypes.QualifiedName;
061    import org.deegree.enterprise.servlet.OGCServletController;
062    import org.deegree.framework.log.ILogger;
063    import org.deegree.framework.log.LoggerFactory;
064    import org.deegree.framework.util.FileUtils;
065    import org.deegree.framework.util.StringTools;
066    import org.deegree.framework.util.TimeTools;
067    import org.deegree.framework.xml.XMLFragment;
068    import org.deegree.framework.xml.XSLTDocument;
069    import org.deegree.framework.xml.schema.XSDocument;
070    import org.deegree.i18n.Messages;
071    import org.deegree.model.feature.FeatureCollection;
072    import org.deegree.model.feature.FeatureException;
073    import org.deegree.model.feature.GMLFeatureAdapter;
074    import org.deegree.ogcbase.ExceptionCode;
075    import org.deegree.ogcwebservices.InvalidParameterValueException;
076    import org.deegree.ogcwebservices.OGCWebServiceException;
077    import org.deegree.ogcwebservices.csw.capabilities.CatalogueOperationsMetadata;
078    import org.deegree.ogcwebservices.csw.configuration.CatalogueConfiguration;
079    import org.deegree.ogcwebservices.csw.configuration.CatalogueConfigurationDocument;
080    import org.deegree.ogcwebservices.csw.configuration.CatalogueOutputSchemaParameter;
081    import org.deegree.ogcwebservices.csw.configuration.CatalogueOutputSchemaValue;
082    import org.deegree.ogcwebservices.csw.configuration.CatalogueTypeNameSchemaParameter;
083    import org.deegree.ogcwebservices.csw.configuration.CatalogueTypeNameSchemaValue;
084    import org.deegree.ogcwebservices.csw.discovery.GetRecords.RESULT_TYPE;
085    import org.deegree.ogcwebservices.wfs.WFService;
086    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
087    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
088    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
089    
090    /**
091     * The Discovery class allows clients to discover resources registered in a catalogue, by providing four operations
092     * named <code>query</code>,<code>present</code>, <code>describeRecordType</code>, and <code>getDomain</code>.
093     * This class has a required association from the Catalogue Service class, and is thus always implemented by all
094     * Catalogue Service implementations. The Session class can be included with the Discovery class, in associations with
095     * the Catalogue Service class. The &quote;query&quote; and &quote;present&quote; operations may be executed in a
096     * session or stateful context. If a session context exists, the dynamic model uses internal states of the session and
097     * the allowed transitions between states. When the &quote;query&quote; and &quote;present&quote; state does not include
098     * a session between a server and a client, any memory or shared information between the client and the server may be
099     * based on private understandings or features available in the protocol binding. The describeRecordType and getDomain
100     * operations do not require a session context.
101     * 
102     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
103     * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
104     * 
105     * @author last edited by: $Author: rbezema $
106     * 
107     * @version $Revision: 7571 $, $Date: 2007-06-14 13:56:27 +0200 (Do, 14 Jun 2007) $
108     * 
109     */
110    public class Discovery {
111    
112        private static final ILogger LOG = LoggerFactory.getLogger( Discovery.class );
113    
114        // Keys are Strings, values are XSLDocuments
115        private static final Map<String, XSLTDocument> IN_XSL = new HashMap<String, XSLTDocument>();
116    
117        // Keys are Strings, values are XSLDocuments
118        private static final Map<String, XSLTDocument> OUT_XSL = new HashMap<String, XSLTDocument>();
119    
120        // Keys are Strings, values are URLs
121        private static final Map<String, URL> SCHEMA_URLS = new HashMap<String, URL>();
122    
123        // Keys are Strings, values are XMLFragments
124        private static final Map<String, XSDocument> SCHEMA_DOCS = new HashMap<String, XSDocument>();
125    
126        private static final String DEFAULT_SCHEMA = "DublinCore";
127    
128        private static final String OGC_CORE_SCHEMA = "OGCCORE";
129    
130        private CatalogueConfiguration cswConfiguration = null;
131    
132        /**
133         * The complete data access of a catalog service is managed by one instances of WFService.
134         */
135        private WFService wfsResource; // single instance only for this CSW
136    
137        /**
138         * @param wfsService
139         *            to contact
140         * @param cswConfiguration
141         *            of this service
142         */
143        public Discovery( WFService wfsService, CatalogueConfiguration cswConfiguration ) {
144            this.wfsResource = wfsService;
145            this.cswConfiguration = cswConfiguration;
146            try {
147                CatalogueOperationsMetadata catalogMetadata = (CatalogueOperationsMetadata) cswConfiguration.getOperationsMetadata();
148                CatalogueOutputSchemaParameter outputSchemaParameter = (CatalogueOutputSchemaParameter) catalogMetadata.getGetRecords()
149                                                                                                                       .getParameter( "outputSchema" );
150    
151                CatalogueConfigurationDocument document = new CatalogueConfigurationDocument();
152                document.setSystemId( cswConfiguration.getSystemId() );
153                CatalogueOutputSchemaValue[] values = outputSchemaParameter.getSpecializedValues();
154                for ( int i = 0; i < values.length; i++ ) {
155                    CatalogueOutputSchemaValue value = values[i];
156                    String schemaName = value.getValue().toUpperCase();
157    
158                    URL fileURL = document.resolve( value.getInXsl() );
159                    LOG.logInfo( StringTools.concat( 300,
160                                                     "Input schema '",
161                                                     schemaName,
162                                                     "' is processed using XSLT-sheet from URL '",
163                                                     fileURL,
164                                                     "'" ) );
165                    XSLTDocument inXSLSheet = new XSLTDocument();
166                    inXSLSheet.load( fileURL );
167                    IN_XSL.put( schemaName, inXSLSheet );
168    
169                    fileURL = document.resolve( value.getOutXsl() );
170                    LOG.logInfo( StringTools.concat( 300,
171                                                     "Output schema '",
172                                                     schemaName,
173                                                     "' is processed using XSLT-sheet from URL '",
174                                                     fileURL,
175                                                     "'" ) );
176                    XSLTDocument outXSLSheet = new XSLTDocument();
177                    outXSLSheet.load( fileURL );
178                    OUT_XSL.put( schemaName, outXSLSheet );
179    
180                }
181    
182                // read and store schema definitions
183                // each type(Name) provided by a CS-W is assigned to one schema
184                CatalogueTypeNameSchemaParameter outputTypeNameParameter = (CatalogueTypeNameSchemaParameter) catalogMetadata.getGetRecords()
185                                                                                                                             .getParameter( "typeName" );
186                CatalogueTypeNameSchemaValue[] tn_values = outputTypeNameParameter.getSpecializedValues();
187                for ( int i = 0; i < tn_values.length; i++ ) {
188                    CatalogueTypeNameSchemaValue value = tn_values[i];
189                    URL fileURL = document.resolve( value.getSchema() );
190                    XSDocument schemaDoc = new XSDocument();
191                    schemaDoc.load( fileURL );
192                    String typeName = value.getValue().toUpperCase();
193                    LOG.logInfo( StringTools.concat( 300,
194                                                     "Schema for type '",
195                                                     typeName,
196                                                     "' is defined in XSD-file at URL '",
197                                                     fileURL,
198                                                     "'" ) );
199                    SCHEMA_URLS.put( typeName, fileURL );
200                    SCHEMA_DOCS.put( typeName, schemaDoc );
201                }
202            } catch ( Exception e ) {
203                e.printStackTrace();
204                LOG.logError( "Error while creating CSW Discovery: " + e.getMessage(), e );
205            }
206            WFSCapabilities capa = wfsResource.getCapabilities();
207            LOG.logInfo( "CSW Discovery initialized with WFS resource, wfs version: " + capa.getVersion() );
208        }
209    
210        /**
211         * Performs the submitted <code>DescribeRecord</code> -request.
212         * 
213         * TODO: Check output schema & Co.
214         * 
215         * @param request
216         * @return The DescribeRecordResult created from the given request
217         * @throws OGCWebServiceException
218         */
219        public DescribeRecordResult describeRecordType( DescribeRecord request ) throws OGCWebServiceException {
220    
221            // requested output format must be 'text/xml'
222            if ( !( "text/xml".equals( request.getOutputFormat() ) || "application/xml".equals( request.getOutputFormat() ) ) ) {
223                String s = Messages.getMessage( "CSW_DESCRIBERECORD_INVALID_FORMAT", request.getOutputFormat() );
224                throw new OGCWebServiceException( getClass().getName(), s, ExceptionCode.INVALID_FORMAT );
225            }
226    
227            // requested schema language must be 'XMLSCHEMA'
228            if ( !( "XMLSCHEMA".equals( request.getSchemaLanguage().toString() ) || "http://www.w3.org/XML/Schema".equals( request.getSchemaLanguage().toString() ) ) ) {
229                String s = Messages.getMessage( "CSW_DESCRIBERECORD_INVALID_SCHEMA", request.getSchemaLanguage() );
230                throw new InvalidParameterValueException( s );
231            }
232    
233            // if no type names are specified, describe all known types
234            String[] typeNames = request.getTypeNames();
235            if ( typeNames == null || typeNames.length == 0 ) {
236                typeNames = SCHEMA_DOCS.keySet().toArray( new String[SCHEMA_DOCS.keySet().size()] );
237            }
238    
239            SchemaComponent[] schemaComponents = new SchemaComponent[typeNames.length];
240    
241            for ( int i = 0; i < typeNames.length; i++ ) {
242                XSDocument doc = SCHEMA_DOCS.get( typeNames[i].toUpperCase() );
243                if ( doc == null ) {
244                    LOG.logDebug( "Discovery.describeRecord, no key found for: " + typeNames[i].toUpperCase() + " trying again with added 'RIM:' prefix");
245                    doc = SCHEMA_DOCS.get( "RIM:"+typeNames[i].toUpperCase() );
246                }
247                if( doc == null ){
248                    String msg = Messages.getMessage( "CSW_DESCRIBERECORD_UNSUPPORTED_TN", typeNames[i] );
249                    throw new OGCWebServiceException( getClass().getName(), msg );
250                }
251                try {
252                    schemaComponents[i] = new SchemaComponent( doc,
253                                                               new URI( "http://www.deegree.org/csw" ),
254                                                               null,
255                                                               new URI( "XMLSCHEMA" ) );
256                } catch ( URISyntaxException e ) {
257                    throw new OGCWebServiceException( this.getClass().getName(), e.getMessage() );
258                }
259            }
260    
261            return new DescribeRecordResult( request, "2.0.0", schemaComponents );
262        }
263    
264        /**
265         * @param request
266         *            which is not handled
267         * @return just a new empty DomainValues instance.
268         * @todo not implemented, yet
269         */
270        public DomainValues getDomain( @SuppressWarnings("unused")
271        GetDomain request ) {
272            return new DomainValues();
273        }
274    
275        private String normalizeOutputSchema( String outputSchema ) throws InvalidParameterValueException {
276            LOG.logDebug( "Normalizing following outputschema: " + outputSchema );
277            if ( outputSchema == null ) {
278                LOG.logDebug( "Setting the outputSchema to: " + DEFAULT_SCHEMA);
279                outputSchema = DEFAULT_SCHEMA;
280            } else if ( outputSchema.equalsIgnoreCase( OGC_CORE_SCHEMA ) ) {
281                LOG.logDebug( "Setting the outputSchema to: " + DEFAULT_SCHEMA);
282                outputSchema = DEFAULT_SCHEMA;
283            }
284            outputSchema = outputSchema.toUpperCase();
285            if ( IN_XSL.get( outputSchema ) == null ) {
286                String msg = "Unsupported output schema '" + outputSchema + "' requested. Supported schemas are: ";
287                Iterator it = IN_XSL.keySet().iterator();
288                while ( it.hasNext() ) {
289                    msg += (String) it.next();
290                    if ( it.hasNext() ) {
291                        msg += ", ";
292                    } else {
293                        msg += ".";
294                    }
295                }
296                throw new InvalidParameterValueException( msg );
297            }
298            return outputSchema;
299        }
300    
301        /**
302         * Performs a <code>GetRecords</code> request.
303         * <p>
304         * This involves the following steps:
305         * <ul>
306         * <li><code>GetRecords</code>-><code>GetRecordsDocument</code></li>
307         * <li><code>GetRecordsDocument</code>-><code>GetFeatureDocument</code> using XSLT</li>
308         * <li><code>GetFeatureDocument</code>-><code>GetFeature</code></li>
309         * <li><code>GetFeature</code> request is performed against the underlying WFS</li>
310         * <li>WFS answers with a <code>FeatureResult</code> object (which contains a <code>FeatureCollection</code>)</li>
311         * <li><code>FeatureCollection</code>-> GMLFeatureCollectionDocument (as a String)</li>
312         * <li>GMLFeatureCollectionDocument</code>-><code>GetRecordsResultDocument</code> using XSLT</li>
313         * <li><code>GetRecordsResultDocument</code>-><code>GetRecordsResult</code></li>
314         * </ul>
315         * </p>
316         * 
317         * @param getRecords
318         * @return GetRecordsResult
319         * @throws OGCWebServiceException
320         */
321        public GetRecordsResult query( GetRecords getRecords ) throws OGCWebServiceException {
322            GetFeature getFeature = null;
323            XMLFragment getFeatureDocument = null;
324            Object wfsResponse = null;
325            GetRecordsResult cswResponse = null;
326            String outputSchema = normalizeOutputSchema( getRecords.getOutputSchema() );
327    
328            // TODO remove this (only necessary because determineRecordsMatched changes the resultType)
329            String resultType = getRecords.getResultTypeAsString();
330    
331            XMLFragment getRecordsDocument = new XMLFragment( XMLFactory.export( getRecords ).getRootElement() );
332            LOG.logDebug( "Input GetRecords request:\n" + getRecordsDocument.getAsPrettyString() );
333            try {
334                // incoming GetRecord request must be transformed to a GetFeature
335                // request because the underlying 'data engine' of the CSW is a WFS
336                XSLTDocument xslSheet = IN_XSL.get( outputSchema );
337                synchronized ( xslSheet ) {
338                    getFeatureDocument = xslSheet.transform( getRecordsDocument );                
339                    LOG.logDebug( "*****First Generated WFS GetFeature request:\n" + getFeatureDocument.getAsPrettyString() );
340                    xslSheet.notifyAll();
341                }
342    
343    
344            } catch ( TransformerException e ) {
345                e.printStackTrace();
346                String msg = "Can't transform GetRecord request to WFS GetFeature request: " + e.getMessage();
347                LOG.logError( msg, e );
348                throw new OGCWebServiceException( msg );
349            }
350    
351    //        if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
352    //            StringWriter sw = new StringWriter( 5000 );
353    //            try {
354    //                getFeatureDocument.prettyPrint( sw );
355    //            } catch ( TransformerException e ) {
356    //                getFeatureDocument.write( sw );
357    //            }
358    //            LOG.logDebug( sw.getBuffer().toString() );
359    //        }
360    
361            try {
362                LOG.logDebug( "Creating the GetFeature bean from the transformed GetRecordsDocument" );
363                getFeature = GetFeature.create( getRecords.getId(), getFeatureDocument.getRootElement() );
364            } catch ( Exception e ) {
365                String msg = "Cannot generate object representation for GetFeature request: " + e.getMessage();
366                LOG.logError( msg, e );
367                throw new OGCWebServiceException( msg );
368            }
369    
370            try {
371                LOG.logDebug( "Sending the GetFeature Request to the local wfs" );
372                wfsResponse = wfsResource.doService( getFeature );
373            } catch ( OGCWebServiceException e ) {
374                String msg = "Generated WFS GetFeature request failed: " + e.getMessage();
375                LOG.logError( msg, e );
376                throw new OGCWebServiceException( msg );
377            }
378    
379            // theoretical it is possible the result of a GetFeature request is not
380            // an instance of FeatureResult; but this never should happen
381            if ( !( wfsResponse instanceof FeatureResult ) ) {
382                String msg = "Unexpected result type '" + wfsResponse.getClass().getName()
383                             + "' from WFS (must be FeatureResult)."
384                             + " Maybe a FeatureType is not correctly registered!?";
385                LOG.logError( msg );
386                throw new OGCWebServiceException( msg );
387            }
388    
389            FeatureResult featureResult = (FeatureResult) wfsResponse;
390    
391            // this never should happen too - but it is possible
392            if ( !( featureResult.getResponse() instanceof FeatureCollection ) ) {
393                String msg = "Unexpected reponse type: '" + featureResult.getResponse().getClass().getName()
394                             + " "
395                             + featureResult.getResponse().getClass()
396                             + "' in FeatureResult of WFS (must be a FeatureCollection).";
397                LOG.logError( msg );
398                throw new OGCWebServiceException( msg );
399            }
400            FeatureCollection featureCollection = (FeatureCollection) featureResult.getResponse();
401    
402            try {
403                int numberOfRecordsReturned = featureCollection.size();
404                int numberOfMatchedRecords = 0;
405                if ( getRecords.getResultType().equals( RESULT_TYPE.HITS ) ) {
406                    numberOfMatchedRecords = Integer.parseInt( featureCollection.getAttribute( "numberOfFeatures" ) );
407                } else {
408                    // if result type does not equal 'HITS', a separate request must
409                    // be created and performed to determine how many records match
410                    // the query
411                    LOG.logDebug( "Going to determine the number of matched records" );
412                    numberOfMatchedRecords = determineRecordsMatched( getRecords );
413                }
414    
415                int startPosition = getRecords.getStartPosition();
416                if ( startPosition < 1 )
417                    startPosition = 1;
418                int nextRecord = startPosition + featureCollection.size();
419    
420                HashMap<String, String> params = new HashMap<String, String>();
421                params.put( "REQUEST_ID", getRecords.getId() );
422                if ( numberOfRecordsReturned != 0 ) {
423                    params.put( "SEARCH_STATUS", "complete" );
424                } else {
425                    params.put( "SEARCH_STATUS", "none" );
426                }
427                params.put( "TIMESTAMP", TimeTools.getISOFormattedTime() );
428                List<QualifiedName> typenames = getRecords.getQuery().getTypeNamesAsList();
429                // this is a bit critical because
430                // a) not the complete result can be validated but just single records
431                // b) it is possible that several different record types are part
432                // of a response that must be validated against different schemas
433                String s = null;
434                String version = getRecords.getVersion();
435                if( version == null || "".equals( version.trim() ) ){
436                    version = GetRecords.DEFAULT_VERSION;
437                }
438                if ( "2.0.0".equals( version ) ) {
439                    s = StringTools.concat( 300,
440                                            OGCServletController.address,
441                                            "?service=CSW&version=2.0.0&",
442                                            "request=DescribeRecord&typeName=",
443                                            typenames.get( 0 ).getPrefix(),
444                                            ":",
445                                            typenames.get( 0 ).getLocalName() );
446                } else {
447                    s = StringTools.concat( 300,
448                                            OGCServletController.address,
449                                            "?service=CSW&version="+version+"&",
450                                            "request=DescribeRecord&typeName=",
451                                            typenames.get( 0 ).getFormattedString() );
452                }
453                params.put( "VERSION", version );
454                params.put( "RECORD_SCHEMA", s );
455                params.put( "RECORDS_MATCHED", "" + numberOfMatchedRecords );
456                params.put( "RECORDS_RETURNED", "" + numberOfRecordsReturned );
457                params.put( "NEXT_RECORD", "" + nextRecord );
458                String elementSet = getRecords.getQuery().getElementSetName();
459                if ( elementSet == null ) {
460                    elementSet = "brief";
461                }
462                params.put( "ELEMENT_SET", elementSet.toLowerCase() );
463                params.put( "RESULT_TYPE", resultType );
464                params.put( "REQUEST_NAME", "GetRecords" );
465    
466                ByteArrayOutputStream bos = new ByteArrayOutputStream( 50000 );
467                GMLFeatureAdapter ada = new GMLFeatureAdapter( true );
468    
469                ada.export( featureCollection, bos );
470                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
471                    s = new String( bos.toByteArray() );
472                    LOG.logDebug( s );
473                    FileUtils.writeToFile( "CSW_GetRecord_FC.xml", s );
474                }
475    
476                // vice versa to request transforming the feature collection being result
477                // to the GetFeature request must be transformed into a GetRecords result
478                ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
479                XSLTDocument xslSheet = OUT_XSL.get( outputSchema );
480                XMLFragment resultDocument = xslSheet.transform( bis, null, null, params );
481                GetRecordsResultDocument cswResponseDocument = new GetRecordsResultDocument();
482                cswResponseDocument.setRootElement( resultDocument.getRootElement() );
483                cswResponse = cswResponseDocument.parseGetRecordsResponse( getRecords );
484            } catch ( IOException e ) {
485                String msg = "Can't transform WFS response (FeatureCollection) to CSW response: " + e.getMessage();
486                LOG.logError( msg, e );
487                throw new OGCWebServiceException( msg );
488    
489            } catch ( FeatureException e ) {
490                String msg = "Can't transform WFS response (FeatureCollection) to CSW response: " + e.getMessage();
491                LOG.logError( msg, e );
492                throw new OGCWebServiceException( msg );
493    
494            } catch ( TransformerException e ) {
495                String msg = "Can't transform WFS response (FeatureCollection) to CSW response: " + e.getMessage();
496                LOG.logError( msg, e );
497                throw new OGCWebServiceException( msg );
498    
499            }
500    
501            return cswResponse;
502        }
503    
504        /**
505         * Returns the number of records matching a GetRecords request.
506         * 
507         * @param getRecords
508         * @return the number of records matching a GetRecords request
509         * @throws OGCWebServiceException
510         */
511        private int determineRecordsMatched( GetRecords getRecords ) throws OGCWebServiceException {
512            getRecords.setResultType( GetRecords.RESULT_TYPE.HITS );
513            GetFeature getFeature = null;
514            XMLFragment getFeatureDocument = null;
515            Object wfsResponse = null;
516            String outputSchema = normalizeOutputSchema( getRecords.getOutputSchema() );
517    
518            XMLFragment getRecordsDocument = new XMLFragment( XMLFactory.export( getRecords ).getRootElement() );
519            try {
520                LOG.logDebug( "Getting the xslt sheet for the determination of the number of matched records" );
521                XSLTDocument xslSheet = IN_XSL.get( outputSchema );
522                synchronized ( xslSheet ) {
523                    getFeatureDocument = xslSheet.transform( getRecordsDocument );                
524                    LOG.logDebug( "*****Second Generated WFS GetFeature request (to determine records matched):\n" + getFeatureDocument.getAsPrettyString() );
525                    xslSheet.notifyAll();
526                }
527    
528    //            getFeatureDocument = xslSheet.transform( getRecordsDocument );
529    //            LOG.logDebug( "Generated WFS GetFeature request (HITS):\n" + getFeatureDocument );
530            } catch ( TransformerException e ) {
531                e.printStackTrace();
532                String msg = "Can't transform GetRecord request to WFS GetFeature request: " + e.getMessage();
533                LOG.logError( msg, e );
534                throw new OGCWebServiceException( msg );
535            }
536    
537            try {
538                getFeature = GetFeature.create( getRecords.getId(), getFeatureDocument.getRootElement() );
539            } catch ( Exception e ) {
540                String msg = "Cannot generate object representation for GetFeature request: " + e.getMessage();
541                LOG.logError( msg, e );
542                throw new OGCWebServiceException( msg );
543            }
544    
545            try {
546                wfsResponse = wfsResource.doService( getFeature );
547            } catch ( OGCWebServiceException e ) {
548                String msg = "Generated WFS GetFeature request failed: " + e.getMessage();
549                LOG.logError( msg, e );
550                throw new OGCWebServiceException( msg );
551            }
552    
553            if ( !( wfsResponse instanceof FeatureResult ) ) {
554                String msg = "Unexpected result type '" + wfsResponse.getClass().getName()
555                             + "' from WFS (must be FeatureResult)."
556                             + " Maybe a FeatureType is not correctly registered!?";
557                LOG.logError( msg );
558                throw new OGCWebServiceException( msg );
559            }
560    
561            FeatureResult featureResult = (FeatureResult) wfsResponse;
562    
563            if ( !( featureResult.getResponse() instanceof FeatureCollection ) ) {
564                String msg = "Unexpected reponse type: '" + featureResult.getResponse().getClass().getName()
565                             + " "
566                             + featureResult.getResponse().getClass()
567                             + "' in FeatureResult of WFS (must be a FeatureCollection).";
568                LOG.logError( msg );
569                throw new OGCWebServiceException( msg );
570            }
571            FeatureCollection featureCollection = (FeatureCollection) featureResult.getResponse();
572    
573            return Integer.parseInt( featureCollection.getAttribute( "numberOfFeatures" ) );
574        }
575    
576        /**
577         * Performs a <code>GetRecordById</code> request.
578         * <p>
579         * This involves the following steps:
580         * <ul>
581         * <li><code>GetRecordById</code>-><code>GetRecordByIdDocument</code></li>
582         * <li><code>GetRecordByIdDocument</code>-><code>GetFeatureDocument</code> using XSLT</li>
583         * <li><code>GetFeatureDocument</code>-><code>GetFeature</code></li>
584         * <li><code>GetFeature</code> request is performed against the underlying WFS</li>
585         * <li>WFS answers with a <code>FeatureResult</code> object (which contains a <code>FeatureCollection</code>)</li>
586         * <li><code>FeatureCollection</code>-> GMLFeatureCollectionDocument (as a String)</li>
587         * <li>GMLFeatureCollectionDocument</code>-><code>GetRecordsResultDocument</code> using XSLT</li>
588         * <li><code>GetRecordsResultDocument</code>-><code>GetRecordsResult</code></li>
589         * </ul>
590         * </p>
591         * 
592         * @param getRecordById
593         * @return The GetRecordByIdResult created from teh given GetRecordById
594         * @throws OGCWebServiceException
595         */
596        public GetRecordByIdResult query( GetRecordById getRecordById ) throws OGCWebServiceException {
597    
598            GetFeature getFeature = null;
599            XMLFragment getFeatureDocument = null;
600            Object wfsResponse = null;
601            GetRecordByIdResult cswResponse = null;
602            String outputSchema = cswConfiguration.getDeegreeParams().getDefaultOutputSchema();
603    
604            XMLFragment getRecordsDocument = new XMLFragment( XMLFactory.export( getRecordById ).getRootElement() );
605            try {
606                XSLTDocument xslSheet = IN_XSL.get( outputSchema.toUpperCase() );
607                getFeatureDocument = xslSheet.transform( getRecordsDocument );
608                LOG.logDebug( "Generated WFS GetFeature request:\n" + getFeatureDocument );
609            } catch ( TransformerException e ) {
610                String msg = "Can't transform GetRecordById request to WFS GetFeature request: " + e.getMessage();
611                LOG.logError( msg, e );
612                throw new OGCWebServiceException( msg );
613            }
614    
615            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
616                StringWriter sw = new StringWriter( 5000 );
617                getFeatureDocument.write( sw );
618                LOG.logDebug( sw.getBuffer().toString() );
619            }
620    
621            try {
622                getFeature = GetFeature.create( getRecordById.getId(), getFeatureDocument.getRootElement() );
623            } catch ( Exception e ) {
624                String msg = "Cannot generate object representation for GetFeature request: " + e.getMessage();
625                LOG.logError( msg, e );
626                throw new OGCWebServiceException( msg );
627            }
628    
629            try {
630                wfsResponse = wfsResource.doService( getFeature );
631            } catch ( OGCWebServiceException e ) {
632                String msg = "Generated WFS GetFeature request failed: " + e.getMessage();
633                LOG.logError( msg, e );
634                throw new OGCWebServiceException( msg );
635            }
636    
637            if ( !( wfsResponse instanceof FeatureResult ) ) {
638                String msg = "Unexpected result type '" + wfsResponse.getClass().getName()
639                             + "' from WFS (must be FeatureResult)."
640                             + " Maybe a FeatureType is not correctly registered!?";
641                LOG.logError( msg );
642                throw new OGCWebServiceException( msg );
643            }
644    
645            FeatureResult featureResult = (FeatureResult) wfsResponse;
646    
647            if ( !( featureResult.getResponse() instanceof FeatureCollection ) ) {
648                String msg = "Unexpected reponse type: '" + featureResult.getResponse().getClass().getName()
649                             + " "
650                             + featureResult.getResponse().getClass()
651                             + "' in FeatureResult of WFS (must be a FeatureCollection).";
652                LOG.logError( msg );
653                throw new OGCWebServiceException( msg );
654            }
655            FeatureCollection featureCollection = (FeatureCollection) featureResult.getResponse();
656    
657            try {
658                int numberOfMatchedRecords = featureCollection == null ? 0 : featureCollection.size();
659                int startPosition = 1;
660                long maxRecords = Integer.MAX_VALUE;
661                long numberOfRecordsReturned = startPosition + maxRecords < numberOfMatchedRecords ? maxRecords
662                                                                                                  : numberOfMatchedRecords - startPosition
663                                                                                                    + 1;
664                long nextRecord = numberOfRecordsReturned + startPosition > numberOfMatchedRecords ? 0
665                                                                                                  : numberOfRecordsReturned + startPosition;
666    
667                HashMap<String, String> params = new HashMap<String, String>();
668                params.put( "REQUEST_ID", getRecordById.getId() );
669                if ( numberOfRecordsReturned != 0 ) {
670                    params.put( "SEARCH_STATUS", "complete" );
671                } else {
672                    params.put( "SEARCH_STATUS", "none" );
673                }
674                params.put( "TIMESTAMP", TimeTools.getISOFormattedTime() );
675                String s = OGCServletController.address + "?service=CSW&version=2.0.0&request=DescribeRecord";
676                params.put( "RECORD_SCHEMA", s );
677                params.put( "RECORDS_MATCHED", "" + numberOfMatchedRecords );
678                params.put( "RECORDS_RETURNED", "" + numberOfRecordsReturned );
679                params.put( "NEXT_RECORD", "" + nextRecord );
680                params.put( "ELEMENT_SET", "full" );
681                params.put( "REQUEST_NAME", "GetRecordById" );
682    
683                featureCollection.setAttribute( "byID", "true" );
684                ByteArrayOutputStream bos = new ByteArrayOutputStream( 50000 );
685                GMLFeatureAdapter ada = new GMLFeatureAdapter( true );
686                ada.export( featureCollection, bos );
687    
688                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
689                    LOG.logDebug( new String( bos.toByteArray() ) );
690                }
691    
692                ByteArrayInputStream bis = new ByteArrayInputStream( bos.toByteArray() );
693                XSLTDocument xslSheet = OUT_XSL.get( outputSchema.toUpperCase() );
694                XMLFragment resultDocument = xslSheet.transform( bis, null, null, params );
695                GetRecordByIdResultDocument cswResponseDocument = new GetRecordByIdResultDocument();
696                cswResponseDocument.setRootElement( resultDocument.getRootElement() );
697                cswResponse = cswResponseDocument.parseGetRecordByIdResponse( getRecordById );
698            } catch ( Exception e ) {
699                e.printStackTrace();
700                String msg = "Can't transform WFS response (FeatureCollection) " + "to CSW response: " + e.getMessage();
701                LOG.logError( msg, e );
702                throw new OGCWebServiceException( msg );
703            }
704    
705            return cswResponse;
706        }
707    }