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