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

Last change on this file since 5924 was 5924, checked in by Oliver Schonefeld, 9 years ago
  • re-work diagnostics to support custom diagnostic URIs
  • Property svn:eol-style set to native
File size: 25.3 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_UNLIMITED_TERMLIST    = "x-unlimited-termlist";
57    private static final String X_INDENT_RESPONSE       = "x-indent-response";
58    private static final int DEFAULT_START_RECORD       = 1;
59    private static final int DEFAULT_RESPONSE_POSITION  = 1;
60    private final SRUServerConfig config;
61    private final HttpServletRequest request;
62    private List<SRUDiagnostic> diagnostics;
63    private SRUOperation operation;
64    private SRUVersion version;
65    private SRURecordPacking recordPacking;
66    private CQLNode query;
67    private int startRecord = DEFAULT_START_RECORD;
68    private int maximumRecords = -1;
69    private String recordSchemaIdentifier;
70    private String stylesheet;
71    private String recordXPath;
72    private int resultSetTTL = -1;
73    private String sortKeys;
74    private CQLNode scanClause;
75    private int responsePosition = DEFAULT_RESPONSE_POSITION;
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, 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, 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                        break;
294                    case START_RECORD:
295                        startRecord = parseNumberedParameter(
296                                parameter.getName(), value, 1);
297                        break;
298                    case MAXIMUM_RECORDS:
299                        maximumRecords = parseNumberedParameter(
300                                parameter.getName(), value, 0);
301                        break;
302                    case RECORD_SCHEMA:
303                        /*
304                         * The parameter recordSchema may contain either schema
305                         * identifier or the short name. If available, set to
306                         * appropriate schema identifier in the request object.
307                         */
308                        SRUServerConfig.SchemaInfo schemaInfo =
309                            config.findSchemaInfo(value);
310                        if (schemaInfo != null) {
311                            recordSchemaIdentifier = schemaInfo.getIdentifier();
312                        } else {
313                            /*
314                             * SRU servers are supposed to raise a non-surrogate
315                             * (fatal) diagnostic in case the record schema is
316                             * not known to the server.
317                             */
318                            addDiagnostic(
319                                    SRUConstants.SRU_UNKNOWN_SCHEMA_FOR_RETRIEVAL,
320                                    value, "Record schema \"" + value +
321                                    "\" is not supported  for retrieval.");
322                        }
323                        break;
324                    case RECORD_XPATH:
325                        recordXPath = value;
326                        break;
327                    case RESULT_SET_TTL:
328                        resultSetTTL = parseNumberedParameter(
329                                parameter.getName(), value, 0);
330                        break;
331                    case SORT_KEYS:
332                        sortKeys = value;
333                        break;
334                    case SCAN_CLAUSE:
335                        scanClause = parseCQLParameter(
336                                parameter.getName(), value);
337                        break;
338                    case RESPONSE_POSITION:
339                        responsePosition = parseNumberedParameter(
340                                parameter.getName(), value, 0);
341                        break;
342                    case MAXIMUM_TERMS:
343                        maximumTerms = parseNumberedParameter(
344                                parameter.getName(), value, 0);
345                        break;
346                    case STYLESHEET:
347                        stylesheet = value;
348                        break;
349                    } // switch
350                } else {
351                    if (parameter.getMandatory()) {
352                        addDiagnostic(
353                                SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
354                                parameter.getName(), "Mandatory parameter \"" +
355                                        parameter.getName() +
356                                        "\" was not supplied.");
357                    }
358                }
359            } // for
360
361            /*
362             *  check if any parameters where not consumed and
363             *  add appropriate warnings
364             */
365            if (!parameterNames.isEmpty()) {
366                for (String name : parameterNames) {
367                    // skip extraRequestData (aka extensions)
368                    if (!name.startsWith(PARAM_EXTENSION_PREFIX)) {
369                        addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER,
370                                name, "Parameter \"" + name + "\" is not " +
371                                        "supported for this operation.");
372                    }
373                }
374            }
375        }
376
377        // diagnostics != null -> consider as success
378        return (diagnostics == null);
379    }
380
381
382    List<SRUDiagnostic> getDiagnostics() {
383        return diagnostics;
384    }
385
386
387    SRUVersion getRawVersion() {
388        return version;
389    }
390
391
392    SRURecordPacking getRawRecordPacking() {
393        return recordPacking;
394    }
395
396
397    String getRawRecordSchemaIdentifier() {
398        return getParameter(PARAM_RECORD_SCHEMA, true, false);
399    }
400
401
402    String getRawQuery() {
403        return getParameter(PARAM_QUERY, true, false);
404    }
405
406
407    int getRawMaximumRecords() {
408        return maximumRecords;
409    }
410
411
412    String getRawScanClause() {
413        return getParameter(PARAM_SCAN_CLAUSE, true, false);
414    }
415
416
417    int getIndentResponse() {
418        if (config.allowOverrideIndentResponse()) {
419            String s = getExtraRequestData(X_INDENT_RESPONSE);
420            if (s != null) {
421                try {
422                    int x = Integer.parseInt(s);
423                    if ((x > -2) && (x < 9)) {
424                        return x;
425                    }
426                } catch (NumberFormatException e) {
427                    /* IGNORE */
428                }
429            }
430        }
431        return config.getIndentResponse();
432    }
433
434
435    @Override
436    public SRUOperation getOperation() {
437        return operation;
438    }
439
440
441    @Override
442    public SRUVersion getVersion() {
443        return (version != null) ? version : config.getDefaultVersion();
444    }
445
446
447    @Override
448    public boolean isVersion(SRUVersion version) {
449        if (version == null) {
450            throw new NullPointerException("version == null");
451        }
452        return getVersion().equals(version);
453    }
454
455
456    @Override
457    public boolean isVersion(SRUVersion min, SRUVersion max) {
458        if (min == null) {
459            throw new NullPointerException("min == null");
460        }
461        if (max == null) {
462            throw new NullPointerException("max == null");
463        }
464        if (min.getVersionNumber() > max.getVersionNumber()) {
465            throw new IllegalArgumentException("min > max");
466        }
467        final SRUVersion v = getVersion();
468        return (min.getVersionNumber() >= v.getVersionNumber()) &&
469                (v.getVersionNumber() <= max.getVersionNumber());
470    }
471
472
473    @Override
474    public SRURecordPacking getRecordPacking() {
475        return (recordPacking != null)
476                ? recordPacking
477                : config.getDefaultRecordPacking();
478    }
479
480
481    @Override
482    public CQLNode getQuery() {
483        return query;
484    }
485
486
487    @Override
488    public int getStartRecord() {
489        return startRecord;
490    }
491
492
493    @Override
494    public int getMaximumRecords() {
495        if (config.allowOverrideMaximumRecords() &&
496                (getExtraRequestData(X_UNLIMITED_RESULTSET) != null)) {
497            return -1;
498        }
499        if (maximumRecords == -1) {
500            return config.getNumberOfRecords();
501        } else {
502            if (maximumRecords > config.getMaximumRecords()) {
503                return config.getMaximumRecords();
504            } else {
505                return maximumRecords;
506            }
507        }
508    }
509
510
511    @Override
512    @Deprecated
513    public String getRecordSchemaName() {
514        return getRawRecordSchemaIdentifier();
515    }
516
517
518    @Override
519    public String getRecordSchemaIdentifier() {
520        return recordSchemaIdentifier;
521    }
522
523
524    @Override
525    public String getRecordXPath() {
526        return recordXPath;
527    }
528
529
530    @Override
531    public int getResultSetTTL() {
532        return resultSetTTL;
533    }
534
535
536    @Override
537    public String getSortKeys() {
538        return sortKeys;
539    }
540
541
542    @Override
543    public CQLNode getScanClause() {
544        return scanClause;
545    }
546
547
548    @Override
549    public int getResponsePosition() {
550        return responsePosition;
551    }
552
553
554    @Override
555    public int getMaximumTerms() {
556        if (config.allowOverrideMaximumTerms() &&
557                (getExtraRequestData(X_UNLIMITED_TERMLIST) != null)) {
558            return -1;
559        }
560        if (maximumTerms == -1) {
561            return config.getNumberOfTerms();
562        } else {
563            if (maximumTerms > config.getMaximumTerms()) {
564                return config.getMaximumTerms();
565            } else {
566                return maximumTerms;
567            }
568        }
569    }
570
571
572    @Override
573    public String getStylesheet() {
574        return stylesheet;
575    }
576
577
578    @Override
579    public String getProtocolScheme() {
580        return request.isSecure() ? "https://" : "http://";
581    }
582
583
584    @Override
585    public List<String> getExtraRequestDataNames() {
586        List<String> result = null;
587        for (Enumeration<?> i = request.getParameterNames(); i.hasMoreElements(); ) {
588            String name = (String) i.nextElement();
589            if (name.startsWith(PARAM_EXTENSION_PREFIX)) {
590                if (result == null) {
591                    result = new ArrayList<String>();
592                }
593                result.add(name);
594            }
595        }
596
597        if (result != null) {
598            return result;
599        } else {
600            return Collections.emptyList();
601        }
602    }
603
604
605    @Override
606    public String getExtraRequestData(String name) {
607        if (name == null) {
608            throw new NullPointerException("name == null");
609        }
610        if (!name.startsWith(PARAM_EXTENSION_PREFIX)) {
611            throw new IllegalArgumentException(
612                    "name must start with \"" + PARAM_EXTENSION_PREFIX + "\"");
613        }
614        return request.getParameter(name);
615    }
616
617
618    @Override
619    public HttpServletRequest getServletRequest() {
620        return request;
621    }
622
623
624    @Override
625    public void addDiagnostic(String uri, String details, String message) {
626        final SRUDiagnostic diagnostic =
627                new SRUDiagnostic(uri, details, message);
628        if (diagnostics == null) {
629            diagnostics = new ArrayList<SRUDiagnostic>();
630        }
631        diagnostics.add(diagnostic);
632    }
633
634
635    private List<String> getParameterNames() {
636        List<String> list = new ArrayList<String>();
637        for (Enumeration<?> i = request.getParameterNames();
638                i.hasMoreElements();) {
639            String name = (String) i.nextElement();
640            if (!(name.equals(PARAM_OPERATION) || name.equals(PARAM_VERSION))) {
641                list.add(name);
642            }
643        }
644        return list;
645    }
646
647
648    private String getParameter(String name, boolean nullify,
649            boolean diagnosticIfEmpty) {
650        String s = request.getParameter(name);
651        if (s != null) {
652            s = s.trim();
653            if (nullify && s.isEmpty()) {
654                s = null;
655                if (diagnosticIfEmpty) {
656                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
657                            name, "An empty parameter \"" + PARAM_OPERATION +
658                            "\" is not supported.");
659                }
660            }
661        }
662        return s;
663    }
664
665
666    private void parseAndCheckVersionParameter() {
667        final String v = getParameter(PARAM_VERSION, true, true);
668        if (v != null) {
669            if (v.equals(VERSION_1_1)) {
670                this.version = SRUVersion.VERSION_1_1;
671            } else if (v.equals(VERSION_1_2)) {
672                this.version = SRUVersion.VERSION_1_2;
673            } else {
674                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_VERSION,
675                        VERSION_1_2, "Version \"" + v +
676                                "\" is not supported");
677            }
678        } else {
679            /*
680             * except for "explain" operation, complain if "version"
681             * parameter was not supplied.
682             */
683            if (this.operation != SRUOperation.EXPLAIN) {
684                addDiagnostic(
685                        SRUConstants.SRU_MANDATORY_PARAMETER_NOT_SUPPLIED,
686                        PARAM_VERSION, "Mandatory parameter \"" +
687                                PARAM_VERSION + "\" was not supplied.");
688            }
689
690            /*
691             * this is an explain operation, assume default version
692             */
693            this.version = config.getDefaultVersion();
694        }
695    }
696
697
698    private int parseNumberedParameter(String param, String value,
699            int minValue) {
700        int result = -1;
701
702        if (value != null) {
703            try {
704                result = Integer.parseInt(value);
705                if (result < minValue) {
706                    addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
707                            param, "Value is less than " + minValue + ".");
708                }
709            } catch (NumberFormatException e) {
710                addDiagnostic(SRUConstants.SRU_UNSUPPORTED_PARAMETER_VALUE,
711                        param, "Invalid number format.");
712            }
713        }
714        return result;
715    }
716
717
718    private CQLNode parseCQLParameter(String param, String value) {
719        CQLNode result = null;
720
721        /*
722         * XXX: maybe query length against limit and return
723         * "Too many characters in query" error?
724         */
725        try {
726            int compat = -1;
727            switch (version) {
728            case VERSION_1_1:
729                compat = CQLParser.V1POINT1;
730                break;
731            case VERSION_1_2:
732                compat = CQLParser.V1POINT2;
733            }
734            result = new CQLParser(compat).parse(value);
735        } catch (CQLParseException e) {
736            addDiagnostic(SRUConstants.SRU_QUERY_SYNTAX_ERROR,
737                    null, "error parsing query");
738        } catch (IOException e) {
739            addDiagnostic(SRUConstants.SRU_QUERY_SYNTAX_ERROR,
740                    null, "error parsing query");
741        }
742        return result;
743    }
744
745} // class SRURequestImpl
Note: See TracBrowser for help on using the repository browser.