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

Last change on this file since 7269 was 7269, checked in by Oliver Schonefeld, 2 years ago
  • Update copyright
  • Property svn:eol-style set to native
File size: 51.0 KB
Line 
1/**
2 * This software is copyright (c) 2011-2022 by
3 *  - Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
4 * This is free software. You can redistribute it
5 * and/or modify it under the terms described in
6 * the GNU General Public License v3 of which you
7 * should have received a copy. Otherwise you can download
8 * it from
9 *
10 *   http://www.gnu.org/licenses/gpl-3.0.txt
11 *
12 * @copyright Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.sru.server;
18
19import java.io.IOException;
20import java.net.URL;
21import java.util.ArrayList;
22import java.util.Collections;
23import java.util.Iterator;
24import java.util.List;
25import java.util.Map;
26import java.util.StringTokenizer;
27
28import javax.xml.XMLConstants;
29import javax.xml.namespace.NamespaceContext;
30import javax.xml.parsers.DocumentBuilder;
31import javax.xml.parsers.DocumentBuilderFactory;
32import javax.xml.parsers.ParserConfigurationException;
33import javax.xml.transform.Source;
34import javax.xml.transform.dom.DOMSource;
35import javax.xml.validation.Schema;
36import javax.xml.validation.SchemaFactory;
37import javax.xml.validation.Validator;
38import javax.xml.xpath.XPath;
39import javax.xml.xpath.XPathConstants;
40import javax.xml.xpath.XPathException;
41import javax.xml.xpath.XPathExpression;
42import javax.xml.xpath.XPathExpressionException;
43import javax.xml.xpath.XPathFactory;
44
45import org.w3c.dom.Attr;
46import org.w3c.dom.Document;
47import org.w3c.dom.Element;
48import org.w3c.dom.NodeList;
49import org.xml.sax.InputSource;
50import org.xml.sax.SAXException;
51import org.xml.sax.SAXParseException;
52import org.xml.sax.helpers.DefaultHandler;
53
54
55/**
56 * SRU server configuration.
57 *
58 * <p>
59 * Example:
60 * </p>
61 * <pre>
62 * URL url = MySRUServlet.class.getClassLoader()
63 *               .getResource("META-INF/sru-server-config.xml");
64 * if (url == null) {
65 *     throw new ServletException("not found, url == null");
66 * }
67 *
68 * // other runtime configuration, usually obtained from Servlet context
69 * HashMap&lt;String, String&gt; params = new HashMap&lt;String, String&gt;();
70 * params.put(SRUServerConfig.SRU_TRANSPORT, "http");
71 * params.put(SRUServerConfig.SRU_HOST, "127.0.0.1");
72 * params.put(SRUServerConfig.SRU_PORT, "80");
73 * params.put(SRUServerConfig.SRU_DATABASE, "sru-server");
74 *
75 * SRUServerConfig config = SRUServerConfig.parse(params, url);
76 * </pre>
77 *
78 * <p>
79 * The XML configuration file must validate against the "sru-server-config.xsd"
80 * W3C schema bundled with the package and need to have the
81 * <code>http://www.clarin.eu/sru-server/1.0/</code> XML namespace.
82 * </p>
83 */
84public final class SRUServerConfig {
85    /**
86     * Parameter constant for setting the minimum supported SRU version for this
87     * SRU server. Must be smaller or equal to {@link #SRU_SUPPORTED_VERSION_MAX}.
88     * <p>
89     * Valid values: "<code>1.1</code>", "<code>1.2</code>" or "
90     * <code>2.0</code>" (without quotation marks)
91     * </p>
92     */
93    public static final String SRU_SUPPORTED_VERSION_MIN =
94            "eu.clarin.sru.server.sruSupportedVersionMin";
95    /**
96     * Parameter constant for setting the maximum supported SRU version for this
97     * SRU server. Must be larger or equal to {@link #SRU_SUPPORTED_VERSION_MIN}.
98     * <p>
99     * Valid values: "<code>1.1</code>", "<code>1.2</code>" or "
100     * <code>2.0</code>" (without quotation marks)
101     * </p>
102     */
103    public static final String SRU_SUPPORTED_VERSION_MAX =
104            "eu.clarin.sru.server.sruSupportedVersionMax";
105    /**
106     * Parameter constant for setting the default SRU version for this SRU
107     * server, e.g. for an <em>Explain</em> request without explicit version.
108     * Must not me less than {@link #SRU_SUPPORTED_VERSION_MIN} or larger than
109     * {@link #SRU_SUPPORTED_VERSION_MAX}. Defaults to
110     * {@link #SRU_SUPPORTED_VERSION_MAX}.
111     * <p>
112     * Valid values: "<code>1.1</code>", "<code>1.2</code>" or "
113     * <code>2.0</code>" (without quotation marks)
114     * </p>
115     */
116    public static final String SRU_SUPPORTED_VERSION_DEFAULT =
117            "eu.clarin.sru.server.sruSupportedVersionDefault";
118    /**
119     * Parameter constant for setting the namespace URIs for SRU 1.1 and SRU
120     * 1.2.
121     * <p>
122     * Valid values: "<code>loc</code>" for Library Of Congress URI or "
123     * <code>oasis</code>" for OASIS URIs (without quotation marks).
124     * </p>
125     */
126    public static final String SRU_LEGACY_NAMESPACE_MODE =
127            "eu.clarin.sru.server.legacyNamespaceMode";
128    /**
129     * Parameter constant for configuring the transports for this SRU server.
130     * <p>
131     * Valid values: "<code>http</code>", "<code>https</code>" or "
132     * <code>http https</code>" (without quotation marks) <br>
133     * <p>
134     * Used as part of the <em>Explain</em> response.
135     * </p>
136     */
137    public static final String SRU_TRANSPORT =
138            "eu.clarin.sru.server.transport";
139    /**
140     * Parameter constant for configuring the host of this SRU server.
141     * <p>
142     * Valid values: any fully qualified hostname, e.g.
143     * <code>sru.example.org</code> <br>
144     * Used as part of the <em>Explain</em> response.
145     * </p>
146     */
147    public static final String SRU_HOST =
148            "eu.clarin.sru.server.host";
149    /**
150     * Parameter constant for configuring the port number of this SRU server.
151     * <p>
152     * Valid values: number between 1 and 65535 (typically 80 or 8080) <br>
153     * Used as part of the <em>Explain</em> response.
154     * </p>
155     */
156    public static final String SRU_PORT =
157            "eu.clarin.sru.server.port";
158    /**
159     * Parameter constant for configuring the database of this SRU server. This
160     * is usually the path component of the SRU servers URI.
161     * <p>
162     * Valid values: typically the path component if the SRU server URI. <br>
163     * Used as part of the <em>Explain</em> response.
164     * </p>
165     */
166    public static final String SRU_DATABASE =
167            "eu.clarin.sru.server.database";
168    /**
169     * Parameter constant for configuring the <em>default</em> number of records
170     * the SRU server will provide in the response to a <em>searchRetrieve</em>
171     * request if the client does not provide this value.
172     * <p>
173     * Valid values: a integer greater than 0 (default value is 100)
174     * </p>
175     */
176    public static final String SRU_NUMBER_OF_RECORDS =
177            "eu.clarin.sru.server.numberOfRecords";
178    /**
179     * Parameter constant for configuring the <em>maximum</em> number of records
180     * the SRU server will support in the response to a <em>searchRetrieve</em>
181     * request. If a client requests more records, the number will be limited to
182     * this value.
183     * <p>
184     * Valid values: a integer greater than 0 (default value is 250)
185     * </p>
186     */
187    public static final String SRU_MAXIMUM_RECORDS =
188            "eu.clarin.sru.server.maximumRecords";
189    /**
190     * Parameter constant for configuring the <em>default</em> number of terms
191     * the SRU server will provide in the response to a <em>scan</em> request if
192     * the client does not provide this value.
193     * <p>
194     * Valid values: a integer greater than 0 (default value is 250)
195     * </p>
196     */
197    public static final String SRU_NUMBER_OF_TERMS =
198            "eu.clarin.sru.server.numberOfTerms";
199    /**
200     * Parameter constant for configuring the <em>maximum</em> number of terms
201     * the SRU server will support in the response to a <em>scan</em> request.
202     * If a client requests more records, the number will be limited to this
203     * value.
204     * <p>
205     * Valid values: a integer greater than 0 (default value is 500)
206     * </p>
207     */
208    public static final String SRU_MAXIMUM_TERMS =
209            "eu.clarin.sru.server.maximumTerms";
210    /**
211     * Parameter constant for configuring, if the SRU server will echo the
212     * request.
213     * <p>
214     * Valid values: <code>true</code> or <code>false</code>
215     * </p>
216     */
217    public static final String SRU_ECHO_REQUESTS =
218            "eu.clarin.sru.server.echoRequests";
219    /**
220     * Parameter constant for configuring, if the SRU server pretty-print the
221     * XML response. Setting this parameter can be useful for manual debugging
222     * of the XML response, however it is <em>not recommended</em> for
223     * production setups.
224     * <p>
225     * Valid values: any integer greater or equal to <code>-1</code> (default)
226     * and less or equal to <code>8</code>
227     * </p>
228     */
229    public static final String SRU_INDENT_RESPONSE =
230            "eu.clarin.sru.server.indentResponse";
231    /**
232     * Parameter constant for configuring, if the SRU server will allow the
233     * client to override the maximum number of records the server supports.
234     * This parameter is solely intended for debugging and setting it to
235     * <code>true</code> is <em>strongly</em> discouraged for production setups.
236     * <p>
237     * Valid values: <code>true</code> or <code>false</code> (default)
238     * </p>
239     */
240    public static final String SRU_ALLOW_OVERRIDE_MAXIMUM_RECORDS =
241            "eu.clarin.sru.server.allowOverrideMaximumRecords";
242    /**
243     * Parameter constant for configuring, if the SRU server will allow the
244     * client to override the maximum number of terms the server supports. This
245     * parameter is solely intended for debugging and setting it to
246     * <code>true</code> it is <em>strongly</em> discouraged for production
247     * setups.
248     * <p>
249     * Valid values: <code>true</code> or <code>false</code> (default)
250     * </p>
251     */
252    public static final String SRU_ALLOW_OVERRIDE_MAXIMUM_TERMS =
253            "eu.clarin.sru.server.allowOverrideMaximumTerms";
254    /**
255     * Parameter constant for configuring, if the SRU server will allow the
256     * client to override the pretty-printing setting of the server. This
257     * parameter is solely intended for debugging and setting it to
258     * <code>true</code> it is <em>strongly</em> discouraged for production
259     * setups.
260     * <p>
261     * Valid values: <code>true</code> or <code>false</code> (default)
262     * </p>
263     */
264    public static final String SRU_ALLOW_OVERRIDE_INDENT_RESPONSE =
265            "eu.clarin.sru.server.allowOverrideIndentResponse";
266    /**
267     * Parameter constant for configuring the size of response buffer. The
268     * Servlet will buffer up to this amount of data before sending a response
269     * to the client. This value specifies the size of the buffer in bytes.
270     * <p>
271     * Valid values: any positive integer (default 65536)
272     * </p>
273     */
274    public static final String SRU_RESPONSE_BUFFER_SIZE =
275            "eu.clarin.sru.server.responseBufferSize";
276    private static final SRUVersion DEFAULT_SRU_VERSION_MIN =
277            SRUVersion.VERSION_1_1;
278    private static final SRUVersion DEFAULT_SRU_VERSION_MAX =
279            SRUVersion.VERSION_1_2;
280    private static final LegacyNamespaceMode DEFAULT_LEGACY_NAMESPACE_MODE =
281            LegacyNamespaceMode.LOC;
282    private static final int DEFAULT_NUMBER_OF_RECORDS    = 100;
283    private static final int DEFAULT_MAXIMUM_RECORDS      = 250;
284    private static final int DEFAULT_NUMBER_OF_TERMS      = 250;
285    private static final int DEFAULT_MAXIMUM_TERMS        = 500;
286    private static final int DEFAULT_RESPONSE_BUFFER_SIZE = 64 * 1024;
287    private static final String CONFIG_FILE_NAMESPACE_URI =
288            "http://www.clarin.eu/sru-server/1.0/";
289    private static final String CONFIG_FILE_SCHEMA_URL =
290            "META-INF/sru-server-config.xsd";
291    public static enum LegacyNamespaceMode {
292        LOC, OASIS
293    } // enum LegacyNamespaceMode
294
295    public static final class LocalizedString {
296        private final boolean primary;
297        private final String lang;
298        private final String value;
299
300        private LocalizedString(String value, String lang, boolean primary) {
301            this.value   = value;
302            this.lang    = lang;
303            this.primary = primary;
304        }
305
306        private LocalizedString(String value, String lang) {
307            this(value, lang, false);
308        }
309
310        public boolean isPrimary() {
311            return primary;
312        }
313
314        public String getLang() {
315            return lang;
316        }
317
318        public String getValue() {
319            return value;
320        }
321    } // class LocalizedString
322
323    public static final class DatabaseInfo {
324        private final List<LocalizedString> title;
325        private final List<LocalizedString> description;
326        private final List<LocalizedString> author;
327        private final List<LocalizedString> extent;
328        private final List<LocalizedString> history;
329        private final List<LocalizedString> langUsage;
330        private final List<LocalizedString> restrictions;
331        private final List<LocalizedString> subjects;
332        private final List<LocalizedString> links;
333        private final List<LocalizedString> implementation;
334
335        private DatabaseInfo(List<LocalizedString> title,
336                List<LocalizedString> description,
337                List<LocalizedString> author, List<LocalizedString> extent,
338                List<LocalizedString> history, List<LocalizedString> langUsage,
339                List<LocalizedString> restrictions,
340                List<LocalizedString> subjects, List<LocalizedString> links,
341                List<LocalizedString> implementation) {
342            if ((title != null) && !title.isEmpty()) {
343                this.title = Collections.unmodifiableList(title);
344            } else {
345                this.title = null;
346            }
347            if ((description != null) && !description.isEmpty()) {
348                this.description = Collections.unmodifiableList(description);
349            } else {
350                this.description = null;
351            }
352            if ((author != null) && !author.isEmpty()) {
353                this.author = Collections.unmodifiableList(author);
354            } else {
355                this.author = null;
356            }
357            if ((extent != null) && !extent.isEmpty()) {
358                this.extent = Collections.unmodifiableList(extent);
359            } else {
360                this.extent = null;
361            }
362            if ((history != null) && !history.isEmpty()) {
363                this.history = Collections.unmodifiableList(history);
364            } else {
365                this.history = null;
366            }
367            if ((langUsage != null) && !langUsage.isEmpty()) {
368                this.langUsage = Collections.unmodifiableList(langUsage);
369            } else {
370                this.langUsage = null;
371            }
372            if ((restrictions != null) && !restrictions.isEmpty()) {
373                this.restrictions = Collections.unmodifiableList(restrictions);
374            } else {
375                this.restrictions = null;
376            }
377            if ((subjects != null) && !subjects.isEmpty()) {
378                this.subjects = Collections.unmodifiableList(subjects);
379            } else {
380                this.subjects = null;
381            }
382            if ((links != null) && !links.isEmpty()) {
383                this.links = Collections.unmodifiableList(links);
384            } else {
385                this.links = null;
386            }
387            if ((implementation != null) && !implementation.isEmpty()) {
388                this.implementation =
389                        Collections.unmodifiableList(implementation);
390            } else {
391                this.implementation = null;
392            }
393        }
394
395        public List<LocalizedString> getTitle() {
396            return title;
397        }
398
399        public List<LocalizedString> getDescription() {
400            return description;
401        }
402
403        public List<LocalizedString> getAuthor() {
404            return author;
405        }
406
407        public List<LocalizedString> getExtend() {
408            return extent;
409        }
410
411        public List<LocalizedString> getHistory() {
412            return history;
413        }
414
415        public List<LocalizedString> getLangUsage() {
416            return langUsage;
417        }
418
419        public List<LocalizedString> getRestrictions() {
420            return restrictions;
421        }
422
423        public List<LocalizedString> getSubjects() {
424            return subjects;
425        }
426
427        public List<LocalizedString> getLinks() {
428            return links;
429        }
430
431        public List<LocalizedString> getImplementation() {
432            return implementation;
433        }
434    } // class DatabaseInfo
435
436    public static class SchemaInfo {
437        private final String identifier;
438        private final String name;
439        private final String location;
440        private final boolean sort;
441        private final boolean retrieve;
442        private final List<LocalizedString> title;
443
444        private SchemaInfo(String identifier, String name, String location,
445                boolean sort, boolean retieve, List<LocalizedString> title) {
446            this.identifier = identifier;
447            this.name       = name;
448            this.location   = location;
449            this.sort       = sort;
450            this.retrieve   = retieve;
451            if ((title != null) && !title.isEmpty()) {
452                this.title = Collections.unmodifiableList(title);
453            } else {
454                this.title = null;
455            }
456        }
457
458
459        public String getIdentifier() {
460            return identifier;
461        }
462
463
464        public String getName() {
465            return name;
466        }
467
468
469        public String getLocation() {
470            return location;
471        }
472
473
474        public boolean getSort() {
475            return sort;
476        }
477
478
479        public boolean getRetrieve() {
480            return retrieve;
481        }
482
483
484        public List<LocalizedString> getTitle() {
485            return title;
486        }
487    } // class SchemaInfo
488
489
490    public static class IndexInfo {
491        public static class Set {
492            private final String identifier;
493            private final String name;
494            private final List<LocalizedString> title;
495
496            private Set(String identifier, String name, List<LocalizedString> title) {
497                this.identifier = identifier;
498                this.name       = name;
499                if ((title != null) && !title.isEmpty()) {
500                    this.title = Collections.unmodifiableList(title);
501                } else {
502                    this.title = null;
503                }
504            }
505
506            public String getIdentifier() {
507                return identifier;
508            }
509
510            public String getName() {
511                return name;
512            }
513
514            public List<LocalizedString> getTitle() {
515                return title;
516            }
517        } // class IndexInfo.Set
518
519        public static class Index {
520            public static class Map {
521                private final boolean primary;
522                private final String set;
523                private final String name;
524
525                private Map(boolean primary, String set, String name) {
526                    this.primary = primary;
527                    this.set     = set;
528                    this.name    = name;
529                }
530
531                public boolean isPrimary() {
532                    return primary;
533                }
534
535                public String getSet() {
536                    return set;
537                }
538
539                public String getName() {
540                    return name;
541                }
542            } // class IndexInfo.Index.Map
543            private final List<LocalizedString> title;
544            private final boolean can_search;
545            private final boolean can_scan;
546            private final boolean can_sort;
547            private final List<Index.Map> maps;
548
549
550            public Index(List<LocalizedString> title, boolean can_search,
551                    boolean can_scan, boolean can_sort, List<Map> maps) {
552                if ((title != null) && !title.isEmpty()) {
553                    this.title = Collections.unmodifiableList(title);
554                } else {
555                    this.title = null;
556                }
557                this.can_search = can_search;
558                this.can_scan   = can_scan;
559                this.can_sort   = can_sort;
560                this.maps       = maps;
561            }
562
563
564            public List<LocalizedString> getTitle() {
565                return title;
566            }
567
568
569            public boolean canSearch() {
570                return can_search;
571            }
572
573
574            public boolean canScan() {
575                return can_scan;
576            }
577
578
579            public boolean canSort() {
580                return can_sort;
581            }
582
583
584            public List<Index.Map> getMaps() {
585                return maps;
586            }
587
588        } // class Index
589
590        private final List<IndexInfo.Set> sets;
591        private final List<IndexInfo.Index> indexes;
592
593        private IndexInfo(List<IndexInfo.Set> sets, List<IndexInfo.Index> indexes) {
594            if ((sets != null) && !sets.isEmpty()) {
595                this.sets = Collections.unmodifiableList(sets);
596            } else {
597                this.sets = null;
598            }
599            if ((indexes != null) && !indexes.isEmpty()) {
600                this.indexes = Collections.unmodifiableList(indexes);
601            } else {
602                this.indexes = null;
603            }
604        }
605
606
607        public List<IndexInfo.Set> getSets() {
608            return sets;
609        }
610
611
612        public List<IndexInfo.Index> getIndexes() {
613            return indexes;
614        }
615    } // IndexInfo
616
617    private final SRUVersion minVersion;
618    private final SRUVersion maxVersion;
619    private final SRUVersion defaultVersion;
620    private final LegacyNamespaceMode legacyNamespaceMode;
621    private final String transport;
622    private final String host;
623    private final int port;
624    private final String database;
625    private final int numberOfRecords;
626    private final int maximumRecords;
627    private final int numberOfTerms;
628    private final int maximumTerms;
629    private final boolean echoRequests;
630    private final int indentResponse;
631    private final int responseBufferSize;
632    private final boolean allowOverrideMaximumRecords;
633    private final boolean allowOverrideMaximumTerms;
634    private final boolean allowOverrideIndentResponse;
635    private final String baseUrl;
636    private final DatabaseInfo databaseInfo;
637    private final IndexInfo indexInfo;
638    private final List<SchemaInfo> schemaInfo;
639
640
641    private SRUServerConfig(SRUVersion minVersion,
642            SRUVersion maxVersion,
643            SRUVersion defaultVersion,
644            LegacyNamespaceMode legacyNamespaceMode,
645            String transport,
646            String host,
647            int port,
648            String database,
649            int numberOfRecords,
650            int maximumRecords,
651            int numberOfTerms,
652            int maximumTerms,
653            boolean echoRequests,
654            int indentResponse,
655            int responseBufferSize,
656            boolean allowOverrideMaximumRecords,
657            boolean allowOverrideMaximumTerms,
658            boolean allowOverrideIndentResponse,
659            DatabaseInfo databaseinfo,
660            IndexInfo indexInfo,
661            List<SchemaInfo> schemaInfo) {
662        this.minVersion                  = minVersion;
663        this.maxVersion                  = maxVersion;
664        this.defaultVersion              = defaultVersion;
665        this.legacyNamespaceMode         = legacyNamespaceMode;
666        this.transport                   = transport;
667        this.host                        = host;
668        this.port                        = port;
669        this.database                    = database;
670        this.numberOfRecords             = numberOfRecords;
671        this.maximumRecords              = maximumRecords;
672        this.numberOfTerms               = numberOfTerms;
673        this.maximumTerms                = maximumTerms;
674        this.echoRequests                = echoRequests;
675        this.indentResponse              = indentResponse;
676        this.responseBufferSize          = responseBufferSize;
677        this.allowOverrideMaximumRecords = allowOverrideMaximumRecords;
678        this.allowOverrideMaximumTerms   = allowOverrideMaximumTerms;
679        this.allowOverrideIndentResponse = allowOverrideIndentResponse;
680        this.databaseInfo                = databaseinfo;
681        this.indexInfo                   = indexInfo;
682        if ((schemaInfo != null) && !schemaInfo.isEmpty()) {
683            this.schemaInfo = Collections.unmodifiableList(schemaInfo);
684        } else {
685            this.schemaInfo = null;
686        }
687
688        // build baseUrl
689        StringBuilder sb = new StringBuilder();
690        sb.append(host);
691        if (port != 80) {
692            sb.append(":").append(port);
693        }
694        sb.append("/").append(database);
695        this.baseUrl = sb.toString();
696    }
697
698
699    public SRUVersion getMinVersion() {
700        return minVersion;
701    }
702
703
704    public SRUVersion getMaxVersion() {
705        return maxVersion;
706    }
707
708
709    public SRUVersion getDefaultVersion() {
710        return defaultVersion;
711    }
712
713
714    public LegacyNamespaceMode getLegacyNamespaceMode() {
715        return legacyNamespaceMode;
716    }
717
718
719    public SRURecordXmlEscaping getDefaultRecordXmlEscaping() {
720        return SRURecordXmlEscaping.XML;
721    }
722
723
724    public SRURecordPacking getDefaultRecordPacking() {
725        return SRURecordPacking.PACKED;
726    }
727
728
729    public boolean getEchoRequests() {
730        return echoRequests;
731    }
732
733
734    public String getTransports() {
735        return transport;
736    }
737
738
739    public String getHost() {
740        return host;
741    }
742
743
744    public int getPort() {
745        return port;
746    }
747
748
749    public String getDatabase() {
750        return database;
751    }
752
753
754    public String getBaseUrl() {
755        return baseUrl;
756    }
757
758
759    public int getNumberOfRecords() {
760        return numberOfRecords;
761    }
762
763
764    public int getMaximumRecords() {
765        return maximumRecords;
766    }
767
768
769    public int getNumberOfTerms() {
770        return numberOfTerms;
771    }
772
773
774    public int getMaximumTerms() {
775        return maximumTerms;
776    }
777
778
779    public int getIndentResponse() {
780        return indentResponse;
781    }
782
783
784    public boolean allowOverrideMaximumRecords() {
785        return allowOverrideMaximumRecords;
786    }
787
788
789    public boolean allowOverrideMaximumTerms() {
790        return allowOverrideMaximumTerms;
791    }
792
793
794    public boolean allowOverrideIndentResponse() {
795        return allowOverrideIndentResponse;
796    }
797
798
799    public int getResponseBufferSize() {
800        return responseBufferSize;
801    }
802
803
804    public DatabaseInfo getDatabaseInfo() {
805        return databaseInfo;
806    }
807
808
809    public IndexInfo getIndexInfo() {
810        return indexInfo;
811    }
812
813
814    public List<SchemaInfo> getSchemaInfo() {
815        return schemaInfo;
816    }
817
818
819    public String getRecordSchemaIdentifier(String recordSchemaName) {
820        if (recordSchemaName != null) {
821            if ((schemaInfo != null) && !schemaInfo.isEmpty()) {
822                for (SchemaInfo schema : schemaInfo) {
823                    if (schema.getName().equals(recordSchemaName)) {
824                        return schema.getIdentifier();
825                    }
826                }
827            }
828        }
829        return null;
830    }
831
832
833    public String getRecordSchemaName(String schemaIdentifier) {
834        if (schemaIdentifier != null) {
835           if ((schemaInfo != null) && !schemaInfo.isEmpty()) {
836               for (SchemaInfo schema : schemaInfo) {
837                   if (schema.getIdentifier().equals(schemaIdentifier)) {
838                       return schema.getName();
839                   }
840               }
841           }
842        }
843        return null;
844    }
845
846
847    public SchemaInfo findSchemaInfo(String value) {
848        if (value != null) {
849            if ((schemaInfo != null) && !schemaInfo.isEmpty()) {
850                for (SchemaInfo schema : schemaInfo) {
851                    if (schema.getIdentifier().equals(value) ||
852                            schema.getName().equals(value)) {
853                        return schema;
854                    }
855                }
856            }
857        }
858        return null;
859    }
860
861
862    /**
863     * Parse a SRU server XML configuration file and create an configuration
864     * object from it.
865     *
866     * @param params
867     *            additional settings
868     * @param configFile
869     *            an {@link URL} pointing to the XML configuration file
870     * @return a initialized <code>SRUEndpointConfig</code> instance
871     * @throws NullPointerException
872     *             if <em>params</em> or <em>configFile</em> is
873     *             <code>null</code>
874     * @throws SRUConfigException
875     *             if an error occurred
876     */
877    public static SRUServerConfig parse(Map<String, String> params,
878            URL configFile) throws SRUConfigException {
879        if (params == null) {
880            throw new NullPointerException("params == null");
881        }
882        if (configFile == null) {
883            throw new NullPointerException("in == null");
884        }
885        try {
886            URL url = SRUServerConfig.class.getClassLoader()
887                    .getResource(CONFIG_FILE_SCHEMA_URL);
888            if (url == null) {
889                throw new SRUConfigException("cannot open \"" +
890                        CONFIG_FILE_SCHEMA_URL + "\"");
891            }
892            SchemaFactory sfactory =
893                SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
894            Schema schema = sfactory.newSchema(url);
895            DocumentBuilderFactory factory =
896                DocumentBuilderFactory.newInstance();
897            factory.setNamespaceAware(true);
898            factory.setSchema(schema);
899            factory.setIgnoringElementContentWhitespace(true);
900            factory.setIgnoringComments(true);
901            factory.setValidating(false);
902
903            // parse input
904            DocumentBuilder builder = factory.newDocumentBuilder();
905            InputSource input = new InputSource(configFile.openStream());
906            input.setPublicId(CONFIG_FILE_NAMESPACE_URI);
907            input.setSystemId(CONFIG_FILE_NAMESPACE_URI);
908            Document doc = builder.parse(input);
909
910            // validate
911            Source source = new DOMSource(doc);
912            Validator validator = schema.newValidator();
913            validator.setErrorHandler(new DefaultHandler() {
914                @Override
915                public void error(SAXParseException e) throws SAXException {
916                    fatalError(e);
917                }
918
919                @Override
920                public void fatalError(SAXParseException e) throws SAXException {
921                    throw new SAXException(
922                            "error parsing endpoint configuration file", e);
923                }
924            });
925            validator.validate(source);
926
927
928            XPathFactory xfactory = XPathFactory.newInstance();
929            XPath xpath = xfactory.newXPath();
930            xpath.setNamespaceContext(new NamespaceContext() {
931                @Override
932                public Iterator<String> getPrefixes(String namespaceURI) {
933                    throw new UnsupportedOperationException();
934                }
935
936                @Override
937                public String getPrefix(String namespaceURI) {
938                    throw new UnsupportedOperationException();
939                }
940
941                @Override
942                public String getNamespaceURI(String prefix) {
943                    if (prefix == null) {
944                        throw new NullPointerException("prefix == null");
945                    }
946                    if (prefix.equals("sru")) {
947                        return CONFIG_FILE_NAMESPACE_URI;
948                    } else if (prefix.equals(XMLConstants.XML_NS_PREFIX)) {
949                        return XMLConstants.XML_NS_URI;
950                    } else {
951                        return XMLConstants.NULL_NS_URI;
952                    }
953                }
954            });
955
956            DatabaseInfo databaseInfo = buildDatabaseInfo(xpath, doc);
957
958            IndexInfo indexInfo = buildIndexInfo(xpath, doc);
959
960            List<SchemaInfo> schemaInfo = buildSchemaInfo(xpath, doc);
961
962            /*
963             * fetch parameters more parameters (usually passed from Servlet
964             * context)
965             */
966
967            SRUVersion minVersion = parseVersionNumber(params,
968                    SRU_SUPPORTED_VERSION_MIN, false, DEFAULT_SRU_VERSION_MIN);
969
970            SRUVersion maxVersion = parseVersionNumber(params,
971                    SRU_SUPPORTED_VERSION_MAX, false, DEFAULT_SRU_VERSION_MAX);
972            if (maxVersion.compareTo(minVersion) < 0) {
973                throw new SRUConfigException(
974                        "parameter value \"" + SRU_SUPPORTED_VERSION_MAX +
975                                "\" (" + maxVersion.getVersionString() +
976                                ") must be equal or larger than value of parameter \"" +
977                                SRU_SUPPORTED_VERSION_MIN + "\" (" +
978                                minVersion.getVersionString() + ")");
979            }
980
981            SRUVersion defaultVersion = parseVersionNumber(params,
982                    SRU_SUPPORTED_VERSION_DEFAULT, false, maxVersion);
983            if ((defaultVersion.compareTo(minVersion) < 0) ||
984                    (defaultVersion.compareTo(maxVersion) > 0)) {
985                throw new SRUConfigException(
986                        "parameter value \"" + SRU_SUPPORTED_VERSION_DEFAULT +
987                                "\" (" + defaultVersion.getVersionString() +
988                                ") must be between value of parameter \"" +
989                                SRU_SUPPORTED_VERSION_MIN + "\" (" +
990                                minVersion.getVersionString() + ") and \"" +
991                                SRU_SUPPORTED_VERSION_MAX + "\" (" +
992                                maxVersion.getVersionString() + ")");
993            }
994
995            LegacyNamespaceMode legacyNamespaceMode =
996                    DEFAULT_LEGACY_NAMESPACE_MODE;
997            String mode = params.get(SRU_LEGACY_NAMESPACE_MODE);
998            if ((mode != null) && !mode.isEmpty()) {
999                if ("loc".equals(mode)) {
1000                    legacyNamespaceMode = LegacyNamespaceMode.LOC;
1001                } else if ("oasis".equals(mode)) {
1002                    legacyNamespaceMode = LegacyNamespaceMode.OASIS;
1003                } else {
1004                    throw new SRUConfigException(
1005                            "invalid value for parameter \"" +
1006                                    SRU_LEGACY_NAMESPACE_MODE + "\": " + mode);
1007                }
1008            }
1009
1010            String transport = params.get(SRU_TRANSPORT);
1011            if ((transport == null) || transport.isEmpty()) {
1012                throw new SRUConfigException("parameter \"" + SRU_TRANSPORT +
1013                        "\" is mandatory");
1014            } else {
1015                StringBuilder sb = new StringBuilder();
1016                StringTokenizer st = new StringTokenizer(transport);
1017                while (st.hasMoreTokens()) {
1018                    String s = st.nextToken().trim().toLowerCase();
1019                    if (!("http".equals(s) || "https".equals(s))) {
1020                        throw new SRUConfigException(
1021                                "unsupported transport \"" + s + "\"");
1022                    }
1023                    if (sb.length() > 0) {
1024                        sb.append(" ");
1025                    }
1026                    sb.append(s);
1027                } // while
1028                transport = sb.toString();
1029            }
1030
1031            String host = params.get(SRU_HOST);
1032            if ((host == null) || host.isEmpty()) {
1033                throw new SRUConfigException("parameter \"" + SRU_HOST +
1034                        "\" is mandatory");
1035            }
1036
1037            int port = parseNumber(params, SRU_PORT, true, -1, 1, 65535);
1038
1039            String database = params.get(SRU_DATABASE);
1040            if ((database == null) || database.isEmpty()) {
1041                throw new SRUConfigException("parameter \"" + SRU_DATABASE +
1042                        "\" is mandatory");
1043            }
1044
1045            // cleanup: remove leading slashed
1046            while (database.startsWith("/")) {
1047                database = database.substring(1);
1048            }
1049
1050
1051            int numberOfRecords = parseNumber(params, SRU_NUMBER_OF_RECORDS,
1052                    false, DEFAULT_NUMBER_OF_RECORDS, 1, -1);
1053
1054            int maximumRecords = parseNumber(params, SRU_MAXIMUM_RECORDS,
1055                    false, DEFAULT_MAXIMUM_RECORDS, numberOfRecords, -1);
1056
1057            int numberOfTerms = parseNumber(params, SRU_NUMBER_OF_TERMS,
1058                    false, DEFAULT_NUMBER_OF_TERMS, 0, -1);
1059
1060            int maximumTerms = parseNumber(params, SRU_MAXIMUM_TERMS, false,
1061                        DEFAULT_MAXIMUM_TERMS, numberOfTerms, -1);
1062
1063            boolean echoRequests = parseBoolean(params, SRU_ECHO_REQUESTS,
1064                    false, true);
1065
1066            int indentResponse = parseNumber(params, SRU_INDENT_RESPONSE,
1067                    false, -1, -1, 8);
1068
1069            boolean allowOverrideMaximumRecords = parseBoolean(params,
1070                    SRU_ALLOW_OVERRIDE_MAXIMUM_RECORDS, false, false);
1071
1072            boolean allowOverrideMaximumTerms = parseBoolean(params,
1073                    SRU_ALLOW_OVERRIDE_MAXIMUM_TERMS, false, false);
1074
1075            boolean allowOverrideIndentResponse = parseBoolean(params,
1076                    SRU_ALLOW_OVERRIDE_INDENT_RESPONSE, false, false);
1077
1078            int responseBufferSize = parseNumber(params,
1079                    SRU_RESPONSE_BUFFER_SIZE, false,
1080                    DEFAULT_RESPONSE_BUFFER_SIZE, 0, -1);
1081
1082            return new SRUServerConfig(minVersion,
1083                    maxVersion,
1084                    defaultVersion,
1085                    legacyNamespaceMode,
1086                    transport,
1087                    host,
1088                    port,
1089                    database,
1090                    numberOfRecords,
1091                    maximumRecords,
1092                    numberOfTerms,
1093                    maximumTerms,
1094                    echoRequests,
1095                    indentResponse,
1096                    responseBufferSize,
1097                    allowOverrideMaximumRecords,
1098                    allowOverrideMaximumTerms,
1099                    allowOverrideIndentResponse,
1100                    databaseInfo,
1101                    indexInfo,
1102                    schemaInfo);
1103        } catch (IOException e) {
1104            throw new SRUConfigException("error reading configuration file", e);
1105        } catch (XPathException e) {
1106            throw new SRUConfigException("error parsing configuration file", e);
1107        } catch (ParserConfigurationException e) {
1108            throw new SRUConfigException("error parsing configuration file", e);
1109        } catch (SAXException e) {
1110            throw new SRUConfigException("error parsing configuration file", e);
1111        }
1112    }
1113
1114
1115    private static SRUVersion parseVersionNumber(Map<String, String> params,
1116            String name, boolean mandatory, SRUVersion defaultValue)
1117                    throws SRUConfigException {
1118        String value = params.get(name);
1119        if ((value == null) || value.isEmpty()) {
1120            if (mandatory) {
1121                throw new SRUConfigException(
1122                        "parameter \"" + name + "\" is mandatory");
1123            } else {
1124                return defaultValue;
1125            }
1126        } else {
1127            if ("1.1".equals(value)) {
1128                return SRUVersion.VERSION_1_1;
1129            } else if ("1.2".equals(value)) {
1130                return SRUVersion.VERSION_1_2;
1131            } else if ("2.0".equals(value)) {
1132                return SRUVersion.VERSION_2_0;
1133            } else {
1134                throw new SRUConfigException("invalid value for parameter \"" +
1135                        name + "\": " + value);
1136            }
1137        }
1138    }
1139
1140
1141    private static int parseNumber(Map<String, String> params, String name,
1142            boolean mandatory, int defaultValue, int minValue, int maxValue)
1143            throws SRUConfigException {
1144        String value = params.get(name);
1145        if ((value == null) || value.isEmpty()) {
1146            if (mandatory) {
1147                throw new SRUConfigException("parameter \"" + name +
1148                        "\" is mandatory");
1149            } else {
1150                return defaultValue;
1151            }
1152        } else {
1153            try {
1154                int num = Integer.parseInt(value);
1155
1156                // sanity checks
1157                if ((minValue != -1) && (maxValue != -1)) {
1158                    if ((num < minValue) || (num > maxValue)) {
1159                        throw new SRUConfigException("parameter \"" + name +
1160                                "\" must be between " + minValue + " and " +
1161                                maxValue + ": " + num);
1162                    }
1163                } else {
1164                    if ((minValue != -1) && (num < minValue)) {
1165                        throw new SRUConfigException("parameter \"" + name +
1166                                "\" must be larger than " + minValue + ": " +
1167                                num);
1168
1169                    }
1170                    if ((maxValue != -1) && (num > maxValue)) {
1171                        throw new SRUConfigException("parameter \"" + name +
1172                                "\" must be smaller than " + maxValue + ": " +
1173                                num);
1174                    }
1175                }
1176                return num;
1177            } catch (NumberFormatException e) {
1178                throw new SRUConfigException("parameter \"" + name +
1179                        "\" must be nummerical and less than " +
1180                        Integer.MAX_VALUE + ": " + value);
1181            }
1182        }
1183    }
1184
1185
1186    private static boolean parseBoolean(Map<String, String> params,
1187            String name, boolean mandatory, boolean defaultValue)
1188            throws SRUConfigException {
1189        String value = params.get(name);
1190        if ((value == null) || value.isEmpty()) {
1191            if (mandatory) {
1192                throw new SRUConfigException("parameter \"" + name +
1193                        "\" is mandatory");
1194            } else {
1195                return defaultValue;
1196            }
1197        } else {
1198            return Boolean.valueOf(value);
1199        }
1200    }
1201
1202
1203    private static DatabaseInfo buildDatabaseInfo(XPath xpath, Document doc)
1204            throws SRUConfigException, XPathExpressionException {
1205        List<LocalizedString> title = buildList(xpath, doc,
1206                "//sru:databaseInfo/sru:title");
1207        List<LocalizedString> description = buildList(xpath, doc,
1208                "//sru:databaseInfo/sru:description");
1209        List<LocalizedString> author = buildList(xpath, doc,
1210                "//sru:databaseInfo/sru:author");
1211        List<LocalizedString> extent = buildList(xpath, doc,
1212                "//sru:databaseInfo/sru:extent");
1213        List<LocalizedString> history = buildList(xpath, doc,
1214                "//sru:databaseInfo/sru:history");
1215        List<LocalizedString> langUsage = buildList(xpath, doc,
1216                "//sru:databaseInfo/sru:langUsage");
1217        List<LocalizedString> restrictions = buildList(xpath, doc,
1218                "//sru:databaseInfo/sru:restrictions");
1219        List<LocalizedString> subjects = buildList(xpath, doc,
1220                "//sru:databaseInfo/sru:subjects");
1221        List<LocalizedString> links = buildList(xpath, doc,
1222                "//sru:databaseInfo/sru:links");
1223        List<LocalizedString> implementation = buildList(xpath, doc,
1224                "//sru:databaseInfo/sru:implementation");
1225        return new DatabaseInfo(title, description, author, extent, history,
1226                langUsage, restrictions, subjects, links, implementation);
1227    }
1228
1229
1230    private static IndexInfo buildIndexInfo(XPath xpath, Document doc)
1231            throws SRUConfigException, XPathExpressionException {
1232        List<IndexInfo.Set> sets      = null;
1233        List<IndexInfo.Index> indexes = null;
1234
1235        XPathExpression expr = xpath.compile("//sru:indexInfo/sru:set");
1236        NodeList result = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
1237        if (result.getLength() > 0) {
1238            sets = new ArrayList<IndexInfo.Set>(result.getLength());
1239            for (int i = 0; i < result.getLength(); i++) {
1240                Element e = (Element) result.item(i);
1241                String identifier = e.getAttribute("identifier");
1242                String name       = e.getAttribute("name");
1243                if (identifier.isEmpty()) {
1244                    throw new SRUConfigException("attribute 'identifier' may "+
1245                            "on element '/indexInfo/set' may not be empty");
1246                }
1247                if (name.isEmpty()) {
1248                    throw new SRUConfigException("attribute 'name' may on " +
1249                            "element '/indexInfo/set' may not be empty");
1250                }
1251                List<LocalizedString> title =
1252                        fromNodeList(e.getElementsByTagName("title"));
1253                sets.add(new IndexInfo.Set(identifier, name, title));
1254            }
1255        } // sets
1256
1257        expr = xpath.compile("//sru:indexInfo/sru:index");
1258        result = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
1259        if (result.getLength() > 0) {
1260            indexes = new ArrayList<IndexInfo.Index>(result.getLength());
1261            for (int i = 0; i < result.getLength(); i++) {
1262                Element e = (Element) result.item(i);
1263                List<LocalizedString> title =
1264                        fromNodeList(e.getElementsByTagName("title"));
1265                boolean can_search = getBooleanAttr(e, "search", false);
1266                boolean can_scan   = getBooleanAttr(e, "scan", false);
1267                boolean can_sort   = getBooleanAttr(e, "sort", false);
1268                List<IndexInfo.Index.Map> maps = null;
1269                NodeList result2 = e.getElementsByTagName("map");
1270                if ((result2 != null) && (result2.getLength() > 0)) {
1271                    maps = new ArrayList<IndexInfo.Index.Map>(
1272                            result2.getLength());
1273                    boolean foundPrimary = false;
1274                    for (int j = 0; j < result2.getLength(); j++) {
1275                        Element e2 = (Element) result2.item(j);
1276                        boolean primary = getBooleanAttr(e2, "primary", false);
1277                        if (primary) {
1278                            if (foundPrimary) {
1279                                throw new SRUConfigException("only one map " +
1280                                        "may be 'primary' in index");
1281                            }
1282                            foundPrimary = true;
1283                        }
1284                        String set  = null;
1285                        String name = null;
1286                        NodeList result3 = e2.getElementsByTagName("name");
1287                        if ((result3 != null) && (result3.getLength() > 0)) {
1288                            Element e3 = (Element) result3.item(0);
1289                            set  = e3.getAttribute("set");
1290                            name = e3.getTextContent();
1291                            if (set.isEmpty()) {
1292                                throw new SRUConfigException("attribute 'set'" +
1293                                        " on element '/indexInfo/index/map/" +
1294                                        "name' may not be empty");
1295                            }
1296                            if ((name == null) || name.isEmpty()) {
1297                                throw new SRUConfigException("element " +
1298                                        "'/indexInfo/index/map/name' may not " +
1299                                        "be empty");
1300                            }
1301                        }
1302                        maps.add(new IndexInfo.Index.Map(primary, set, name));
1303                    }
1304                }
1305                indexes.add(new IndexInfo.Index(title, can_search, can_scan,
1306                        can_sort, maps));
1307            } // for
1308
1309            // sanity check (/index/map/name/@set exists in any set/@name)
1310            if (sets != null) {
1311                for (IndexInfo.Index index : indexes) {
1312                    if (index.getMaps() != null) {
1313                        for (IndexInfo.Index.Map maps : index.getMaps()) {
1314                            if (findSetByName(sets, maps.getSet()) == null) {
1315                                throw new SRUConfigException("/index/map/" +
1316                                        "name refers to nonexitsing set (" +
1317                                        maps.getSet() + ")");
1318                            }
1319                        }
1320                    }
1321                }
1322            }
1323        } // if
1324        return new IndexInfo(sets, indexes);
1325    }
1326
1327
1328    private static List<SchemaInfo> buildSchemaInfo(XPath xpath, Document doc)
1329            throws SRUConfigException, XPathExpressionException {
1330        List<SchemaInfo> schemaInfos = null;
1331        XPathExpression expr = xpath.compile("//sru:schemaInfo/sru:schema");
1332        NodeList result = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
1333        if (result.getLength() > 0) {
1334            schemaInfos = new ArrayList<SchemaInfo>(result.getLength());
1335            for (int i = 0; i < result.getLength(); i++) {
1336                Element e = (Element) result.item(i);
1337                String identifier = e.getAttribute("identifier");
1338                String name       = e.getAttribute("name");
1339                String location   = e.getAttribute("location");
1340                if ((location != null) && location.isEmpty()) {
1341                    location = null;
1342                }
1343                boolean sort     = getBooleanAttr(e, "sort", false);
1344                boolean retrieve = getBooleanAttr(e, "retrieve", true);
1345                List<LocalizedString> title =
1346                        fromNodeList(e.getElementsByTagName("title"));
1347                schemaInfos.add(new SchemaInfo(identifier, name, location,
1348                        sort, retrieve, title));
1349            }
1350        }
1351        return schemaInfos;
1352    }
1353
1354
1355    private static IndexInfo.Set findSetByName(List<IndexInfo.Set> sets,
1356            String name) {
1357        for (IndexInfo.Set set : sets) {
1358            if (set.getName().equals(name)) {
1359                return set;
1360            }
1361        }
1362        return null;
1363    }
1364
1365
1366    private static List<LocalizedString> buildList(XPath xpath, Document doc,
1367            String expression) throws SRUConfigException,
1368            XPathExpressionException {
1369        XPathExpression expr = xpath.compile(expression);
1370        NodeList result = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
1371        return fromNodeList(result);
1372    }
1373
1374
1375    private static List<LocalizedString> fromNodeList(NodeList nodes)
1376            throws SRUConfigException {
1377        List<LocalizedString> list = null;
1378        if (nodes.getLength() > 0) {
1379            list = new ArrayList<LocalizedString>(nodes.getLength());
1380            boolean foundPrimary = false;
1381            for (int i = 0; i < nodes.getLength(); i++) {
1382                Element e = (Element) nodes.item(i);
1383                boolean primary = getBooleanAttr(e, "primary", false);
1384                if (primary) {
1385                    if (foundPrimary) {
1386                        throw new SRUConfigException("list may only contain "
1387                                + "one element as primary");
1388                    }
1389                    foundPrimary = true;
1390                }
1391                list.add(new LocalizedString(e.getTextContent(),
1392                        e.getAttributeNS(XMLConstants.XML_NS_URI, "lang"),
1393                        primary));
1394            }
1395        }
1396        return list;
1397    }
1398
1399
1400    private static boolean getBooleanAttr(Element e, String localName,
1401            boolean defaultValue) {
1402        boolean result = defaultValue;
1403        Attr attr = e.getAttributeNode(localName);
1404        if ((attr != null) && attr.getSpecified()) {
1405            result = Boolean.valueOf(attr.getValue());
1406        }
1407        return result;
1408    }
1409
1410} // class SRUServerConfig
Note: See TracBrowser for help on using the repository browser.