source: SRUServer/tags/SRUServer-1.2.0/src/main/java/eu/clarin/sru/server/SRUServerConfig.java @ 2624

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