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

Last change on this file since 2623 was 2623, checked in by oschonef, 11 years ago
  • update copyright
  • fix some whitespace
  • Property svn:eol-style set to native
File size: 24.7 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.List;
24
25import javax.servlet.http.HttpServletRequest;
26
27import org.z3950.zing.cql.CQLNode;
28import org.z3950.zing.cql.CQLParseException;
29import org.z3950.zing.cql.CQLParser;
30
31
32final class SRURequestImpl implements SRURequest, SRUDiagnosticList {
33    private static final String PARAM_OPERATION         = "operation";
34    private static final String PARAM_VERSION           = "version";
35    private static final String PARAM_RECORD_PACKING    = "recordPacking";
36    private static final String PARAM_STYLESHEET        = "stylesheet";
37    private static final String PARAM_QUERY             = "query";
38    private static final String PARAM_START_RECORD      = "startRecord";
39    private static final String PARAM_MAXIMUM_RECORDS   = "maximumRecords";
40    private static final String PARAM_RECORD_SCHEMA     = "recordSchema";
41    private static final String PARAM_RECORD_X_PATH     = "recordXPath";
42    private static final String PARAM_RESULT_SET_TTL    = "resultSetTTL";
43    private static final String PARAM_SORT_KEYS         = "sortKeys";
44    private static final String PARAM_SCAN_CLAUSE       = "scanClause";
45    private static final String PARAM_RESPONSE_POSITION = "responsePosition";
46    private static final String PARAM_MAXIMUM_TERMS     = "maximumTerms";
47    private static final String OP_EXPLAIN              = "explain";
48    private static final String OP_SCAN                 = "scan";
49    private static final String OP_SEARCH_RETRIEVE      = "searchRetrieve";
50    private static final String VERSION_1_1             = "1.1";
51    private static final String VERSION_1_2             = "1.2";
52    private static final String RECORD_PACKING_XML      = "xml";
53    private static final String RECORD_PACKING_STRING   = "string";
54    private static final String PARAM_EXTENSION_PREFIX  = "x-";
55    private static final String X_UNLIMITED_RESULTSET   = "x-unlimited-resultset";
56    private static final String X_INDENT_RESPONSE       = "x-indent-response";
57    private final SRUServerConfig config;
58    private final HttpServletRequest request;
59    private List<SRUDiagnostic> diagnostics;
60    private SRUOperation operation;
61    private SRUVersion version;
62    private SRURecordPacking recordPacking;
63    private CQLNode query;
64    private String rawQuery;
65    private int startRecord    = -1;
66    private int maximumRecords = -1;
67    private String recordSchemaName;
68    private String recordSchemaIdentifier;
69    private String stylesheet;
70    private String recordXPath;
71    private int resultSetTTL   = -1;
72    private String sortKeys;
73    private CQLNode scanClause;
74    private String rawScanClause;
75    private int responsePosition = -1;
76    private int maximumTerms = -1;
77
78    private static enum Parameter {
79        RECORD_PACKING,
80        QUERY,
81        START_RECORD,
82        MAXIMUM_RECORDS,
83        RECORD_SCHEMA,
84        RECORD_XPATH,
85        RESULT_SET_TTL,
86        SORT_KEYS,
87        SCAN_CLAUSE,
88        RESPONSE_POSITION,
89        MAXIMUM_TERMS,
90        STYLESHEET
91    }
92
93    private static final class ParameterInfo {
94        private final Parameter parameter;
95        private final SRUVersion min;
96        private final SRUVersion max;
97        private final boolean mandatory;
98
99        private ParameterInfo(Parameter name, boolean mandatory,
100                SRUVersion min, SRUVersion max) {
101            this.parameter = name;
102            this.mandatory = mandatory;
103            this.min       = min;
104            this.max       = max;
105        }
106
107        public Parameter getParameter() {
108            return parameter;
109        }
110
111        public String getName() {
112            switch (parameter) {
113            case RECORD_PACKING:
114                return PARAM_RECORD_PACKING;
115            case QUERY:
116                return PARAM_QUERY;
117            case START_RECORD:
118                return PARAM_START_RECORD;
119            case MAXIMUM_RECORDS:
120                return PARAM_MAXIMUM_RECORDS;
121            case RECORD_SCHEMA:
122                return PARAM_RECORD_SCHEMA;
123            case RECORD_XPATH:
124                return PARAM_RECORD_X_PATH;
125            case RESULT_SET_TTL:
126                return PARAM_RESULT_SET_TTL;
127            case SORT_KEYS:
128                return PARAM_SORT_KEYS;
129            case SCAN_CLAUSE:
130                return PARAM_SCAN_CLAUSE;
131            case RESPONSE_POSITION:
132                return PARAM_RESPONSE_POSITION;
133            case MAXIMUM_TERMS:
134                return PARAM_MAXIMUM_TERMS;
135            case STYLESHEET:
136                return PARAM_STYLESHEET;
137            default:
138                throw new InternalError();
139            } // switch
140        }
141
142        public boolean getMandatory() {
143            return mandatory;
144        }
145
146        public boolean isForVersion(SRUVersion version) {
147            return (min.getVersionNumber() <= version.getVersionNumber()) &&
148                    (version.getVersionNumber() <= max.getVersionNumber());
149        }
150    } // class ParameterInfo
151
152    private static final ParameterInfo[] PARAMS_EXPLAIN = {
153        new ParameterInfo(Parameter.RECORD_PACKING, false,
154                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
155        new ParameterInfo(Parameter.STYLESHEET, false,
156                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2)
157    };
158    private static final ParameterInfo[] PARAMS_SCAN = {
159        new ParameterInfo(Parameter.SCAN_CLAUSE, true,
160                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
161        new ParameterInfo(Parameter.RESPONSE_POSITION, false,
162                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
163        new ParameterInfo(Parameter.MAXIMUM_TERMS, false,
164                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
165        new ParameterInfo(Parameter.STYLESHEET, false,
166                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2)
167
168    };
169    private static final ParameterInfo[] PARAMS_SEARCH_RETRIEVE = {
170        new ParameterInfo(Parameter.QUERY, true,
171                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
172        new ParameterInfo(Parameter.START_RECORD, false,
173                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
174        new ParameterInfo(Parameter.MAXIMUM_RECORDS, false,
175                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
176        new ParameterInfo(Parameter.RECORD_PACKING, false,
177                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
178        new ParameterInfo(Parameter.RECORD_SCHEMA, false,
179                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
180        new ParameterInfo(Parameter.RECORD_XPATH, false,
181                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_1),
182        new ParameterInfo(Parameter.RESULT_SET_TTL, false,
183                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2),
184        new ParameterInfo(Parameter.SORT_KEYS, false,
185                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_1),
186        new ParameterInfo(Parameter.STYLESHEET, false,
187                SRUVersion.VERSION_1_1, SRUVersion.VERSION_1_2)
188    };
189
190
191    SRURequestImpl(SRUServerConfig config, HttpServletRequest request) {
192        this.config        = config;
193        this.request       = request;
194    }
195
196
197    boolean checkParameters() {
198        // parse mandatory operation parameter
199        final String op = getParameter(PARAM_OPERATION, false);
200        if (op != null) {
201            if (!op.isEmpty()) {
202                if (op.equals(OP_EXPLAIN)) {
203                    this.operation = SRUOperation.EXPLAIN;
204                } else if (op.equals(OP_SCAN)) {
205                    this.operation = SRUOperation.SCAN;
206                } else if (op.equals(OP_SEARCH_RETRIEVE)) {
207                    this.operation = SRUOperation.SEARCH_RETRIEVE;
208                } else {
209                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_OPERATION, null,
210                            "Operation \"" + op + "\" is not supported.");
211                }
212            } else {
213                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_OPERATION, null,
214                        "An empty parameter \"" + PARAM_OPERATION +
215                                "\" is not supported.");
216            }
217
218            // parse and check version
219            parseAndCheckVersionParameter();
220        } else {
221            /*
222             * absent parameter should be interpreted as "explain"
223             */
224            this.operation = SRUOperation.EXPLAIN;
225
226            // parse and check version
227            parseAndCheckVersionParameter();
228        }
229
230        if (diagnostics == null) {
231            // check mandatory/optional parameters for operation
232            ParameterInfo[] parameters;
233            switch (operation) {
234            case EXPLAIN:
235                parameters = PARAMS_EXPLAIN;
236                break;
237            case SCAN:
238                parameters = PARAMS_SCAN;
239                break;
240            case SEARCH_RETRIEVE:
241                parameters = PARAMS_SEARCH_RETRIEVE;
242                break;
243            default:
244                /* actually cannot happen */
245                addDiagnostic(SRUConstants.SRU_GENERAL_SYSTEM_ERROR, null,
246                        "internal error (invalid operation)");
247                return false;
248            }
249
250            /*
251             * keep list of all submitted parameters (except "operation" and
252             * "version"), so we can later warn if an unsupported parameter
253             * was sent (= not all parameters were consumed).
254             */
255            List<String> parameterNames = getParameterNames();
256
257            // check parameters ...
258            for (ParameterInfo parameter : parameters) {
259                String value = getParameter(parameter.getName(), true);
260                if (value != null) {
261                    // remove supported parameter from list
262                    parameterNames.remove(parameter.getName());
263
264                    /*
265                     * if parameter is not supported in this version, skip
266                     * it and create add an diagnostic.
267                     */
268                    if (!parameter.isForVersion(version)) {
269                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER,
270                                parameter.getName(),
271                                "Version " + version.getVersionString() +
272                                        " does not support parameter \"" +
273                                        parameter.getName() + "\".");
274                        continue;
275                    }
276
277                    // validate and parse parameters ...
278                    switch (parameter.getParameter()) {
279                    case RECORD_PACKING:
280                        if (value.endsWith(RECORD_PACKING_XML)) {
281                            recordPacking = SRURecordPacking.XML;
282                        } else if (value.equals(RECORD_PACKING_STRING)) {
283                            recordPacking = SRURecordPacking.STRING;
284                        } else {
285                            addDiagnostic(
286                                    SRUConstants.SRU_UNSUPPORTED_RECORD_PACKING,
287                                    null, "Record packing \"" + value +
288                                            "\" is not supported.");
289                        }
290                        break;
291                    case QUERY:
292                        query = parseCQLParameter(parameter.getName(), value);
293                        if (query != null) {
294                            rawQuery = value;
295                        }
296                        break;
297                    case START_RECORD:
298                        startRecord = parseNumberedParameter(
299                                parameter.getName(), value, 1);
300                        break;
301                    case MAXIMUM_RECORDS:
302                        maximumRecords = parseNumberedParameter(
303                                parameter.getName(), value, 0);
304                        break;
305                    case RECORD_SCHEMA:
306                        if (value != null) {
307                            /*
308                             * If the recordSchema is supplied, check if it is
309                             * supported by this endpoint. If not, raise
310                             * an error. recoedSchema may contain either
311                             * schema identifier or the short name.
312                             */
313                            SRUServerConfig.SchemaInfo schemaInfo =
314                                    config.findSchemaInfo(value);
315                            if (schemaInfo != null) {
316                                recordSchemaIdentifier =
317                                        schemaInfo.getIdentifier();
318                                recordSchemaName =
319                                        schemaInfo.getName();
320                            } else {
321                                addDiagnostic(
322                                        SRUConstants.SRU_UNKNOWN_SCHEMA_FOR_RETRIEVAL,
323                                        value,
324                                        "Record schema \"" + value +
325                                        "\" is not supported for retrieval.");
326                            }
327                        }
328                        break;
329                    case RECORD_XPATH:
330                        recordXPath = value;
331                        break;
332                    case RESULT_SET_TTL:
333                        resultSetTTL = parseNumberedParameter(
334                                parameter.getName(), value, 0);
335                        break;
336                    case SORT_KEYS:
337                        sortKeys = value;
338                        break;
339                    case SCAN_CLAUSE:
340                        scanClause = parseCQLParameter(
341                                parameter.getName(), value);
342                        if (scanClause != null) {
343                            rawScanClause = value;
344                        }
345                        break;
346                    case RESPONSE_POSITION:
347                        responsePosition = parseNumberedParameter(
348                                parameter.getName(), value, 0);
349                        break;
350                    case MAXIMUM_TERMS:
351                        maximumTerms = parseNumberedParameter(
352                                parameter.getName(), value, 0);
353                        break;
354                    case STYLESHEET:
355                        stylesheet = value;
356                        break;
357                    } // switch
358                } else {
359                    if (parameter.getMandatory()) {
360                        addDiagnostic(
361                                SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
362                                parameter.getName(), "Mandatory parameter \"" +
363                                        parameter.getName() +
364                                        "\" was not supplied.");
365                    }
366                }
367            } // for
368
369            /*
370             *  check if any parameters where not consumed and
371             *  add appropriate warnings
372             */
373            if (!parameterNames.isEmpty()) {
374                for (String name : parameterNames) {
375                    // skip extraRequestData (aka extensions)
376                    if (!name.startsWith(PARAM_EXTENSION_PREFIX)) {
377                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER,
378                                name, "Parameter \"" + name + "\" is not " +
379                                        "supported for this operation.");
380                    }
381                }
382            }
383        }
384
385        // diagnostics != null -> consider as sucesss
386        return (diagnostics == null);
387    }
388
389
390    List<SRUDiagnostic> getDiagnostics() {
391        return diagnostics;
392    }
393
394
395    SRUVersion getRawVersion() {
396        return version;
397    }
398
399
400    SRURecordPacking getRawRecordPacking() {
401        return recordPacking;
402    }
403
404
405    String getRawQuery() {
406        return rawQuery;
407    }
408
409
410    int getRawMaximumRecords() {
411        return maximumRecords;
412    }
413
414
415    String getRawScanClause() {
416        return rawScanClause;
417    }
418
419
420    int getIndentResponse() {
421        if (config.allowOverrideIndentResponse()) {
422            String s = getExtraRequestData(X_INDENT_RESPONSE);
423            if (s != null) {
424                try {
425                    int x = Integer.parseInt(s);
426                    if ((x > -2) && (x < 9)) {
427                        return x;
428                    }
429                } catch (NumberFormatException e) {
430                    /* IGNORE */
431                }
432            }
433        }
434        return config.getIndentResponse();
435    }
436
437
438    @Override
439    public SRUOperation getOperation() {
440        return operation;
441    }
442
443
444    @Override
445    public SRUVersion getVersion() {
446        return (version != null) ? version : config.getDefaultVersion();
447    }
448
449
450    @Override
451    public boolean isVersion(SRUVersion version) {
452        if (version == null) {
453            throw new NullPointerException("version == null");
454        }
455        return getVersion().equals(version);
456    }
457
458
459    @Override
460    public boolean isVersion(SRUVersion min, SRUVersion max) {
461        if (min == null) {
462            throw new NullPointerException("min == null");
463        }
464        if (max == null) {
465            throw new NullPointerException("max == null");
466        }
467        if (min.getVersionNumber() > max.getVersionNumber()) {
468            throw new IllegalArgumentException("min > max");
469        }
470        final SRUVersion v = getVersion();
471        return (min.getVersionNumber() >= v.getVersionNumber()) &&
472                (v.getVersionNumber() <= max.getVersionNumber());
473    }
474
475
476    @Override
477    public SRURecordPacking getRecordPacking() {
478        return (recordPacking != null)
479                ? recordPacking
480                : config.getDeaultRecordPacking();
481    }
482
483
484    @Override
485    public CQLNode getQuery() {
486        return query;
487    }
488
489
490    @Override
491    public int getStartRecord() {
492        return startRecord;
493    }
494
495
496    @Override
497    public int getMaximumRecords() {
498        if (config.allowOverrideIndentResponse() &&
499                (getExtraRequestData(X_UNLIMITED_RESULTSET) != null)) {
500            return -1;
501        }
502        if (maximumRecords == -1) {
503            return config.getNumberOfRecords();
504        } else {
505            if (maximumRecords > config.getMaximumRecords()) {
506                return config.getMaximumRecords();
507            } else {
508                return maximumRecords;
509            }
510        }
511    }
512
513
514    @Override
515    public String getRecordSchemaName() {
516        return recordSchemaName;
517    }
518
519
520    @Override
521    public String getRecordSchemaIdentifier() {
522        return recordSchemaIdentifier;
523    }
524
525
526    @Override
527    public String getRecordXPath() {
528        return recordXPath;
529    }
530
531
532    @Override
533    public int getResultSetTTL() {
534        return resultSetTTL;
535    }
536
537
538    @Override
539    public String getSortKeys() {
540        return sortKeys;
541    }
542
543
544    @Override
545    public CQLNode getScanClause() {
546        return scanClause;
547    }
548
549
550    @Override
551    public int getResponsePosition() {
552        return responsePosition;
553    }
554
555
556    @Override
557    public int getMaximumTerms() {
558        return maximumTerms;
559    }
560
561
562    @Override
563    public String getStylesheet() {
564        return stylesheet;
565    }
566
567
568    @Override
569    public String getProtocolScheme() {
570        return request.isSecure() ? "https://" : "http://";
571    }
572
573
574    @Override
575    public List<String> getExtraRequestDataNames() {
576        List<String> result = null;
577        for (Enumeration<?> i = request.getParameterNames(); i.hasMoreElements(); ) {
578            String name = (String) i.nextElement();
579            if (name.startsWith(PARAM_EXTENSION_PREFIX)) {
580                if (result == null) {
581                    result = new ArrayList<String>();
582                }
583                result.add(name);
584            }
585        }
586
587        if (result != null) {
588            return result;
589        } else {
590            return Collections.emptyList();
591        }
592    }
593
594
595    @Override
596    public String getExtraRequestData(String name) {
597        if (name == null) {
598            throw new NullPointerException("name == null");
599        }
600        if (!name.startsWith(PARAM_EXTENSION_PREFIX)) {
601            throw new IllegalArgumentException(
602                    "name must start with \"" + PARAM_EXTENSION_PREFIX + "\"");
603        }
604        return request.getParameter(name);
605    }
606
607
608    @Override
609    public HttpServletRequest getServletRequest() {
610        return request;
611    }
612
613
614    @Override
615    public void addDiagnostic(int code, String details, String message) {
616        if (diagnostics == null) {
617            diagnostics = new ArrayList<SRUDiagnostic>();
618        }
619        diagnostics.add(new SRUDiagnostic(code, details, message));
620    }
621
622
623    private List<String> getParameterNames() {
624        List<String> list = new ArrayList<String>();
625        for (Enumeration<?> i = request.getParameterNames();
626                i.hasMoreElements();) {
627            String name = (String) i.nextElement();
628            if (!(name.equals(PARAM_OPERATION) || name.equals(PARAM_VERSION))) {
629                list.add(name);
630            }
631        }
632        return list;
633    }
634
635
636    private String getParameter(String name, boolean nullify) {
637        String s = request.getParameter(name);
638        if (s != null) {
639            s = s.trim();
640            if (nullify && s.isEmpty()) {
641                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
642                        name, "An empty parameter \"" + PARAM_OPERATION +
643                                "\" is not supported.");
644                s = null;
645            }
646        }
647        return s;
648    }
649
650
651    private void parseAndCheckVersionParameter() {
652        final String v = getParameter(PARAM_VERSION, true);
653        if (v != null) {
654            if (v.equals(VERSION_1_1)) {
655                this.version = SRUVersion.VERSION_1_1;
656            } else if (v.equals(VERSION_1_2)) {
657                this.version = SRUVersion.VERSION_1_2;
658            } else {
659                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_VERSION,
660                        VERSION_1_2, "Version \"" + v +
661                                "\" is not supported");
662            }
663        } else {
664            /*
665             * except for "explain" operation, complain if "version"
666             * parameter was not supplied.
667             */
668            if (this.operation != SRUOperation.EXPLAIN) {
669                addDiagnostic(
670                        SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
671                        PARAM_VERSION, "Mandatory parameter \"" +
672                                PARAM_VERSION + "\" was not supplied.");
673            }
674
675            /*
676             * this is an explain operation, assume default version
677             */
678            this.version = config.getDefaultVersion();
679        }
680    }
681
682
683    private int parseNumberedParameter(String param, String value,
684            int minValue) {
685        int result = -1;
686
687        if (value != null) {
688            try {
689                result = Integer.parseInt(value);
690                if (result < minValue) {
691                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
692                            param, "Value is less than " + minValue + ".");
693                }
694            } catch (NumberFormatException e) {
695                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
696                        param, "Invalid number format.");
697            }
698        }
699        return result;
700    }
701
702
703    private CQLNode parseCQLParameter(String param, String value) {
704        CQLNode result = null;
705
706        /*
707         * XXX: maybe query length against limit and return
708         * "Too many characters in query" error?
709         */
710        try {
711            int compat = -1;
712            switch (version) {
713            case VERSION_1_1:
714                compat = CQLParser.V1POINT1;
715                break;
716            case VERSION_1_2:
717                compat = CQLParser.V1POINT2;
718            }
719            result = new CQLParser(compat).parse(value);
720        } catch (CQLParseException e) {
721            addDiagnostic(SRUConstants.SRU_QUERY_SYNTAX_ERROR,
722                    null, "error parsing query");
723        } catch (IOException e) {
724            addDiagnostic(SRUConstants.SRU_QUERY_SYNTAX_ERROR,
725                    null, "error parsing query");
726        }
727        return result;
728    }
729
730} // class SRURequestImpl
Note: See TracBrowser for help on using the repository browser.