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

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