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