source: SRUServer/tags/SRUServer-1.4.1/src/main/java/eu/clarin/sru/server/SRURequestImpl.java @ 2691

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