    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/csw/discovery/GetRecords.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.StringReader;
    039    import java.net.URI;
    040    import java.net.URISyntaxException;
    041    import java.util.Map;
    042
    043    import org.deegree.framework.log.ILogger;
    044    import org.deegree.framework.log.LoggerFactory;
    045    import org.deegree.framework.util.StringTools;
    046    import org.deegree.framework.xml.NamespaceContext;
    047    import org.deegree.framework.xml.XMLParsingException;
    048    import org.deegree.framework.xml.XMLTools;
    049    import org.deegree.i18n.Messages;
    050    import org.deegree.model.filterencoding.AbstractFilter;
    051    import org.deegree.model.filterencoding.Filter;
    052    import org.deegree.ogcbase.CommonNamespaces;
    053    import org.deegree.ogcbase.ExceptionCode;
    054    import org.deegree.ogcbase.SortProperty;
    055    import org.deegree.ogcwebservices.InvalidParameterValueException;
    056    import org.deegree.ogcwebservices.MissingParameterValueException;
    057    import org.deegree.ogcwebservices.OGCWebServiceException;
    058    import org.deegree.ogcwebservices.OperationNotSupportedException;
    059    import org.deegree.ogcwebservices.csw.AbstractCSWRequest;
    060    import org.deegree.ogcwebservices.csw.CSWPropertiesAccess;
    061    import org.w3c.dom.Document;
    062    import org.w3c.dom.Element;
    063
    064    /**
    065     * Class representation of a <code>GetRecords</code> request.
    066     * <p>
    067     * The primary means of resource discovery in the general model are the two operations search and present. In the HTTP
    068     * protocol binding these are combined in the form of the mandatory <code>GetRecords</code> operation, which does a
    069     * search.
    070     * <p>
    071     * Parameters specific to the <code>GetRecords</code> -request (omitting REQUEST, SERVICE and VERSION): <table
    072     * border="1">
    073     * <tr>
    074     * <th>Name</th>
    075     * <th>Occurences</th>
    076     * <th>Function</th>
    077     * </tr>
    078     * <tr>
    079     * <td align="center">NAMESPACE</td>
    080     * <td align="center">0|1</td>
    081     * <td>The NAMESPACE parameter is included in the KVP encoding to allow clients to bind any namespace prefixes that
    082     * might be used for qualified names specified in other parameters. For example, the typeName parameter may include
    083     * qualified names of the form namespace prefix:name. The value of the NAMESPACE parameter is a comma separated list of
    084     * character strings of the form [namespace prefix:] namespace url. Not including the name namespace prefix binds the
    085     * specified URL to the default namespace. As in XML, only one default namespace may be bound. This parameter is not
    086     * required for the XML encoding since XML includes a mechanism for binding namespace prefixes.</td>
    087     * </tr>
    088     * <tr>
    089     * <td align="center">resultType</td>
    090     * <td align="center">0|1 (default: RESULTS)</td>
    091     * <td>The resultType parameter may have the values HITS, RESULTS or VALIDATE and is used to indicate whether the
    092     * catalogue service returns the full result set, the number of hits the query found or validates the request. If the
    093     * resultType parameter is set to HITS, the catalogue service shall return an empty &lt;GetRecordsResponse&gt; element
    094     * with the numberOfRecordsMatched attribute set to indicate the number of hits. The other attributes may be set to zero
    095     * or not specified at all if they are optional. If the resultType parameter is set to HITS, then the values for the
    096     * parameters outputFormat and outputSchema (if specified) shall be ignored since no actual records will be returned. If
    097     * the resultType parameter is set to RESULTS, the catalogue service should generate a complete response with the
    098     * &lt;GetRecordsResponse&gt; element containing the result set for the request. If the resultType parameter is set to
    099     * VALIDATE, the catalogue service shall validate the request and return an empty &lt;GetRecordsResponse&gt;. All
    100     * mandatory attributes may be given a value of zero and all optional attributes may be omitted. If the request does not
    101     * validate then a service exception shall be raised as describe in Subclause 10.3.2.3.</td>
    102     * </tr>
    103     * <tr>
    104     * <td align="center">outputFormat</td>
    105     * <td align="center">0|1 (default: text/xml)</td>
    106     * <td>The outputFormat parameter is used to control the format of the output that is generated in response to a
    107     * GetRecords request. Its value must be a MIME type. The default value, "text/xml", means that the output shall be an
    108     * XML document. All registries shall at least support XML as an output format. Other output formats may be supported
    109     * and may include output formats such as TEXT (MIME type text/plain), or HTML (MIME type text/html). The list of output
    110     * formats that a CSW instance provides must be advertised in the Capabilities document. In the case where the output
    111     * format is text/xml, the CSW must generate an XML document that validates against a schema document that is specified
    112     * in the output document via the xsi:schemaLocation attribute defined in XML.</td>
    113     * </tr>
    114     * <tr>
    115     * <td align="center">outputSchema</td>
    116     * <td align="center">0|1 (default: OGCCORE)</td>
    117     * <td>The outputSchema parameter is used to indicate the schema of the output that is generated in response to a
    118     * GetRecords request. The default value for this parameter shall be OGCCORE indicating that the schema for the core
    119     * returnable properties (as defined in subclause 6.3.3) shall be used. Application profiles may define additional
    120     * values for outputSchema and may redefine the default value but all profiles must support the value OGCCORE. Examples
    121     * values for the outputSchema parameter might be FGDC, or ISO19119, ISO19139 or ANZLIC. The list of supported output
    122     * schemas must be advertised in the capabilities document.</tr>
    123     * <tr>
    124     * <td align="center">startPosition</td>
    125     * <td align="center">0|1 (default: 1)</td>
    126     * <td>The startPosition paramater is used to indicate at which record position the catalogue should start generating
    127     * output. The default value is 1 meaning it starts at the first record in the result set.</td>
    128     * </tr>
    129     * <tr>
    130     * <td align="center">maxRecords</td>
    131     * <td align="center">0|1 (default: 10)</td>
    132     * <td>The maxRecords parameter is used to define the maximum number of records that should be returned from the result
    133     * set of a query. If it is not specified, then 10 records shall be returned. If its value is set to zero, then the
    134     * behavior is indentical to setting "resultType=HITS" as described above.</td>
    135     * </tr>
    136     * <tr>
    137     * <td align="center">typeName</td>
    138     * <td align="center">1</td>
    139     * <td>The typeName parameter is a list of record type names that define a set of metadata record element names which
    140     * will be constrained in the predicate of the query. In addition, all or some of the these names may be specified in
    141     * the query to define which metadata record elements the query should present in the response to the GetRecords
    142     * operation.</td>
    143     * </tr>
    144     * <tr>
    145     * <td align="center">ElementSetName / ElementName</td>
    146     * <td align="center">* (default: 10)</td>
    147     * <td>The ElementName parameter is used to specify one or more metadata record elements that the query should present
    148     * in the response to the a GetRecords operation. Well known sets of element may be named, in which case the
    149     * ElementSetName parameter may be used (e.g.brief, summary or full). If neither parameter is specified, then a CSW
    150     * shall present all metadata record elements. As mentioned above, if the outputFormat parameter is set to text/xml,
    151     * then the response to the GetRecords operation shall validate against a schema document that is referenced in the
    152     * response using the xmlns attributes. If the set of metadata record elements that the client specifies in the query in
    153     * insufficient to generate a valid XML response document, a CSW may augment the list of elements presented to the
    154     * client in order to be able to generate a valid document. Thus a client application should expect to receive more than
    155     * the requested elements if the output format is set to XML. </td>
    156     * </tr>
    157     * <tr>
    158     * <td align="center">CONSTRAINTLANGUAGE / Constraint</td>
    159     * <td align="center">0|1</td>
    160     * <td>Each request encoding (XML and KVP) has a specific mechanism for specifying the predicate language that will be
    161     * used to constrain a query. In the XML encoding, the element &lt;Constraint&gt; is used to define the query predicate.
    162     * The root element of the content of the &lt;Constraint&gt; element defines the predicate language that is being used.
    163     * Two possible root elements are &lt;ogc:Filter&gt; for the OGC XML filter encoding, and &lt;csw:CqlText&gt; for a
    164     * common query language string. An example predicate specification in the XML encoding is:
    165     *
    166     * &lt;Constraint&gt; &lt;CqlText&gt;prop1!=10&lt;/CqlText&gt; &lt;/Constraint&gt;
    167     *
    168     * In the KVP encoding, the parameter CONSTRAINTLANGUAGE is used to specify the predicate language being used. The
    169     * Constraint parameter is used to specify the actual predicate. For example, to specify a CQL predicate, the following
    170     * parameters would be set in the KVP encoding: <br>
    171     *
    172     * ...CONSTRAINTLANGUAGE=CQL_TEXT&amp;CONSTRAINT=&quot;prop1!=10&quot;...
    173     *
    174     * </td>
    175     * </tr>
    176     * <tr>
    177     * <td align="center">SortBy</td>
    178     * <td align="center">0|1</td>
    179     * <td>The result set may be sorted by specifying one or more metadata record elements upon which to sort. In KVP
    180     * encoding, the SORTBY parameter is used to specify the list of sort elements. The value for the SORTBY parameter is a
    181     * comma-separated list of metadata record element names upon which to sort the result set. The format for each element
    182     * in the list shall be either element name:A indicating that the element values should be sorted in ascending order or
    183     * element name:D indicating that the element values should be sorted in descending order. For XML encoded requests, the
    184     * &lt;ogc:SortBy&gt; element is used to specify a list of sort metadata record elements. The attribute sortOrder is
    185     * used to specify the sort order for each element. Valid values for the sortOrder attribute are ASC indicating an
    186     * ascending sort and DESC indicating a descending sort.</td>
    187     * </tr>
    188     * <tr>
    189     * <td align="center">DistributedSearch / hopCount</td>
    190     * <td align="center">0|1 (default: FALSE)</td>
    191     * <td>The DistributedSearch parameter may be used to indicate that the query should be distributed. The default query
    192     * behaviour, if the DistributedSearch parameter is set to FALSE (or is not specified at all), is to execute the query
    193     * on the local server. In the XML encoding, if the &lt;DistributedSearch&gt; element is not specified then the query is
    194     * executed on the local server. <br>
    195     * <br>
    196     * The hopCount parameter controls the distributed query behaviour by limiting the maximum number of message hops before
    197     * the search is terminated. Each catalogue decrements this value by one when the request is received and does not
    198     * propagate the request if the hopCount=0.</td>
    199     * </tr>
    200     * <tr>
    201     * <td align="center">ResponseHandler</td>
    202     * <td align="center">0|1</td>
    203     * <td>The ResponseHandler parameter is a flag that indicates how the GetRecords operation should be processed by a
    204     * CSW. If the parameter is not present, then the GetRecords operation is processed synchronously meaning that the
    205     * client sends the GetRecords request to a CSW and waits to receive a valid response or exception message. The CSW
    206     * immediately processes the GetRecords request while the client waits for a response. The problem with this mode of
    207     * operation is that the client may timeout waiting for the CSW to process the request. If the ResponseHandler parameter
    208     * is present, the GetRecords operation is processed asynchronously. In this case, the CSW responds immediately to a
    209     * client's request with an acknowledgment message that tells the client that the request has been received and
    210     * validated, and notification of completion will be sent to the URI specified as the value of the ResponseHandler
    211     * parameter.</td>
    212     * </tr>
    213     * </table>
    214     *
    215     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
    216     * @version $Revision: 25619 $
    217     *
    218     *
    219     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
    220     * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
    221     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
    222     *
    223     * @author last edited by: $Author: apoth $
    224     *
    225     * @version $Revision: 25619 $, $Date: 2010-08-02 08:17:24 +0200 (Mo, 02. Aug 2010) $
    226     */
    227
    228    public class GetRecords extends AbstractCSWRequest {
    229
    230        private static final long serialVersionUID = 2796229558893029054L;
    231
    232        private static final ILogger LOG = LoggerFactory.getLogger( GetRecords.class );
    233
    234        protected static final String DEFAULT_OUTPUTFORMAT = "application/xml";
    235
    236        protected static final String DEFAULT_OUTPUTSCHEMA = "csw:Record";
    237
    238        protected static final String DEFAULT_OUTPUTSCHEMA_202 = "http://www.opengis.net/cat/csw/2.0.2";
    239
    240        protected static final int DEFAULT_STARTPOSITION = 1;
    241
    242        protected static final int DEFAULT_MAX_RECORDS = 10;
    243
    244        protected static final int DEFAULT_HOPCOUNT = 2;
    245
    246        protected static final String DEFAULT_VERSION = "2.0.0";
    247
    248        /**
    249         * defining HITS as String
    250         */
    251        public static String RESULT_TYPE_STRING_HITS = "hits";
    252
    253        /**
    254         * defining VALIDATE as String
    255         */
    256        public static String RESULT_TYPE_STRING_VALIDATE = "validate";
    257
    258        /**
    259         * defining RESULTS as String
    260         */
    261        public static String RESULT_TYPE_STRING_RESULTS = "results";
    262
    263        private static NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
    264
    265        private RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
    266
    267        // keys are Strings (namespace prefix or "" for default namespace), values
    268        // are URIs
    269        private Map<String, URI> namespace;
    270
    271        private String outputFormat;
    272
    273        private String outputSchema;
    274
    275        private int startPosition;
    276
    277        private int maxRecords;
    278
    279        private int hopCount;
    280
    281        private URI responseHandler;
    282
    283        // private Query[] queries;
    284
    285        private Query query;
    286
    287        /**
    288         * Creates a new <code>GetRecords</code> instance.
    289         *
    290         * @param id
    291         * @param version
    292         * @param vendorSpecificParameters
    293         * @param namespace
    294         * @param resultType
    295         * @param outputFormat
    296         * @param outputSchema
    297         * @param startPosition
    298         * @param maxRecords
    299         * @param hopCount
    300         * @param responseHandler
    301         * @param query
    302         */
    303        public GetRecords( String id, String version, Map<String, String> vendorSpecificParameters,
    304                           Map<String, URI> namespace, RESULT_TYPE resultType, String outputFormat, String outputSchema,
    305                           int startPosition, int maxRecords, int hopCount, URI responseHandler, Query query ) {
    306            super( version, id, vendorSpecificParameters );
    307            this.namespace = namespace;
    308            this.resultType = resultType;
    309            this.outputFormat = outputFormat;
    310            this.outputSchema = outputSchema;
    311            this.startPosition = startPosition;
    312            this.maxRecords = maxRecords;
    313            this.hopCount = hopCount;
    314            this.responseHandler = responseHandler;
    315            this.query = query;
    316        }
    317
    318        /**
    319         * creates a GetRecords request from the XML fragment passed. The passed element must be valid against the OGC CSW
    320         * 2.0 GetRecords schema.
    321         *
    322         * TODO respect namespaces (use QualifiedNames) for type names
    323         *
    324         * @param id
    325         *            unique ID of the request
    326         * @param root
    327         *            root element of the GetRecors request
    328         * @return a GetRecords instance with given id and parsed values from the root element
    329         * @throws MissingParameterValueException
    330         *             if a required parameter was not set
    331         * @throws InvalidParameterValueException
    332         *             if a parameter is invalid
    333         * @throws OGCWebServiceException
    334         *             if something went wrong while creating the Request
    335         */
    336        public static GetRecords create( String id, Element root )
    337                                throws MissingParameterValueException, InvalidParameterValueException,
    338                                OGCWebServiceException {
    339            String version = null;
    340            try {
    341                // first try to read verdsion attribute which is optional for CSW 2.0.0 and 2.0.1
    342                version = XMLTools.getNodeAsString( root, "./@version", nsContext, null );
    343            } catch ( XMLParsingException e ) {
    344                // default version?
    345            }
    346            if ( version == null ) {
    347                // if no version attribute has been set try mapping namespace URI to a version;
    348                // this is not well defined for 2.0.0 and 2.0.1 which uses the same namespace.
    349                // in this case 2.0.0 will be returned!
    350                version = CSWPropertiesAccess.getString( root.getNamespaceURI() );
    351            }
    352
    353            // read class for version depenging parsing of GetRecords request from properties
    354            String className = CSWPropertiesAccess.getString( "GetRecords" + version );
    355            Class<?> clzz = null;
    356            try {
    357                clzz = Class.forName( className );
    358            } catch ( ClassNotFoundException e ) {
    359                LOG.logError( e.getMessage(), e );
    360                throw new InvalidParameterValueException( e.getMessage(), e );
    361            }
    362            GetRecordsDocument document = null;
    363            try {
    364                document = (GetRecordsDocument) clzz.newInstance();
    365            } catch ( InstantiationException e ) {
    366                LOG.logError( e.getMessage(), e );
    367                throw new InvalidParameterValueException( e.getMessage(), e );
    368            } catch ( IllegalAccessException e ) {
    369                LOG.logError( e.getMessage(), e );
    370                throw new InvalidParameterValueException( e.getMessage(), e );
    371            }
    372
    373            document.setRootElement( root );
    374
    375            GetRecords ogcRequest = document.parse( id );
    376
    377            return ogcRequest;
    378        }
    379
    380        /**
    381         * Creates a new <code>GetRecords</code> instance from the values stored in the submitted Map. Keys (parameter
    382         * names) in the Map must be uppercase.
    383         *
    384         * @TODO evaluate vendorSpecificParameter
    385         *
    386         * @param kvp
    387         *            Map containing the parameters
    388         * @return a GetRecords instance with given id and values from the kvp
    389         * @exception InvalidParameterValueException
    390         * @exception MissingParameterValueException
    391         * @throws OperationNotSupportedException
    392         *             if an CQL_TEXT constrain is requested
    393         */
    394        public static GetRecords create( Map<String, String> kvp )
    395                                throws InvalidParameterValueException, MissingParameterValueException,
    396                                OperationNotSupportedException {
    397
    398            // String version = "2.0.0";
    399            // Map<String, String> vendorSpecificParameters = null;
    400            // RESULT_TYPE resultType = RESULT_TYPE.HITS;
    401            // String outputFormat = "text/xml";
    402            // String outputSchema = "OGCCORE";
    403            // int startPosition = 1;
    404            // int maxRecords = 10;
    405            // int hopCount = 2;
    406
    407            String service = getParam( "SERVICE", kvp, "CSW" );
    408            if ( !"CSW".equals( service ) ) {
    409                throw new InvalidParameterValueException( "GetRecordDocument",
    410                                                          Messages.getMessage( "CSW_INVALID_SERVICE_PARAM" ),
    411                                                          ExceptionCode.INVALIDPARAMETERVALUE );
    412            }
    413
    414            String id = getParam( "ID", kvp, "" );
    415            LOG.logDebug( "GetRecordRequest id=" + id );
    416
    417            String version = getParam( "VERSION", kvp, DEFAULT_VERSION );
    418            if ( !( DEFAULT_VERSION.equals( version ) || "2.0.1".equals( version ) || "2.0.2".equals( version ) ) ) {
    419                throw new InvalidParameterValueException( "GetRecords", Messages.getMessage( "CSW_NOT_SUPPORTED_VERSION",
    420                                                                                             GetRecords.DEFAULT_VERSION,
    421                                                                                             "2.0.1", "2.0.2", version ),
    422                                                          ExceptionCode.INVALIDPARAMETERVALUE );
    423            }
    424
    425            // extract namespace mappings
    426            Map<String, URI> namespaceMappings = getNSMappings( getParam( "NAMESPACE", kvp, null ) );
    427
    428            String resultTypeString = getParam( "RESULTTYPE", kvp, RESULT_TYPE_STRING_HITS );
    429            RESULT_TYPE resultType = RESULT_TYPE.HITS;
    430            if ( RESULT_TYPE_STRING_HITS.equalsIgnoreCase( resultTypeString ) ) {
    431                resultType = RESULT_TYPE.HITS;
    432            } else if ( RESULT_TYPE_STRING_RESULTS.equalsIgnoreCase( resultTypeString ) ) {
    433                resultType = RESULT_TYPE.RESULTS;
    434            } else if ( RESULT_TYPE_STRING_VALIDATE.equalsIgnoreCase( resultTypeString ) ) {
    435                resultType = RESULT_TYPE.VALIDATE;
    436            } else {
    437                throw new InvalidParameterValueException( "GetRecords",
    438                                                          Messages.getMessage( "CSW_INVALID_RESULTTYPE", resultTypeString,
    439                                                                               GetRecords.RESULT_TYPE_STRING_HITS,
    440                                                                               GetRecords.RESULT_TYPE_STRING_RESULTS,
    441                                                                               GetRecords.RESULT_TYPE_STRING_VALIDATE ),
    442                                                          ExceptionCode.INVALIDPARAMETERVALUE );
    443            }
    444
    445            String outputFormat = getParam( "OUTPUTFORMAT", kvp, DEFAULT_OUTPUTFORMAT );
    446            String defaultOutputSchema = DEFAULT_OUTPUTSCHEMA;
    447            if ( version.equals( "2.0.2" ) ) {
    448                defaultOutputSchema = DEFAULT_OUTPUTSCHEMA_202;
    449            }
    450            String outputSchema = getParam( "OUTPUTSCHEMA", kvp, defaultOutputSchema );
    451            int startPosition = getParamAsInt( "STARTPOSITION", kvp, DEFAULT_STARTPOSITION );
    452            if ( startPosition < 1 ) {
    453                String msg = Messages.getMessage( "CSW_INVALID_STARTPOSITION", new Integer( startPosition ) );
    454                throw new InvalidParameterValueException( msg );
    455            }
    456            int maxRecords = getParamAsInt( "MAXRECORDS", kvp, DEFAULT_MAX_RECORDS );
    457
    458            if ( maxRecords < 0 ) {
    459                maxRecords = DEFAULT_MAX_RECORDS;
    460            }
    461
    462            // build one Query object for each specified typeName
    463            String tmp = getRequiredParam( "TYPENAMES", kvp );
    464            String[] typeNames = StringTools.toArray( tmp, ",", false );
    465            if ( typeNames.length == 0 ) {
    466                throw new MissingParameterValueException( "Mandatory parameter 'TYPENAMES' is missing!" );
    467            }
    468
    469            String elementSetName = kvp.remove( "ELEMENTSETNAME" );
    470            String elementName = kvp.remove( "ELEMENTNAME" );
    471            String[] elementNames = null;
    472
    473            if ( version.equals( "2.0.2" ) ) {
    474                if ( elementSetName == null ) {
    475                    elementSetName = "summary";
    476                } else {
    477                    if ( elementName != null ) {
    478                        LOG.logInfo( Messages.getMessage( "CSW_ELEMENT_SET_NAME_DUPLICATE" ) );
    479                    } else {
    480                        elementNames = StringTools.toArray( elementName, ",", false );
    481                    }
    482                }
    483            } else {
    484
    485                if ( elementSetName == null ) {
    486                    elementSetName = kvp.remove( "ELEMENTNAME" );
    487                } else {
    488                    String test = kvp.remove( "ELEMENTNAME" );
    489                    if ( test != null ) {
    490                        LOG.logInfo( Messages.getMessage( "CSW_ELEMENT_SET_NAME_DUPLICATE" ) );
    491                    }
    492                }
    493
    494                if ( elementSetName != null ) {
    495                    elementNames = StringTools.toArray( elementSetName, ",", false );
    496                    if ( elementNames.length == 0 ) {
    497                        elementNames = null;
    498                    }
    499                }
    500                if ( elementNames == null ) {
    501                    elementNames = new String[] { "full" };
    502                }
    503
    504            }
    505
    506            String constraintString = kvp.remove( "CONSTRAINT" );
    507            if ( constraintString == null ) {
    508                // not really clear if CSW 2.0.2 uses parameter QUERYCONSTRAINT instead
    509                constraintString = kvp.remove( "QUERYCONSTRAINT" );
    510            }
    511            Filter constraint = null;
    512            String constraintLanguage = null;
    513            String cnstrntVersion = null;
    514            if ( constraintString != null ) {
    515                // build Filter object (from CONSTRAINT parameter)
    516                constraintLanguage = kvp.remove( "CONSTRAINTLANGUAGE" );
    517                if ( constraintLanguage != null ) {
    518                    if ( "CQL_TEXT".equalsIgnoreCase( constraintLanguage.trim() ) ) {
    519                        throw new OperationNotSupportedException( Messages.getMessage( "CSW_NO_CQL_IMPLEMENTATION" ) );
    520                    } else if ( !"FILTER".equalsIgnoreCase( constraintLanguage.trim() ) ) {
    521                        throw new InvalidParameterValueException( Messages.getMessage( "CSW_INVALID_CONSTRAINT_LANGUAGE",
    522                                                                                       constraintLanguage.trim() ) );
    523                    }
    524                } else {
    525                    throw new InvalidParameterValueException( Messages.getMessage( "CSW_CQL_NOR_FILTER_KVP" ) );
    526                }
    527                cnstrntVersion = kvp.remove( "CONSTRAINT_LANGUAGE_VERSION" );
    528                if ( "2.0.2".equals( version ) && cnstrntVersion == null ) {
    529                    throw new InvalidParameterValueException(
    530                                                              Messages.getMessage( "CSW_MISSING_CONSTRAINT_LANGUAGE_VERSION" ) );
    531                }
    532
    533                try {
    534                    Document doc = XMLTools.parse( new StringReader( constraintString ) );
    535                    Element element = doc.getDocumentElement();
    536                    constraint = AbstractFilter.buildFromDOM( element, "1.0.0".equals( cnstrntVersion ) );
    537                } catch ( Exception e ) {
    538                    String msg = "An error occured when parsing the 'CONSTRAINT' parameter " + "Filter expression: "
    539                                 + e.getMessage();
    540                    throw new InvalidParameterValueException( msg );
    541                }
    542            }
    543
    544            SortProperty[] sortProperties = SortProperty.create( kvp.remove( "SORTBY" ), namespaceMappings );
    545
    546            // Query[] queries = new Query[typeNames.length];
    547            // for ( int i = 0; i < typeNames.length; i++ ) {
    548            Query query = new Query( elementSetName, elementNames, constraint, sortProperties, typeNames );
    549            // }
    550
    551            // find out if the query should be performed locally or in a distributed
    552            // fashion
    553            int hopCount = DEFAULT_HOPCOUNT;
    554            String distributedSearch = getParam( "DISTRIBUTEDSEARCH", kvp, "false" );
    555            if ( distributedSearch.equalsIgnoreCase( "true" ) ) {
    556                hopCount = getParamAsInt( "HOPCOUNT", kvp, DEFAULT_HOPCOUNT );
    557            }
    558
    559            String rHandler = kvp.remove( "RESPONSEHANDLER" );
    560            URI responseHandler = null;
    561            if ( rHandler != null ) {
    562                try {
    563                    responseHandler = new URI( rHandler );
    564                } catch ( URISyntaxException e ) {
    565                    throw new InvalidParameterValueException(
    566                                                              Messages.getMessage( "CSW_INVALID_RESPONSE_HANDLER", rHandler ) );
    567                }
    568                throw new OperationNotSupportedException( Messages.getMessage( "CSW_NO_REPONSE_HANDLER_IMPLEMENTATION" ) );
    569
    570            }
    571
    572            return new GetRecords( id, version, kvp, namespaceMappings, resultType, outputFormat, outputSchema,
    573                                   startPosition, maxRecords, hopCount, responseHandler, query );
    574        }
    575
    576        /**
    577         * Used to specify a namespace and its prefix. Format must be [ <prefix>:] <url>. If the prefix is not specified
    578         * then this is the default namespace
    579         * <p>
    580         * Zero or one (Optional) ; Include value for each distinct namespace used by all qualified names in the request. If
    581         * not included, all qualified names are in default namespace
    582         * <p>
    583         * The NAMESPACE parameter is included in the KVP encoding to allow clients to bind any namespace prefixes that
    584         * might be used for qualified names specified in other parameters. For example, the typeName parameter may include
    585         * qualified names of the form namespace prefix:name.
    586         * <p>
    587         * The value of the NAMESPACE parameter is separated list of character strings of the form [namespace
    588         * prefix:]namespace url. Not including the name namespace prefix binds the specified URL to the default namespace.
    589         * As in XML, only one default namespace may be bound.
    590         *
    591         * @return the mapped namespaces or <code>null</code> if all qualified names are in default namespace.
    592         *
    593         */
    594        public Map<String, URI> getNamespace() {
    595            return this.namespace;
    596        }
    597
    598        /**
    599         * The resultType parameter may have the values HITS, RESULTS or VALIDATE and is used to indicate whether the
    600         * catalogue service returns the full result set, the number of hits the query found or validates the request.
    601         * <p>
    602         * If the resultType parameter is set to HITS, the catalogue service shall return an empty
    603         * &lt;GetRecordsResponse&gt;element with the numberOfRecordsMatched attribute set to indicate the number of hits.
    604         * The other attributes may be set to zero or not specified at all if they are optional.
    605         * <p>
    606         * If the resultType parameter is set to HITS, then the values for the parameters outputFormat and outputSchema (if
    607         * specified) shall be ignored since no actual records will be returned
    608         * <p>
    609         * If the resultType parameter is set to RESULTS, the catalogue service should generate a complete response with the
    610         * &lt;GetRecordsResponse&gt;element containing the result set for the request
    611         * <p>
    612         * If the resultType parameter is set to VALIDATE, the catalogue service shall validate the request and return an
    613         * empty &lt;GetRecordsResponse&gt;. All mandatory attributes may be given a value of zero and all optional
    614         * attributes may be omitted. If the request does not validate then a service exception shall be raised
    615         *
    616         * @return one of HITS, RESULTS or VALIDATE
    617         *
    618         */
    619        public RESULT_TYPE getResultType() {
    620            return this.resultType;
    621        }
    622
    623        /**
    624         * The resultType parameter may have the values HITS, RESULTS or VALIDATE and is used to indicate whether the
    625         * catalogue service returns the full result set, the number of hits the query found or validates the request.
    626         * <p>
    627         * If the resultType parameter is set to HITS, the catalogue service shall return an empty
    628         * &lt;GetRecordsResponse&gt;element with the numberOfRecordsMatched attribute set to indicate the number of hits.
    629         * The other attributes may be set to zero or not specified at all if they are optional.
    630         * <p>
    631         * If the resultType parameter is set to HITS, then the values for the parameters outputFormat and outputSchema (if
    632         * specified) shall be ignored since no actual records will be returned
    633         * <p>
    634         * If the resultType parameter is set to RESULTS, the catalogue service should generate a complete response with the
    635         * &lt;GetRecordsResponse&gt;element containing the result set for the request
    636         * <p>
    637         * If the resultType parameter is set to VALIDATE, the catalogue service shall validate the request and return an
    638         * empty &lt;GetRecordsResponse&gt;. All mandatory attributes may be given a value of zero and all optional
    639         * attributes may be omitted. If the request does not validate then a service exception shall be raised
    640         *
    641         * @return the resulttype as a String, one of "HITS", "VALIDATE" or "RESULTS"
    642         *
    643         */
    644        public String getResultTypeAsString() {
    645            String resultTypeString = null;
    646            switch ( this.resultType ) {
    647            case HITS: {
    648                resultTypeString = RESULT_TYPE_STRING_HITS;
    649                break;
    650            }
    651            case RESULTS: {
    652                resultTypeString = RESULT_TYPE_STRING_RESULTS;
    653                break;
    654            }
    655            case VALIDATE: {
    656                resultTypeString = RESULT_TYPE_STRING_VALIDATE;
    657                break;
    658            }
    659            }
    660            return resultTypeString;
    661        }
    662
    663        /**
    664         * sets the resultType of a request. This may be useful to perform a request first with resultType = HITS to
    665         * determine the total number of records matching a query and afterwards performing the same request with resultType =
    666         * RESULTS (and maxRecords &lt; number of matched records).
    667         *
    668         * @param resultType
    669         */
    670        public void setResultType( RESULT_TYPE resultType ) {
    671            this.resultType = resultType;
    672        }
    673
    674        /**
    675         * returns <= 0 if no distributed search shall be performed. otherwise the recursion depht is returned.
    676         * <p>
    677         * The hopCount parameter controls the distributed query behaviour by limiting the maximum number of message hops
    678         * before the search is terminated. Each catalogue decrements this value by one when the request is received and
    679         * does not propagate the request if the hopCount=0
    680         *
    681         * @return <= 0 if no distributed search shall be performed. otherwise the recursion depht is returned.
    682         *
    683         */
    684        public int getHopCount() {
    685            return this.hopCount;
    686        }
    687
    688        /**
    689         * Value is Mime type;The only value that must be supported is text/xml. Other suppored values may include text/html
    690         * and text/plain
    691         * <p>
    692         * The outputFormat parameter is used to control the format of the output that is generated in response to a
    693         * GetRecords request. Its value must be a MIME type. The default value, "text/xml", means that the output shall be
    694         * an XML document. All registries shall at least support XML as an output format. Other output formats may be
    695         * supported and may include output formats such as TEXT (MIME type text/plain), or HTML (MIME type text/html). The
    696         * list of output formats that a CSW instance provides must be advertised in the Capabilities document
    697         * <p>
    698         * In the case where the output format is text/xml, the CSW must generate an XML document that validates against a
    699         * schema document that is specified in the output document via the xsi:schemaLocation attribute defined in XML
    700         *
    701         * @return Value is a Mime type
    702         *
    703         */
    704        public String getOutputFormat() {
    705            return this.outputFormat;
    706        }
    707
    708        /**
    709         * The outputSchema parameter is used to indicate the schema of the output that is generated in response to a
    710         * GetRecords request. The default value for this parameter shall be OGCCORE indicating that the schema for the core
    711         * returnable properties shall be used. Application profiles may define additional values for outputSchema and may
    712         * redefine the default value but all profiles must support the value OGCCORE
    713         * <p>
    714         * Examples values for the outputSchema parameter might be FGDC, or ISO19119, ISO19139 or ANZLIC. The list of
    715         * supported output schemas must be advertised in the capabilities document
    716         *
    717         * @return The default value for this parameter shall be OGCCORE
    718         *
    719         */
    720        public String getOutputSchema() {
    721            return this.outputSchema;
    722        }
    723
    724        /**
    725         * @return the number of the first returned dataset. Zero or one (Optional)Default value is 1. If startPosition >
    726         *         the number of datasets satisfying the constraint, no dataset will be returned
    727         *
    728         */
    729        public int getStartPosition() {
    730            return this.startPosition;
    731        }
    732
    733        /**
    734         * @return The maxRecords parameter. It is used to define the maximum number of records that should be returned from
    735         *         the result set of a query. If it is not specified, then 10 records shall be returned. If its value is set
    736         *         to zero, then the behavior is indentical to setting "resultType=HITS"
    737         *
    738         */
    739        public int getMaxRecords() {
    740            return this.maxRecords;
    741        }
    742
    743        /**
    744         * @return the location of a response adress to which an asynchronous result may be sent.
    745         */
    746        public URI getResponseHandler() {
    747            return responseHandler;
    748        }
    749
    750        /**
    751         * @return the query object.
    752         */
    753        public Query getQuery() {
    754            return query;
    755        }
    756
    757        /**
    758         * @see #getQuery()
    759         * @param query
    760         */
    761        public void setQuery( Query query ) {
    762            this.query = query;
    763        }
    764
    765        /**
    766         * The <code>RESULT_TYPE</code> a simple enum which defines some result values of a GetRecord.
    767         *
    768         * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
    769         *
    770         * @author last edited by: $Author: apoth $
    771         *
    772         * @version $Revision: 25619 $, $Date: 2010-08-02 08:17:24 +0200 (Mo, 02. Aug 2010) $
    773         *
    774         */
    775
    776        public static enum RESULT_TYPE {
    777            /**
    778             * HITS, the catalogue service shall return an empty &lt;GetRecordsResponse&gt;element with the
    779             * numberOfRecordsMatched attribute set to indicate the number of hits
    780             */
    781            HITS,
    782            /**
    783             * VALIDATE, the catalogue service shall validate the request
    784             */
    785            VALIDATE,
    786            /**
    787             * RESULTS, the catalogue service should generate a complete response with the &lt;GetRecordsResponse&gt;element
    788             * containing the result set for the request
    789             */
    790            RESULTS
    791        }
    792    }