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

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