001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/csw/discovery/Discovery.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005     Department of Geography, University of Bonn
006     and
007     lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035     ----------------------------------------------------------------------------*/
036    package org.deegree.ogcwebservices.csw.discovery;
037    
038    import java.io.ByteArrayInputStream;
039    import java.io.ByteArrayOutputStream;
040    import java.io.IOException;
041    import java.io.StringReader;
042    import java.io.StringWriter;
043    import java.net.MalformedURLException;
044    import java.net.URI;
045    import java.net.URISyntaxException;
046    import java.net.URL;
047    import java.util.HashMap;
048    import java.util.Iterator;
049    import java.util.List;
050    import java.util.Map;
051    
052    import javax.xml.transform.TransformerException;
053    
054    import org.deegree.datatypes.QualifiedName;
055    import org.deegree.enterprise.servlet.OGCServletController;
056    import org.deegree.framework.log.ILogger;
057    import org.deegree.framework.log.LoggerFactory;
058    import org.deegree.framework.util.StringTools;
059    import org.deegree.framework.util.TimeTools;
060    import org.deegree.framework.xml.XMLFragment;
061    import org.deegree.framework.xml.XMLParsingException;
062    import org.deegree.framework.xml.XSLTDocument;
063    import org.deegree.framework.xml.schema.XSDocument;
064    import org.deegree.i18n.Messages;
065    import org.deegree.io.datastore.PropertyPathResolvingException;
066    import org.deegree.model.feature.Feature;
067    import org.deegree.model.feature.FeatureCollection;
068    import org.deegree.model.feature.FeatureException;
069    import org.deegree.model.feature.FeatureProperty;
070    import org.deegree.model.feature.GMLFeatureAdapter;
071    import org.deegree.model.filterencoding.ComplexFilter;
072    import org.deegree.model.filterencoding.Expression;
073    import org.deegree.model.filterencoding.Literal;
074    import org.deegree.model.filterencoding.OperationDefines;
075    import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
076    import org.deegree.model.filterencoding.PropertyName;
077    import org.deegree.ogcbase.ExceptionCode;
078    import org.deegree.ogcbase.PropertyPath;
079    import org.deegree.ogcbase.PropertyPathFactory;
080    import org.deegree.ogcwebservices.InvalidParameterValueException;
081    import org.deegree.ogcwebservices.OGCWebServiceException;
082    import org.deegree.ogcwebservices.csw.CSWExceptionCode;
083    import org.deegree.ogcwebservices.csw.capabilities.CatalogueOperationsMetadata;
084    import org.deegree.ogcwebservices.csw.configuration.CatalogueConfiguration;
085    import org.deegree.ogcwebservices.csw.configuration.CatalogueConfigurationDocument;
086    import org.deegree.ogcwebservices.csw.configuration.CatalogueOutputSchemaParameter;
087    import org.deegree.ogcwebservices.csw.configuration.CatalogueOutputSchemaValue;
088    import org.deegree.ogcwebservices.csw.configuration.CatalogueTypeNameSchemaParameter;
089    import org.deegree.ogcwebservices.csw.configuration.CatalogueTypeNameSchemaValue;
090    import org.deegree.ogcwebservices.csw.discovery.GetRecords.RESULT_TYPE;
091    import org.deegree.ogcwebservices.wfs.WFService;
092    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
093    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
094    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
095    import org.deegree.ogcwebservices.wfs.operation.GetFeatureDocument;
096    import org.deegree.ogcwebservices.wfs.operation.Query;
097    import org.w3c.dom.Document;
098    import org.w3c.dom.NamedNodeMap;
099    import org.w3c.dom.Node;
100    import org.w3c.dom.NodeList;
101    import org.xml.sax.SAXException;
102    
103    /**
104     * The Discovery class allows clients to discover resources registered in a catalogue, by providing four operations
105     * named <code>query</code>,<code>present</code>, <code>describeRecordType</code>, and <code>getDomain</code>. 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: 28346 $, $Date: 2010-11-23 12:08:02 +0100 (Di, 23 Nov 2010) $
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                XSLTDocument xslSheet = OUT_XSL.get( outputSchema.toUpperCase() );
756                XMLFragment resultDocument = xslSheet.transform( bis, null, null, params );
757                GetRecordByIdResultDocument cswResponseDocument = new GetRecordByIdResultDocument();
758                cswResponseDocument.setRootElement( resultDocument.getRootElement() );
759                cswResponse = cswResponseDocument.parseGetRecordByIdResponse( getRecordById );
760            } catch ( Exception e ) {
761                e.printStackTrace();
762                String msg = "Can't transform WFS response (FeatureCollection) " + "to CSW response: " + e.getMessage();
763                LOG.logError( msg, e );
764                throw new OGCWebServiceException( msg );
765            }
766    
767            return cswResponse;
768        }
769    
770        /**
771         * Contacts the wfsResource to find a rim:ExtrinsicObject which contains the
772         * {@link GetRepositoryItem#getRepositoryItemID()} and retrieves it's
773         * app:RegistryObject/app:extrinsicObject/app:ExtrinsicObject/app:object. The value in this property will then be
774         * written to the response stream (e.g. sent to the requester).
775         * 
776         * @param request
777         *            the created OGCRequest
778         * @return the repository item response
779         * @throws OGCWebServiceException
780         */
781        public GetRepositoryItemResponse guery( GetRepositoryItem request )
782                                throws OGCWebServiceException {
783            // Some properterypaths which are used for the creation of a complex filter.
784            URI appURI = URI.create( "http://www.deegree.org/app" );
785    
786            QualifiedName registryObject = new QualifiedName( "app", "RegistryObject", appURI );
787            Expression iduriExpr = new PropertyName( new QualifiedName( "app", "iduri", appURI ) );
788            Expression idLiteral = new Literal( request.getRepositoryItemID().toString() );
789            PropertyIsCOMPOperation idOperator = new PropertyIsCOMPOperation( OperationDefines.PROPERTYISEQUALTO,
790                                                                              iduriExpr, idLiteral );
791            ComplexFilter idFilter = new ComplexFilter( idOperator );
792    
793            FeatureCollection featureCollectionOnId = null;
794            try {
795                FeatureResult fr = sendWFSGetFeature( registryObject, idFilter );
796                if ( fr != null ) {
797                    featureCollectionOnId = (FeatureCollection) fr.getResponse();
798                }
799            } catch ( OGCWebServiceException e ) {
800                throw new OGCWebServiceException( "The requested item " + request.getRepositoryItemID()
801                                                  + " could not be retrieved from the csw backend: " + e.getMessage(),
802                                                  CSWExceptionCode.WRS_NOTFOUND );
803            }
804            if ( featureCollectionOnId == null ) {
805                throw new OGCWebServiceException( "The requested item " + request.getRepositoryItemID()
806                                                  + " could not be retrieved from the csw backend.",
807                                                  CSWExceptionCode.WRS_NOTFOUND );
808            }
809            String numbOfFeatures = featureCollectionOnId.getAttribute( "numberOfFeatures" );
810            int featureCount = 0;
811            try {
812                featureCount = Integer.parseInt( numbOfFeatures );
813                LOG.logDebug( "the number of features in the GetFeature was: " + featureCount );
814            } catch ( NumberFormatException nfe ) {
815                // nottin
816            }
817    
818            GetRepositoryItemResponse response = null;
819            // Check the number of hits we've found, if the id allready exists it means we want to set the status of the
820            // object to invalid.
821            // String newID = id;
822            if ( featureCount > 1 ) {
823                throw new OGCWebServiceException( "The id : " + request.getRepositoryItemID()
824                                                  + " is not unique. This repositoryItem can therefore not be retrieved.",
825                                                  CSWExceptionCode.WRS_NOTFOUND );
826            } else if ( featureCount == 0 ) {
827                throw new OGCWebServiceException(
828                                                  "The id: "
829                                                                          + request.getRepositoryItemID()
830                                                                          + " corresponds to no rim:ExtrinsicObject. This repositoryItem can therefore not be retrieved.",
831                                                  CSWExceptionCode.WRS_NOTFOUND );
832    
833            } else {
834                Feature f = featureCollectionOnId.getFeature( 0 );
835                if ( f != null ) {
836                    PropertyPath pp = PropertyPathFactory.createPropertyPath( registryObject );
837                    pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "extrinsicObject",
838                                                                                              appURI ) ) );
839                    pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "ExtrinsicObject",
840                                                                                              appURI ) ) );
841                    pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "object", appURI ) ) );
842                    FeatureProperty retrievedObject = null;
843                    try {
844                        retrievedObject = f.getDefaultProperty( pp );
845                    } catch ( PropertyPathResolvingException ppre ) {
846                        throw new OGCWebServiceException(
847                                                          "The id: "
848                                                                                  + request.getRepositoryItemID()
849                                                                                  + " has no repository item stored, there is nothing to be retrieved.",
850                                                          CSWExceptionCode.WRS_NOTFOUND );
851    
852                    }
853                    if ( retrievedObject == null || retrievedObject.getValue() == null ) {
854                        throw new OGCWebServiceException(
855                                                          "The id: "
856                                                                                  + request.getRepositoryItemID()
857                                                                                  + " has no repository item stored, there is nothing to be retrieved.",
858                                                          CSWExceptionCode.WRS_NOTFOUND );
859                    }
860    
861                    String repositoryItem = (String) retrievedObject.getValue();
862                    LOG.logDebug( "found the repositoryItem: " + repositoryItem );
863    
864                    pp = PropertyPathFactory.createPropertyPath( registryObject );
865                    pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "extrinsicObject",
866                                                                                              appURI ) ) );
867                    pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "ExtrinsicObject",
868                                                                                              appURI ) ) );
869                    pp.append( PropertyPathFactory.createPropertyPathStep( new QualifiedName( "app", "mimeType", appURI ) ) );
870                    FeatureProperty mimeType = null;
871                    try {
872                        mimeType = f.getDefaultProperty( pp );
873                    } catch ( PropertyPathResolvingException ppre ) {
874                        LOG.logError( "The mimetype value (of the GetRepositoryItem: " + request.getRepositoryItemID()
875                                      + ") was not set, setting content header to 'application/xml' " );
876                    }
877                    if ( mimeType == null || mimeType.getValue() == null ) {
878                        LOG.logError( "The mimetype value (of the GetRepositoryItem: " + request.getRepositoryItemID()
879                                      + ") was not set, setting content header to 'application/xml' " );
880                    }
881    
882                    try {
883                        XMLFragment itemFrag = new XMLFragment( new StringReader( repositoryItem ), XMLFragment.DEFAULT_URL );
884                        response = new GetRepositoryItemResponse( request.getId(), request.getRepositoryItemID(), itemFrag );
885                    } catch ( SAXException e ) {
886                        LOG.logError( e.getLocalizedMessage(), e );
887                        throw new OGCWebServiceException( null, "The resulting repository item was not of type xml: "
888                                                                + e.getLocalizedMessage(),
889                                                          CSWExceptionCode.NOAPPLICABLECODE );
890                    } catch ( IOException e ) {
891                        LOG.logError( e.getLocalizedMessage(), e );
892                        throw new OGCWebServiceException( null, "The resulting repository item was not of type xml: "
893                                                                + e.getLocalizedMessage(),
894                                                          CSWExceptionCode.NOAPPLICABLECODE );
895                    }
896                }
897            }
898            return response;
899        }
900    
901        /**
902         * Generates and sends a GetFeature to the wfsResource.
903         * 
904         * @param registryObject
905         *            the QName of the registryObject e.g. app:RegistryObject (xmlns:app="http://www.deegree.org/app")
906         * @param filter
907         *            a ogc:Filter representation containing the (app:iduri isequal requestID) mapping.
908         * @return the FeatureResult of the given filter or <code>null</code> if something went wrong.
909         * @throws OGCWebServiceException
910         *             thrown if the wfsResource encounters any problems
911         */
912        private FeatureResult sendWFSGetFeature( QualifiedName registryObject, ComplexFilter filter )
913                                throws OGCWebServiceException {
914            Query q = Query.create( registryObject, filter );
915            GetFeature gfwl = GetFeature.create( "1.1.0", "0",
916                                                 org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE.RESULTS,
917                                                 "text/xml; subtype=gml/3.1.1", "no_handle", -1, 0, -1, -1,
918                                                 new Query[] { q } );
919            // GetFeature gfwl = GetFeature.create( "1.1.0", "0", RESULT_TYPE.RESULTS, "text/xml; subtype=gml/3.1.1",
920            // "no_handle", -1, 0, -1, -1, new Query[] { q } );
921            if ( LOG.isDebug() ) {
922                try {
923                    GetFeatureDocument gd = org.deegree.ogcwebservices.wfs.XMLFactory.export( gfwl );
924                    LOG.logDebug( " The getFeature:\n" + gd.getAsPrettyString() );
925                } catch ( IOException e ) {
926                    LOG.logError( "CSW (Ebrim) GetRepositoryItem-Filter:  An error occurred while trying to get a debugging output for the generated GetFeatureDocument: "
927                                  + e.getMessage() );
928                } catch ( XMLParsingException 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                }
932            }
933    
934            Object response = wfsResource.doService( gfwl );
935            if ( response instanceof FeatureResult ) {
936                return (FeatureResult) response;
937            }
938            throw new OGCWebServiceException( "No valid response from the backend while retrieving GetRepositoryItem." );
939            // LOG.logDebug( "Got no valid response from the wfsResource, returning null" );
940            // return null;
941        }
942    
943    }