001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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):
072     * <table * 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.
123     * </tr>
124     * <tr>
125     * <td align="center">startPosition</td>
126     * <td align="center">0|1 (default: 1)</td>
127     * <td>The startPosition paramater is used to indicate at which record position the catalogue should start generating
128     * output. The default value is 1 meaning it starts at the first record in the result set.</td>
129     * </tr>
130     * <tr>
131     * <td align="center">maxRecords</td>
132     * <td align="center">0|1 (default: 10)</td>
133     * <td>The maxRecords parameter is used to define the maximum number of records that should be returned from the result
134     * set of a query. If it is not specified, then 10 records shall be returned. If its value is set to zero, then the
135     * behavior is indentical to setting "resultType=HITS" as described above.</td>
136     * </tr>
137     * <tr>
138     * <td align="center">typeName</td>
139     * <td align="center">1</td>
140     * <td>The typeName parameter is a list of record type names that define a set of metadata record element names which
141     * will be constrained in the predicate of the query. In addition, all or some of the these names may be specified in
142     * the query to define which metadata record elements the query should present in the response to the GetRecords
143     * operation.</td>
144     * </tr>
145     * <tr>
146     * <td align="center">ElementSetName / ElementName</td>
147     * <td align="center">* (default: 10)</td>
148     * <td>The ElementName parameter is used to specify one or more metadata record elements that the query should present
149     * in the response to the a GetRecords operation. Well known sets of element may be named, in which case the
150     * ElementSetName parameter may be used (e.g.brief, summary or full). If neither parameter is specified, then a CSW
151     * shall present all metadata record elements. As mentioned above, if the outputFormat parameter is set to text/xml,
152     * then the response to the GetRecords operation shall validate against a schema document that is referenced in the
153     * response using the xmlns attributes. If the set of metadata record elements that the client specifies in the query in
154     * insufficient to generate a valid XML response document, a CSW may augment the list of elements presented to the
155     * client in order to be able to generate a valid document. Thus a client application should expect to receive more than
156     * the requested elements if the output format is set to XML.</td>
157     * </tr>
158     * <tr>
159     * <td align="center">CONSTRAINTLANGUAGE / Constraint</td>
160     * <td align="center">0|1</td>
161     * <td>Each request encoding (XML and KVP) has a specific mechanism for specifying the predicate language that will be
162     * used to constrain a query. In the XML encoding, the element &lt;Constraint&gt; is used to define the query predicate.
163     * The root element of the content of the &lt;Constraint&gt; element defines the predicate language that is being used.
164     * Two possible root elements are &lt;ogc:Filter&gt; for the OGC XML filter encoding, and &lt;csw:CqlText&gt; for a
165     * common query language string. An example predicate specification in the XML encoding is:
166     * 
167     * &lt;Constraint&gt; &lt;CqlText&gt;prop1!=10&lt;/CqlText&gt; &lt;/Constraint&gt;
168     * 
169     * In the KVP encoding, the parameter CONSTRAINTLANGUAGE is used to specify the predicate language being used. The
170     * Constraint parameter is used to specify the actual predicate. For example, to specify a CQL predicate, the following
171     * parameters would be set in the KVP encoding: <br>
172     * 
173     * ...CONSTRAINTLANGUAGE=CQL_TEXT&amp;CONSTRAINT=&quot;prop1!=10&quot;...
174     * 
175     * </td>
176     * </tr>
177     * <tr>
178     * <td align="center">SortBy</td>
179     * <td align="center">0|1</td>
180     * <td>The result set may be sorted by specifying one or more metadata record elements upon which to sort. In KVP
181     * encoding, the SORTBY parameter is used to specify the list of sort elements. The value for the SORTBY parameter is a
182     * comma-separated list of metadata record element names upon which to sort the result set. The format for each element
183     * in the list shall be either element name:A indicating that the element values should be sorted in ascending order or
184     * element name:D indicating that the element values should be sorted in descending order. For XML encoded requests, the
185     * &lt;ogc:SortBy&gt; element is used to specify a list of sort metadata record elements. The attribute sortOrder is
186     * used to specify the sort order for each element. Valid values for the sortOrder attribute are ASC indicating an
187     * ascending sort and DESC indicating a descending sort.</td>
188     * </tr>
189     * <tr>
190     * <td align="center">DistributedSearch / hopCount</td>
191     * <td align="center">0|1 (default: FALSE)</td>
192     * <td>The DistributedSearch parameter may be used to indicate that the query should be distributed. The default query
193     * behaviour, if the DistributedSearch parameter is set to FALSE (or is not specified at all), is to execute the query
194     * on the local server. In the XML encoding, if the &lt;DistributedSearch&gt; element is not specified then the query is
195     * executed on the local server. <br>
196     * <br>
197     * The hopCount parameter controls the distributed query behaviour by limiting the maximum number of message hops before
198     * the search is terminated. Each catalogue decrements this value by one when the request is received and does not
199     * propagate the request if the hopCount=0.</td>
200     * </tr>
201     * <tr>
202     * <td align="center">ResponseHandler</td>
203     * <td align="center">0|1</td>
204     * <td>The ResponseHandler parameter is a flag that indicates how the GetRecords operation should be processed by a CSW.
205     * If the parameter is not present, then the GetRecords operation is processed synchronously meaning that the client
206     * sends the GetRecords request to a CSW and waits to receive a valid response or exception message. The CSW immediately
207     * processes the GetRecords request while the client waits for a response. The problem with this mode of operation is
208     * that the client may timeout waiting for the CSW to process the request. If the ResponseHandler parameter is present,
209     * the GetRecords operation is processed asynchronously. In this case, the CSW responds immediately to a client's
210     * request with an acknowledgment message that tells the client that the request has been received and validated, and
211     * notification of completion will be sent to the URI specified as the value of the ResponseHandler parameter.</td>
212     * </tr>
213     * </table>
214     * 
215     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
216     * @version $Revision: 28346 $
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: lbuesching $
224     * 
225     * @version $Revision: 28346 $, $Date: 2010-11-23 12:08:02 +0100 (Di, 23 Nov 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         * setst the startPosition of a request. This may be useful to perform a request first with startPosition = 0 to
676         * determine the total number of records matching a query.
677         * 
678         * @param startPosition
679         */
680        public void setStartPosition( int startPosition ) {
681            this.startPosition = startPosition;
682        }
683    
684        /**
685         * returns <= 0 if no distributed search shall be performed. otherwise the recursion depht is returned.
686         * <p>
687         * The hopCount parameter controls the distributed query behaviour by limiting the maximum number of message hops
688         * before the search is terminated. Each catalogue decrements this value by one when the request is received and
689         * does not propagate the request if the hopCount=0
690         * 
691         * @return <= 0 if no distributed search shall be performed. otherwise the recursion depht is returned.
692         * 
693         */
694        public int getHopCount() {
695            return this.hopCount;
696        }
697    
698        /**
699         * Value is Mime type;The only value that must be supported is text/xml. Other suppored values may include text/html
700         * and text/plain
701         * <p>
702         * The outputFormat parameter is used to control the format of the output that is generated in response to a
703         * GetRecords request. Its value must be a MIME type. The default value, "text/xml", means that the output shall be
704         * an XML document. All registries shall at least support XML as an output format. Other output formats may be
705         * supported and may include output formats such as TEXT (MIME type text/plain), or HTML (MIME type text/html). The
706         * list of output formats that a CSW instance provides must be advertised in the Capabilities document
707         * <p>
708         * In the case where the output format is text/xml, the CSW must generate an XML document that validates against a
709         * schema document that is specified in the output document via the xsi:schemaLocation attribute defined in XML
710         * 
711         * @return Value is a Mime type
712         * 
713         */
714        public String getOutputFormat() {
715            return this.outputFormat;
716        }
717    
718        /**
719         * The outputSchema parameter is used to indicate the schema of the output that is generated in response to a
720         * GetRecords request. The default value for this parameter shall be OGCCORE indicating that the schema for the core
721         * returnable properties shall be used. Application profiles may define additional values for outputSchema and may
722         * redefine the default value but all profiles must support the value OGCCORE
723         * <p>
724         * Examples values for the outputSchema parameter might be FGDC, or ISO19119, ISO19139 or ANZLIC. The list of
725         * supported output schemas must be advertised in the capabilities document
726         * 
727         * @return The default value for this parameter shall be OGCCORE
728         * 
729         */
730        public String getOutputSchema() {
731            return this.outputSchema;
732        }
733    
734        /**
735         * @return the number of the first returned dataset. Zero or one (Optional)Default value is 1. If startPosition >
736         *         the number of datasets satisfying the constraint, no dataset will be returned
737         * 
738         */
739        public int getStartPosition() {
740            return this.startPosition;
741        }
742    
743        /**
744         * @return The maxRecords parameter. It is used to define the maximum number of records that should be returned from
745         *         the result set of a query. If it is not specified, then 10 records shall be returned. If its value is set
746         *         to zero, then the behavior is indentical to setting "resultType=HITS"
747         * 
748         */
749        public int getMaxRecords() {
750            return this.maxRecords;
751        }
752    
753        /**
754         * @return the location of a response adress to which an asynchronous result may be sent.
755         */
756        public URI getResponseHandler() {
757            return responseHandler;
758        }
759    
760        /**
761         * @return the query object.
762         */
763        public Query getQuery() {
764            return query;
765        }
766    
767        /**
768         * @see #getQuery()
769         * @param query
770         */
771        public void setQuery( Query query ) {
772            this.query = query;
773        }
774    
775        /**
776         * The <code>RESULT_TYPE</code> a simple enum which defines some result values of a GetRecord.
777         * 
778         * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
779         * 
780         * @author last edited by: $Author: lbuesching $
781         * 
782         * @version $Revision: 28346 $, $Date: 2010-11-23 12:08:02 +0100 (Di, 23 Nov 2010) $
783         * 
784         */
785    
786        public static enum RESULT_TYPE {
787            /**
788             * HITS, the catalogue service shall return an empty &lt;GetRecordsResponse&gt;element with the
789             * numberOfRecordsMatched attribute set to indicate the number of hits
790             */
791            HITS,
792            /**
793             * VALIDATE, the catalogue service shall validate the request
794             */
795            VALIDATE,
796            /**
797             * RESULTS, the catalogue service should generate a complete response with the &lt;GetRecordsResponse&gt;element
798             * containing the result set for the request
799             */
800            RESULTS
801        }
802    }