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

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