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