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