source: SRUServer/trunk/src/main/java/eu/clarin/sru/server/SRURequestImpl.java @ 6851

Last change on this file since 6851 was 6851, checked in by Oliver Schonefeld, 9 years ago
  • add convenience method to SRURequest to automatically cast query to a supplied type
  • Property svn:eol-style set to native
File size: 41.6 KB
Line 
1/**
2 * This software is copyright (c) 2011-2013 by
3 *  - Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
4 * This is free software. You can redistribute it
5 * and/or modify it under the terms described in
6 * the GNU General Public License v3 of which you
7 * should have received a copy. Otherwise you can download
8 * it from
9 *
10 *   http://www.gnu.org/licenses/gpl-3.0.txt
11 *
12 * @copyright Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.sru.server;
18
19import java.io.IOException;
20import java.util.ArrayList;
21import java.util.Collections;
22import java.util.Enumeration;
23import java.util.HashMap;
24import java.util.List;
25import java.util.Map;
26
27import javax.servlet.http.HttpServletRequest;
28
29import org.slf4j.Logger;
30import org.slf4j.LoggerFactory;
31import org.z3950.zing.cql.CQLNode;
32import org.z3950.zing.cql.CQLParseException;
33import org.z3950.zing.cql.CQLParser;
34
35
36final class SRURequestImpl implements SRURequest, SRUDiagnosticList {
37    private static final Logger logger =
38            LoggerFactory.getLogger(SRURequest.class);
39    /* general / explain related parameter names */
40    private static final String PARAM_OPERATION            = "operation";
41    private static final String PARAM_VERSION              = "version";
42    private static final String PARAM_STYLESHEET           = "stylesheet";
43    private static final String PARAM_RENDER_BY            = "renderedBy";
44    private static final String PARAM_HTTP_ACCEPT          = "httpAccept";
45    private static final String PARAM_RESPONSE_TYPE        = "responseType";
46    /* searchRetrieve related parameter names */
47    private static final String PARAM_QUERY                = "query";
48    private static final String PARAM_QUERY_TYPE           = "queryType";
49    private static final String PARAM_START_RECORD         = "startRecord";
50    private static final String PARAM_MAXIMUM_RECORDS      = "maximumRecords";
51    private static final String PARAM_RECORD_XML_ESCAPING  = "recordXMLEscaping";
52    private static final String PARAM_RECORD_PACKING       = "recordPacking";
53    private static final String PARAM_RECORD_SCHEMA        = "recordSchema";
54    private static final String PARAM_RECORD_X_PATH        = "recordXPath";
55    private static final String PARAM_RESULT_SET_TTL       = "resultSetTTL";
56    private static final String PARAM_SORT_KEYS            = "sortKeys";
57    /* scan related parameter names */
58    private static final String PARAM_SCAN_CLAUSE          = "scanClause";
59    private static final String PARAM_RESPONSE_POSITION    = "responsePosition";
60    private static final String PARAM_MAXIMUM_TERMS        = "maximumTerms";
61    /* operations */
62    private static final String OP_EXPLAIN                 = "explain";
63    private static final String OP_SCAN                    = "scan";
64    private static final String OP_SEARCH_RETRIEVE         = "searchRetrieve";
65    private static final String VERSION_1_1                = "1.1";
66    private static final String VERSION_1_2                = "1.2";
67    /* various parameter values */
68    private static final String RECORD_XML_ESCAPING_XML    = "xml";
69    private static final String RECORD_XML_ESCPAING_STRING = "string";
70    private static final String RECORD_PACKING_PACKED      = "packed";
71    private static final String RECORD_PACKING_UNPACKED    = "unpacked";
72    private static final String RENDER_BY_CLIENT           = "client";
73    private static final String RENDER_BY_SERVER           = "server";
74    private static final String PARAM_EXTENSION_PREFIX     = "x-";
75    private static final String X_UNLIMITED_RESULTSET      = "x-unlimited-resultset";
76    private static final String X_UNLIMITED_TERMLIST       = "x-unlimited-termlist";
77    private static final String X_INDENT_RESPONSE          = "x-indent-response";
78    private static final int DEFAULT_START_RECORD          = 1;
79    private static final int DEFAULT_RESPONSE_POSITION     = 1;
80    private final SRUServerConfig config;
81    private final SRUQueryParserRegistry queryParsers;
82    private final HttpServletRequest request;
83    private List<SRUDiagnostic> diagnostics;
84    private SRUOperation operation;
85    private SRUVersion version;
86    private SRURecordXmlEscaping recordXmlEscaping;
87    private SRURecordPacking recordPacking;
88    private SRUQuery<?> query;
89    private int startRecord = DEFAULT_START_RECORD;
90    private int maximumRecords = -1;
91    private String recordSchemaIdentifier;
92    private String stylesheet;
93    private SRURenderBy renderBy;
94    private String responseType;
95    private String httpAccept;
96    private String recordXPath;
97    private int resultSetTTL = -1;
98    private String sortKeys;
99    private CQLNode scanClause;
100    private int responsePosition = DEFAULT_RESPONSE_POSITION;
101    private int maximumTerms = -1;
102
103    private static enum Parameter {
104        STYLESHEET,
105        RENDER_BY,
106        HTTP_ACCEPT,
107        RESPONSE_TYPE,
108        START_RECORD,
109        MAXIMUM_RECORDS,
110        RECORD_XML_ESCAPING,
111        RECORD_PACKING,
112        RECORD_SCHEMA,
113        RECORD_XPATH,
114        RESULT_SET_TTL,
115        SORT_KEYS,
116        SCAN_CLAUSE,
117        RESPONSE_POSITION,
118        MAXIMUM_TERMS,
119    }
120
121    private static final class ParameterInfo {
122        private final Parameter parameter;
123        private final SRUVersion min;
124        private final SRUVersion max;
125        private final boolean mandatory;
126
127        private ParameterInfo(Parameter name, boolean mandatory,
128                SRUVersion min, SRUVersion max) {
129            this.parameter = name;
130            this.mandatory = mandatory;
131            this.min       = min;
132            this.max       = max;
133        }
134
135        public Parameter getParameter() {
136            return parameter;
137        }
138
139        public String getName(SRUVersion version) {
140            switch (parameter) {
141            case STYLESHEET:
142                return PARAM_STYLESHEET;
143            case RENDER_BY:
144                return PARAM_RENDER_BY;
145            case HTTP_ACCEPT:
146                return PARAM_HTTP_ACCEPT;
147            case RESPONSE_TYPE:
148                return PARAM_RESPONSE_TYPE;
149            case START_RECORD:
150                return PARAM_START_RECORD;
151            case MAXIMUM_RECORDS:
152                return PARAM_MAXIMUM_RECORDS;
153            case RECORD_XML_ESCAPING:
154                /*
155                 * 'recordPacking' was renamed to 'recordXMLEscaping' in SRU
156                 * 2.0. For library API treat 'recordPacking' parameter as
157                 * 'recordPacking' for SRU 1.1 and SRU 1.2.
158                 */
159                if (version == SRUVersion.VERSION_2_0) {
160                    return PARAM_RECORD_XML_ESCAPING;
161                } else {
162                    return PARAM_RECORD_PACKING;
163                }
164            case RECORD_PACKING:
165                /*
166                 * 'recordPacking' only exists in SRU 2.0; the old variant is
167                 * handled by the case for RECORD_XML_ESCAPING
168                 */
169                if (version == SRUVersion.VERSION_2_0) {
170                    return PARAM_RECORD_PACKING;
171                } else {
172                    return null;
173                }
174            case RECORD_SCHEMA:
175                return PARAM_RECORD_SCHEMA;
176            case RECORD_XPATH:
177                return PARAM_RECORD_X_PATH;
178            case RESULT_SET_TTL:
179                return PARAM_RESULT_SET_TTL;
180            case SORT_KEYS:
181                return PARAM_SORT_KEYS;
182            case SCAN_CLAUSE:
183                return PARAM_SCAN_CLAUSE;
184            case RESPONSE_POSITION:
185                return PARAM_RESPONSE_POSITION;
186            case MAXIMUM_TERMS:
187                return PARAM_MAXIMUM_TERMS;
188            default:
189                throw new InternalError();
190            } // switch
191        }
192
193        public boolean getMandatory() {
194            return mandatory;
195        }
196
197        public boolean isForVersion(SRUVersion version) {
198            return (min.getVersionNumber() <= version.getVersionNumber()) &&
199                    (version.getVersionNumber() <= max.getVersionNumber());
200        }
201    } // class ParameterInfo
202
203    private static final ParameterInfo[] PARAMETER_SET_EXPLAIN = {
204            new ParameterInfo(Parameter.STYLESHEET, false,
205                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
206            new ParameterInfo(Parameter.RECORD_XML_ESCAPING, false,
207                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2)
208    };
209    private static final ParameterInfo[] PARAMETER_SET_SCAN = {
210            new ParameterInfo(Parameter.STYLESHEET, false,
211                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
212            new ParameterInfo(Parameter.HTTP_ACCEPT, false,
213                    SRUVersion.VERSION_2_0, SRUVersion.VERSION_2_0),
214            new ParameterInfo(Parameter.SCAN_CLAUSE, true,
215                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
216            new ParameterInfo(Parameter.RESPONSE_POSITION, false,
217                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
218            new ParameterInfo(Parameter.MAXIMUM_TERMS, false,
219                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0)
220    };
221    private static final ParameterInfo[] PARAMETER_SET_SEARCH_RETRIEVE = {
222        new ParameterInfo(Parameter.STYLESHEET, false,
223                    SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
224        new ParameterInfo(Parameter.HTTP_ACCEPT, false,
225                SRUVersion.VERSION_2_0, SRUVersion.VERSION_2_0),
226        new ParameterInfo(Parameter.RENDER_BY, false,
227                SRUVersion.VERSION_2_0, SRUVersion.VERSION_2_0),
228        new ParameterInfo(Parameter.RESPONSE_TYPE, false,
229                SRUVersion.VERSION_2_0, SRUVersion.VERSION_2_0),
230        new ParameterInfo(Parameter.START_RECORD, false,
231                SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
232        new ParameterInfo(Parameter.MAXIMUM_RECORDS, false,
233                SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
234        new ParameterInfo(Parameter.RECORD_XML_ESCAPING, false,
235                SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
236        new ParameterInfo(Parameter.RECORD_PACKING, false,
237                SRUVersion.VERSION_2_0, SRUVersion.VERSION_2_0),
238        new ParameterInfo(Parameter.RECORD_SCHEMA, false,
239                SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
240        new ParameterInfo(Parameter.RESULT_SET_TTL, false,
241                SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
242        new ParameterInfo(Parameter.RECORD_XPATH, false,
243                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
244        new ParameterInfo(Parameter.SORT_KEYS, false,
245                SRUVersion.VERSION_1_1, SRUVersion.VERSION_2_0),
246    };
247
248
249    SRURequestImpl(SRUServerConfig config,
250            SRUQueryParserRegistry queryParsers,
251            HttpServletRequest request) {
252        this.config       = config;
253        this.queryParsers = queryParsers;
254        this.request      = request;
255    }
256
257
258    /**
259     * Validate incoming request parameters.
260     *
261     * @return <code>true</code> if successful, <code>false</code> if something
262     *         went wrong
263     */
264    boolean checkParameters() {
265        final SRUVersion minVersion = config.getMinVersion();
266        final SRUVersion maxVersion = config.getMaxVersion();
267
268        /*
269         * generally assume, we will also allow processing of SRU 1.1 or 1.2
270         */
271        boolean processSruOld = true;
272
273        /*
274         * Heuristic to detect SRU version and operation ...
275         */
276        SRUOperation operation = null;
277        SRUVersion version = null;
278        if (maxVersion.compareTo(SRUVersion.VERSION_2_0) >= 0) {
279            if (getParameter(PARAM_VERSION, false, false) == null) {
280                /*
281                 * Ok, we're committed to SRU 2.0 now, so don't allow processing
282                 * of SRU 1.1 and 1.2 ...
283                 */
284                processSruOld = false;
285
286                logger.debug("handling request as SRU 2.0, because no '{}' " +
287                        "parameter was found in the request", PARAM_VERSION);
288                if ((getParameter(PARAM_QUERY, false, false) != null) ||
289                        (getParameter(PARAM_QUERY_TYPE, false, false) != null)) {
290                    logger.debug("found parameter '{}' or '{}' therefore " +
291                            "assuming '{}' operation",
292                            PARAM_QUERY, PARAM_QUERY_TYPE,
293                            SRUOperation.SEARCH_RETRIEVE);
294                    operation = SRUOperation.SEARCH_RETRIEVE;
295                } else if (getParameter(PARAM_SCAN_CLAUSE, false, false) != null) {
296                    logger.debug("found parameter '{}' therefore " +
297                            "assuming '{}' operation",
298                            PARAM_SCAN_CLAUSE, SRUOperation.SCAN);
299                    operation = SRUOperation.SCAN;
300                } else {
301                    logger.debug("no special parameter found therefore " +
302                            "assuming '{}' operation",
303                            SRUOperation.EXPLAIN);
304                    operation = SRUOperation.EXPLAIN;
305                }
306
307                /* record version ... */
308                version = SRUVersion.VERSION_2_0;
309
310                /* do pedantic check for 'operation' parameter */
311                final String op = getParameter(PARAM_OPERATION, false, false);
312                if (op != null) {
313                    /*
314                     * XXX: if operation is searchRetrive and the 'operation'
315                     * parameter is also searchRetrieve, should the server just
316                     * ignore it?
317                     */
318                    if (!(operation == SRUOperation.SEARCH_RETRIEVE) &&
319                            op.equals(OP_SEARCH_RETRIEVE)) {
320                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER,
321                                PARAM_OPERATION, "Parameter \"" +
322                                        PARAM_OPERATION +
323                                        "\" is not valid for SRU version 2.0");
324                    }
325                }
326            } else {
327                logger.debug("handling request as legacy SRU, because found " +
328                        "parameter '{}' in request", PARAM_VERSION);
329            }
330        }
331
332        if (processSruOld) {
333            // parse mandatory operation parameter
334            final String op = getParameter(PARAM_OPERATION, false, false);
335            if (op != null) {
336                if (!op.isEmpty()) {
337                    if (op.equals(OP_EXPLAIN)) {
338                        operation = SRUOperation.EXPLAIN;
339                    } else if (op.equals(OP_SCAN)) {
340                        operation = SRUOperation.SCAN;
341                    } else if (op.equals(OP_SEARCH_RETRIEVE)) {
342                        operation = SRUOperation.SEARCH_RETRIEVE;
343                    } else {
344                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_OPERATION,
345                                null, "Operation \"" + op + "\" is not supported.");
346                    }
347                } else {
348                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_OPERATION,
349                            null, "An empty parameter \"" +
350                                    PARAM_OPERATION +
351                                  "\" is not supported.");
352                }
353
354                // parse and check version
355                version = parseAndCheckVersionParameter(operation);
356            } else {
357                /*
358                 * absent parameter should be interpreted as "explain"
359                 */
360                operation = SRUOperation.EXPLAIN;
361
362                // parse and check version
363                version = parseAndCheckVersionParameter(operation);
364            }
365        }
366
367
368        /*
369         * sanity check
370         */
371        if ((version != null) && (operation != null)) {
372            logger.debug("min = {}, min? = {}, max = {}, max? = {}, version = {}",
373                    minVersion,
374                    version.compareTo(minVersion),
375                    maxVersion,
376                    version.compareTo(maxVersion),
377                    version);
378            if ((version.compareTo(minVersion) >= 0) &&
379                    (version.compareTo(maxVersion) <= 0)) {
380                /*
381                 * FIXME: re-factor to make this nicer ...
382                 */
383                this.version   = version;
384                this.operation = operation;
385                return checkParameters2();
386            } else {
387                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_VERSION,
388                        maxVersion.getVersionString(),
389                        "Version \"" + version.getVersionString() +
390                        "\" is not supported by this endpoint.");
391            }
392        }
393        logger.debug("bailed");
394        return false;
395    }
396
397
398    private boolean checkParameters2() {
399        if (diagnostics == null) {
400            // check mandatory/optional parameters for operation
401            ParameterInfo[] parameter_set;
402            switch (operation) {
403            case EXPLAIN:
404                parameter_set = PARAMETER_SET_EXPLAIN;
405                break;
406            case SCAN:
407                parameter_set = PARAMETER_SET_SCAN;
408                break;
409            case SEARCH_RETRIEVE:
410                parameter_set = PARAMETER_SET_SEARCH_RETRIEVE;
411                break;
412            default:
413                /* actually cannot happen */
414                addDiagnostic(SRUConstants.SRU_GENERAL_SYSTEM_ERROR, null,
415                        "internal error (invalid operation)");
416                return false;
417            }
418
419            /*
420             * keep list of all submitted parameters (except "operation" and
421             * "version"), so we can later warn if an unsupported parameter
422             * was sent (= not all parameters were consumed).
423             */
424            List<String> parameterNames = getParameterNames();
425
426            // check parameters ...
427            for (ParameterInfo parameter : parameter_set) {
428                final String name = parameter.getName(version);
429                if (name == null) {
430                    /*
431                     * this parameter is not supported in the SRU version that
432                     * was used for the request
433                     */
434                    continue;
435                }
436                final String value = getParameter(name, true, true);
437                if (value != null) {
438                    // remove supported parameter from list
439                    parameterNames.remove(name);
440
441                    /*
442                     * if parameter is not supported in this version, skip
443                     * it and create add an diagnostic.
444                     */
445                    if (!parameter.isForVersion(version)) {
446                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER,
447                                name,
448                                "Version " + version.getVersionString() +
449                                        " does not support parameter \"" +
450                                        name + "\".");
451                        continue;
452                    }
453
454                    // validate and parse parameters ...
455                    switch (parameter.getParameter()) {
456                    case RECORD_XML_ESCAPING:
457                        if (value.equals(RECORD_XML_ESCAPING_XML)) {
458                            recordXmlEscaping = SRURecordXmlEscaping.XML;
459                        } else if (value.equals(RECORD_XML_ESCPAING_STRING)) {
460                            recordXmlEscaping = SRURecordXmlEscaping.STRING;
461                        } else {
462                            addDiagnostic(
463                                    SRUConstants.SRU_UNSUPPORTED_XML_ESCAPING_VALUE,
464                                    null, "Record XML escaping \"" + value +
465                                            "\" is not supported.");
466                        }
467                        break;
468                    case RECORD_PACKING:
469                        if (value.equals(RECORD_PACKING_PACKED)) {
470                            recordPacking = SRURecordPacking.PACKED;
471                        } else if (value.equals(RECORD_PACKING_UNPACKED)) {
472                            recordPacking = SRURecordPacking.UNPACKED;
473                        } else {
474                            addDiagnostic(
475                                    SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
476                                    null, "Record packing \"" + value +
477                                            "\" is not supported.");
478                        }
479                        break;
480                    case START_RECORD:
481                        startRecord = parseNumberedParameter(name, value, 1);
482                        break;
483                    case MAXIMUM_RECORDS:
484                        maximumRecords = parseNumberedParameter(name, value, 0);
485                        break;
486                    case RECORD_SCHEMA:
487                        /*
488                         * The parameter recordSchema may contain either schema
489                         * identifier or the short name. If available, set to
490                         * appropriate schema identifier in the request object.
491                         */
492                        SRUServerConfig.SchemaInfo schemaInfo =
493                            config.findSchemaInfo(value);
494                        if (schemaInfo != null) {
495                            recordSchemaIdentifier = schemaInfo.getIdentifier();
496                        } else {
497                            /*
498                             * SRU servers are supposed to raise a non-surrogate
499                             * (fatal) diagnostic in case the record schema is
500                             * not known to the server.
501                             */
502                            addDiagnostic(
503                                    SRUConstants.SRU_UNKNOWN_SCHEMA_FOR_RETRIEVAL,
504                                    value, "Record schema \"" + value +
505                                    "\" is not supported  for retrieval.");
506                        }
507                        break;
508                    case RECORD_XPATH:
509                        recordXPath = value;
510                        break;
511                    case RESULT_SET_TTL:
512                        resultSetTTL = parseNumberedParameter(name, value, 0);
513                        break;
514                    case SORT_KEYS:
515                        sortKeys = value;
516                        break;
517                    case SCAN_CLAUSE:
518                        scanClause = parseScanQueryParameter(name, value);
519                        break;
520                    case RESPONSE_POSITION:
521                        responsePosition = parseNumberedParameter(
522                                name, value, 0);
523                        break;
524                    case MAXIMUM_TERMS:
525                        maximumTerms = parseNumberedParameter(name, value, 0);
526                        break;
527                    case STYLESHEET:
528                        stylesheet = value;
529                        break;
530                    case RENDER_BY:
531                        if (value.equals(RENDER_BY_CLIENT)) {
532                            renderBy = SRURenderBy.CLIENT;
533                        } else if (value.equals(RENDER_BY_SERVER)) {
534                            renderBy = SRURenderBy.SERVER;
535                        } else {
536                            addDiagnostic(
537                                    SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
538                                    null,
539                                    "Value \"" + value + "\" for parameter '" +
540                                            name + "' is not supported.");
541                        }
542                        break;
543                    case RESPONSE_TYPE:
544                        /*
545                         * FIXME: check parameter validity?!
546                         */
547                        responseType = value;
548                        break;
549                    case HTTP_ACCEPT:
550                        /*
551                         * FIXME: check parameter validity?!
552                         */
553                        httpAccept = value;
554                        break;
555                    } // switch
556                } else {
557                    if (parameter.getMandatory()) {
558                        addDiagnostic(
559                                SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
560                                name, "Mandatory parameter \"" + name +
561                                        "\" was not supplied.");
562                    }
563                }
564            } // for
565
566            /*
567             * handle query and queryType
568             */
569            if (operation == SRUOperation.SEARCH_RETRIEVE) {
570                /*
571                 * determine queryType
572                 */
573                String queryType = null;
574                if (version == SRUVersion.VERSION_2_0) {
575                    parameterNames.remove(PARAM_QUERY_TYPE);
576                    String value = getParameter(PARAM_QUERY_TYPE, true, true);
577                    if (value == null) {
578                        queryType = SRUConstants.SRU_QUERY_TYPE_CQL;
579                    } else {
580                        boolean badCharacters = false;
581                        for (int i = 0; i < value.length(); i++) {
582                            final char ch = value.charAt(i);
583                            if (!((ch >= 'a' && ch <= 'z') ||
584                                    (ch >= 'A' && ch <= 'Z') ||
585                                    (ch >= '0' && ch <= '9') ||
586                                    ((i > 0) && ((ch == '-') || ch == '_')))) {
587                                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
588                                        PARAM_QUERY_TYPE, "Value contains illegal characters.");
589                                badCharacters = true;
590                                break;
591                            }
592                        }
593                        if (!badCharacters) {
594                            queryType = value;
595                        }
596                    }
597                } else {
598                    // SRU 1.1 and SRU 1.2 only support CQL
599                    queryType = SRUConstants.SRU_QUERY_TYPE_CQL;
600                }
601
602
603                if (queryType != null) {
604                    logger.debug("looking for query parser for query type '{}'",
605                            queryType);
606                    final SRUQueryParser<?> queryParser =
607                            queryParsers.findQueryParser(queryType);
608                    if (queryParser != null) {
609                        if (queryParser.supportsVersion(version)) {
610                            /*
611                             * gather query parameters (as required by
612                             * QueryParser implementation
613                             */
614                            final Map<String, String> queryParameters =
615                                    new HashMap<String, String>();
616                            List<String> missingParameter = null;
617                            for (String name : queryParser.getQueryParameterNames()) {
618                                parameterNames.remove(name);
619                                final String value =
620                                        getParameter(name, true, false);
621                                if (value != null) {
622                                    queryParameters.put(name, value);
623                                } else {
624                                    if (missingParameter == null) {
625                                        missingParameter = new ArrayList<String>();
626                                    }
627                                    missingParameter.add(name);
628                                }
629                            }
630
631                            if (missingParameter == null) {
632                                logger.debug("parsing query with parser for " +
633                                        "type '{}' and parameters {}",
634                                        queryParser.getQueryType(),
635                                        queryParameters);
636                                query = queryParser.parseQuery(version,
637                                        queryParameters, this);
638                            } else {
639                                logger.debug("parameters {} missing, cannot " +
640                                        "parse query", missingParameter);
641                                for (String name : missingParameter) {
642                                    addDiagnostic(SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
643                                            name,
644                                            "Mandatory parameter '" + name +
645                                                    "' is missing or empty. " +
646                                                    "Required to perform query " +
647                                                    "of query type '" +
648                                                    queryType + "'.");
649                                }
650                            }
651                        } else {
652                            logger.debug("query parser for query type '{}' " +
653                                    "is not supported by SRU version {}",
654                                    queryType, version);
655                            addDiagnostic(SRUConstants.SRU_CANNOT_PROCESS_QUERY_REASON_UNKNOWN, null,
656                                    "Query parser for query type '" +
657                                            queryType + "' is nut supported " +
658                                            "by SRU version '" +
659                                            version.getVersionString() + "'.");
660                        }
661                    } else {
662                        logger.debug("no parser for query type '{}' found", queryType);
663                        addDiagnostic(SRUConstants.SRU_CANNOT_PROCESS_QUERY_REASON_UNKNOWN, null,
664                                "Cannot find query parser for query type '" + queryType + "'.");
665                    }
666                } else {
667                    logger.debug("cannot determine query type");
668                    addDiagnostic(SRUConstants.SRU_CANNOT_PROCESS_QUERY_REASON_UNKNOWN, null,
669                            "Cannot determine query type.");
670                }
671            }
672
673
674            /*
675             *  check if any parameters where not consumed and
676             *  add appropriate warnings
677             */
678            if (!parameterNames.isEmpty()) {
679                for (String name : parameterNames) {
680                    // skip extraRequestData (aka extensions)
681                    if (!name.startsWith(PARAM_EXTENSION_PREFIX)) {
682                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER,
683                                name, "Parameter \"" + name + "\" is not " +
684                                        "supported for this operation.");
685                    }
686                }
687            }
688        }
689
690        /*
691         *  diagnostics == null -> consider as success
692         *  FIXME: this should ne done nicer!
693         */
694        return (diagnostics == null);
695    }
696
697
698    List<SRUDiagnostic> getDiagnostics() {
699        return diagnostics;
700    }
701
702
703    SRUVersion getRawVersion() {
704        return version;
705    }
706
707
708    SRURecordXmlEscaping getRawRecordPacking() {
709        return recordXmlEscaping;
710    }
711
712
713    String getRawRecordSchemaIdentifier() {
714        return getParameter(PARAM_RECORD_SCHEMA, true, false);
715    }
716
717
718    String getRawQuery() {
719        return getParameter(PARAM_QUERY, true, false);
720    }
721
722
723    int getRawMaximumRecords() {
724        return maximumRecords;
725    }
726
727
728    String getRawScanClause() {
729        return getParameter(PARAM_SCAN_CLAUSE, true, false);
730    }
731
732
733    String getRawHttpAccept() {
734        return getParameter(PARAM_HTTP_ACCEPT, true, false);
735    }
736
737
738    int getIndentResponse() {
739        if (config.allowOverrideIndentResponse()) {
740            String s = getExtraRequestData(X_INDENT_RESPONSE);
741            if (s != null) {
742                try {
743                    int x = Integer.parseInt(s);
744                    if ((x > -2) && (x < 9)) {
745                        return x;
746                    }
747                } catch (NumberFormatException e) {
748                    /* IGNORE */
749                }
750            }
751        }
752        return config.getIndentResponse();
753    }
754
755
756    @Override
757    public SRUOperation getOperation() {
758        return operation;
759    }
760
761
762    @Override
763    public SRUVersion getVersion() {
764        return (version != null) ? version : config.getDefaultVersion();
765    }
766
767
768    @Override
769    public boolean isVersion(SRUVersion version) {
770        if (version == null) {
771            throw new NullPointerException("version == null");
772        }
773        return getVersion().equals(version);
774    }
775
776
777    @Override
778    public boolean isVersion(SRUVersion min, SRUVersion max) {
779        if (min == null) {
780            throw new NullPointerException("min == null");
781        }
782        if (max == null) {
783            throw new NullPointerException("max == null");
784        }
785        if (min.getVersionNumber() > max.getVersionNumber()) {
786            throw new IllegalArgumentException("min > max");
787        }
788        final SRUVersion v = getVersion();
789        return (min.getVersionNumber() >= v.getVersionNumber()) &&
790                (v.getVersionNumber() <= max.getVersionNumber());
791    }
792
793
794    @Override
795    public SRURecordXmlEscaping getRecordXmlEscaping() {
796        return (recordXmlEscaping != null)
797                ? recordXmlEscaping
798                : config.getDefaultRecordXmlEscaping();
799    }
800
801
802    @Override
803    public SRURecordPacking getRecordPacking() {
804        return (recordPacking != null)
805                ? recordPacking
806                : config.getDefaultRecordPacking();
807    }
808
809
810    @Override
811    public SRUQuery<?> getQuery() {
812        return query;
813    }
814
815
816    @Override
817    public <T extends SRUQuery<?>> T getQuery(Class<T> type) {
818        if (type == null) {
819            throw new ClassCastException("type == null");
820        }
821        // cast() is null-safe
822        return type.cast(query);
823    }
824
825
826    @Override
827    public boolean isQueryType(String queryType) {
828        if ((queryType != null) && (query != null)) {
829            return query.getQueryType().equals(queryType);
830        }
831        return false;
832    }
833
834
835    @Override
836    public String getQueryType() {
837        if (query != null) {
838            return query.getQueryType();
839        }
840        return null;
841    }
842
843
844    @Override
845    public int getStartRecord() {
846        return startRecord;
847    }
848
849
850    @Override
851    public int getMaximumRecords() {
852        if (config.allowOverrideMaximumRecords() &&
853                (getExtraRequestData(X_UNLIMITED_RESULTSET) != null)) {
854            return -1;
855        }
856        if (maximumRecords == -1) {
857            return config.getNumberOfRecords();
858        } else {
859            if (maximumRecords > config.getMaximumRecords()) {
860                return config.getMaximumRecords();
861            } else {
862                return maximumRecords;
863            }
864        }
865    }
866
867
868    @Override
869    public String getRecordSchemaIdentifier() {
870        return recordSchemaIdentifier;
871    }
872
873
874    @Override
875    public String getRecordXPath() {
876        return recordXPath;
877    }
878
879
880    @Override
881    public int getResultSetTTL() {
882        return resultSetTTL;
883    }
884
885
886    @Override
887    public String getSortKeys() {
888        return sortKeys;
889    }
890
891
892    @Override
893    public CQLNode getScanClause() {
894        return scanClause;
895    }
896
897
898    @Override
899    public int getResponsePosition() {
900        return responsePosition;
901    }
902
903
904    @Override
905    public int getMaximumTerms() {
906        if (config.allowOverrideMaximumTerms() &&
907                (getExtraRequestData(X_UNLIMITED_TERMLIST) != null)) {
908            return -1;
909        }
910        if (maximumTerms == -1) {
911            return config.getNumberOfTerms();
912        } else {
913            if (maximumTerms > config.getMaximumTerms()) {
914                return config.getMaximumTerms();
915            } else {
916                return maximumTerms;
917            }
918        }
919    }
920
921
922    @Override
923    public String getStylesheet() {
924        return stylesheet;
925    }
926
927
928    @Override
929    public SRURenderBy getRenderBy() {
930        return renderBy;
931    }
932
933
934    @Override
935    public String getResponeType() {
936        return responseType;
937    }
938
939
940    @Override
941    public String getHttpAccept() {
942        if (httpAccept != null) {
943            return httpAccept;
944        } else {
945            return request.getHeader("ACCEPT");
946        }
947    }
948
949
950    @Override
951    public String getProtocolScheme() {
952        return request.isSecure() ? "https://" : "http://";
953    }
954
955
956    @Override
957    public List<String> getExtraRequestDataNames() {
958        List<String> result = null;
959        for (Enumeration<?> i = request.getParameterNames(); i.hasMoreElements(); ) {
960            String name = (String) i.nextElement();
961            if (name.startsWith(PARAM_EXTENSION_PREFIX)) {
962                if (result == null) {
963                    result = new ArrayList<String>();
964                }
965                result.add(name);
966            }
967        }
968
969        if (result != null) {
970            return result;
971        } else {
972            return Collections.emptyList();
973        }
974    }
975
976
977    @Override
978    public String getExtraRequestData(String name) {
979        if (name == null) {
980            throw new NullPointerException("name == null");
981        }
982        if (!name.startsWith(PARAM_EXTENSION_PREFIX)) {
983            throw new IllegalArgumentException(
984                    "name must start with \"" + PARAM_EXTENSION_PREFIX + "\"");
985        }
986        return request.getParameter(name);
987    }
988
989
990    @Override
991    public HttpServletRequest getServletRequest() {
992        return request;
993    }
994
995
996    @Override
997    public void addDiagnostic(String uri, String details, String message) {
998        final SRUDiagnostic diagnostic =
999                new SRUDiagnostic(uri, details, message);
1000        if (diagnostics == null) {
1001            diagnostics = new ArrayList<SRUDiagnostic>();
1002        }
1003        diagnostics.add(diagnostic);
1004    }
1005
1006
1007    private List<String> getParameterNames() {
1008        List<String> list = new ArrayList<String>();
1009        for (Enumeration<?> i = request.getParameterNames();
1010                i.hasMoreElements();) {
1011            String name = (String) i.nextElement();
1012            if (!(name.equals(PARAM_OPERATION) || name.equals(PARAM_VERSION))) {
1013                list.add(name);
1014            }
1015        }
1016        return list;
1017    }
1018
1019
1020    private String getParameter(String name, boolean nullify,
1021            boolean diagnosticIfEmpty) {
1022        String s = request.getParameter(name);
1023        if (s != null) {
1024            s = s.trim();
1025            if (nullify && s.isEmpty()) {
1026                s = null;
1027                if (diagnosticIfEmpty) {
1028                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
1029                            name, "An empty parameter \"" + name +
1030                            "\" is not supported.");
1031                }
1032            }
1033        }
1034        return s;
1035    }
1036
1037
1038    private SRUVersion parseAndCheckVersionParameter(SRUOperation operation) {
1039        final String v = getParameter(PARAM_VERSION, true, true);
1040        if (v != null) {
1041            SRUVersion version = null;
1042            if (v.equals(VERSION_1_1)) {
1043                version = SRUVersion.VERSION_1_1;
1044            } else if (v.equals(VERSION_1_2)) {
1045                version = SRUVersion.VERSION_1_2;
1046            } else {
1047                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_VERSION,
1048                        VERSION_1_2, "Version \"" + v +
1049                                "\" is not supported");
1050            }
1051            return version;
1052        } else {
1053            /*
1054             * except for "explain" operation, complain if "version"
1055             * parameter was not supplied.
1056             */
1057            if (operation != SRUOperation.EXPLAIN) {
1058                addDiagnostic(
1059                        SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
1060                        PARAM_VERSION, "Mandatory parameter \"" +
1061                                PARAM_VERSION + "\" was not supplied.");
1062            }
1063
1064            /*
1065             * this is an explain operation, assume default version
1066             */
1067            return config.getDefaultVersion();
1068        }
1069    }
1070
1071
1072    private int parseNumberedParameter(String param, String value,
1073            int minValue) {
1074        int result = -1;
1075
1076        if (value != null) {
1077            try {
1078                result = Integer.parseInt(value);
1079                if (result < minValue) {
1080                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
1081                            param, "Value is less than " + minValue + ".");
1082                }
1083            } catch (NumberFormatException e) {
1084                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
1085                        param, "Invalid number format.");
1086            }
1087        }
1088        return result;
1089    }
1090
1091
1092    private CQLNode parseScanQueryParameter(String param, String value) {
1093        CQLNode result = null;
1094
1095        /*
1096         * XXX: maybe query length against limit and return
1097         * "Too many characters in query" error?
1098         */
1099        try {
1100            int compat = -1;
1101            switch (version) {
1102            case VERSION_1_1:
1103                compat = CQLParser.V1POINT1;
1104                break;
1105            case VERSION_1_2:
1106                /* FALL-THROUGH */
1107            case VERSION_2_0:
1108                compat = CQLParser.V1POINT2;
1109            }
1110            result = new CQLParser(compat).parse(value);
1111        } catch (CQLParseException e) {
1112            addDiagnostic(SRUConstants.SRU_QUERY_SYNTAX_ERROR,
1113                    null, "error parsing query");
1114        } catch (IOException e) {
1115            addDiagnostic(SRUConstants.SRU_QUERY_SYNTAX_ERROR,
1116                    null, "error parsing query");
1117        }
1118        return result;
1119    }
1120
1121} // class SRURequestImpl
Note: See TracBrowser for help on using the repository browser.