CatalogFeatures.java revision 908:af1875980cbf
1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.xml.catalog;
26
27import java.net.MalformedURLException;
28import java.net.URISyntaxException;
29import java.util.HashMap;
30import java.util.Map;
31import jdk.xml.internal.SecuritySupport;
32
33/**
34 * The CatalogFeatures holds a collection of features and properties.
35 * <p>
36 *
37 * <center><h2><a name="CatalogFeatures">Catalog Features</a></h2></center></p>
38 *
39 * <table border="1">
40 * <thead>
41 * <tr>
42 * <th rowspan="2">Feature</th>
43 * <th rowspan="2">Description</th>
44 * <th rowspan="2">Property Name</th>
45 * <th rowspan="2">System Property [1]</th>
46 * <th rowspan="2">jaxp.properties [1]</th>
47 * <th colspan="2" align="center">Value [2]</th>
48 * <th rowspan="2">Action</th>
49 * </tr>
50 * <tr>
51 * <th>Type</th>
52 * <th>Value</th>
53 * </tr>
54 * </thead>
55 * <tbody>
56 *
57 * <tr>
58 * <td><a name="FILES">FILES</a></td>
59 * <td>A semicolon-delimited list of catalog files. Relative file paths are
60 * considered relative to ${user.dir}.
61 * </td>
62 * <td>javax.xml.catalog.files</td>
63 * <td>javax.xml.catalog.files</td>
64 * <td>javax.xml.catalog.files</td>
65 * <td>String</td>
66 * <td>File paths</td>
67 * <td>
68 * Reads the first catalog as the current catalog; Loads others if no match
69 * is found in the current catalog including delegate catalogs if any.
70 * </td>
71 * </tr>
72 *
73 * <tr>
74 * <td rowspan="2"><a name="PREFER">PREFER</a></td>
75 * <td rowspan="2">Indicates the preference between the public and system
76 * identifiers. The default value is public [3].</td>
77 * <td rowspan="2">javax.xml.catalog.prefer</td>
78 * <td rowspan="2">N/A</td>
79 * <td rowspan="2">N/A</td>
80 * <td rowspan="2">String</td>
81 * <td>{@code system}</td>
82 * <td>Searches system entries for a match; Searches public entries when
83 * external identifier specifies only a public identifier</td>
84 * </tr>
85 * <tr>
86 * <td>{@code public}</td>
87 * <td>Searches system entries for a match; Searches public entries when
88 * there is no matching system entry.</td>
89 * </tr>
90 *
91 * <tr>
92 * <td rowspan="2"><a name="DEFER">DEFER</a></td>
93 * <td rowspan="2">Indicates that the alternative catalogs including those
94 * specified in delegate entries or nextCatalog are not read until they are
95 * needed. The default value is true.</td>
96 * <td rowspan="2">javax.xml.catalog.defer [4]</td>
97 * <td rowspan="2">javax.xml.catalog.defer</td>
98 * <td rowspan="2">javax.xml.catalog.defer</td>
99 * <td rowspan="2">String</td>
100 * <td>{@code true}</td>
101 * <td>Loads alternative catalogs as needed.
102 * </td>
103 * </tr>
104 * <tr>
105 * <td>{@code false}</td>
106 * <td>Loads all catalogs[5]. </td>
107 * </tr>
108 *
109 * <tr>
110 * <td rowspan="3"><a name="RESOLVE">RESOLVE</a></td>
111 * <td rowspan="3">Determines the action if there is no matching entry found after
112 * all of the specified catalogs are exhausted. The default is strict.</td>
113 * <td rowspan="3">javax.xml.catalog.resolve [4]</td>
114 * <td rowspan="3">javax.xml.catalog.resolve</td>
115 * <td rowspan="3">javax.xml.catalog.resolve</td>
116 * <td rowspan="3">String</td>
117 * <td>{@code strict}</td>
118 * <td>Throws CatalogException if there is no match.
119 * </td>
120 * </tr>
121 * <tr>
122 * <td>{@code continue}</td>
123 * <td>Allows the XML parser to continue as if there is no match.
124 * </td>
125 * </tr>
126 * <tr>
127 * <td>{@code ignore}</td>
128 * <td>Tells the XML parser to skip the external references if there no match.
129 * </td>
130 * </tr>
131 *
132 * </tbody>
133 * </table>
134 * <p>
135 * <b>[1]</b> There is no System property for the features that marked as "N/A".
136 *
137 * <p>
138 * <b>[2]</b> The value shall be exactly as listed in this table, case-sensitive.
139 * Any unspecified value will result in {@link IllegalArgumentException}.
140 * <p>
141 * <b>[3]</b> The Catalog specification defined complex rules on
142 * <a href="https://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html#attrib.prefer">
143 * the prefer attribute</a>. Although the prefer can be public or system, the
144 * specification actually made system the preferred option, that is, no matter
145 * the option, a system entry is always used if found. Public entries are only
146 * considered if the prefer is public and system entries are not found. It is
147 * therefore recommended that the prefer attribute be set as public
148 * (which is the default).
149 * <p>
150 * <b>[4]</b> Although non-standard attributes in the OASIS Catalog specification,
151 * {@code defer} and {@code resolve} are recognized by the Java Catalog API the
152 * same as the {@code prefer} as being an attribute in the catalog entry of the
153 * main catalog. Note that only the attributes specified for the catalog entry
154 * of the main Catalog file will be used.
155  * <p>
156 * <b>[5]</b> If the intention is to share an entire catalog store, it may be desirable to
157 * set the property {@code javax.xml.catalog.defer} to false to allow the entire
158 * catalog to be pre-loaded.
159 * <p>
160 * <h3>Scope and Order</h3>
161 * Features and properties can be set through the catalog file, the Catalog API,
162 * system properties, and {@code jaxp.properties}, with a preference in the same order.
163 * <p>
164 * Properties that are specified as attributes in the catalog file for the
165 * catalog and group entries shall take preference over any of the other settings.
166 * For example, if a {@code prefer} attribute is set in the catalog file as in
167 * {@code <catalog prefer="public">}, any other input for the "prefer" property
168 * is not necessary or will be ignored.
169 * <p>
170 * Properties set through the Catalog API override those that may have been set
171 * by system properties and/or in {@code jaxp.properties}. In case of multiple
172 * interfaces, the latest in a procedure shall take preference. For
173 * {@link Feature#FILES}, this means that the path(s) specified through the methods
174 * of the {@link CatalogManager} will override any that may have been entered
175 * through the {@link Builder}.
176 *
177 * <p>
178 * System properties when set shall override those in {@code jaxp.properties}.
179 * <p>
180 * The {@code jaxp.properties} file is typically in the conf directory of the Java
181 * installation. The file is read only once by the JAXP implementation and
182 * its values are then cached for future use. If the file does not exist
183 * when the first attempt is made to read from it, no further attempts are
184 * made to check for its existence. It is not possible to change the value
185 * of any properties in {@code jaxp.properties} after it has been read.
186 * <p>
187 * A CatalogFeatures instance can be created through its builder as illustrated
188 * in the following sample code:
189 * <pre>{@code
190                CatalogFeatures f = CatalogFeatures.builder()
191                        .with(Feature.FILES, "catalog.xml")
192                        .with(Feature.PREFER, "public")
193                        .with(Feature.DEFER, "true")
194                        .with(Feature.RESOLVE, "ignore")
195                        .build();
196 * }</pre>
197 *
198 * @since 9
199 */
200public class CatalogFeatures {
201
202    /**
203     * The constant name of the javax.xml.catalog.files property. See the property table for more details.
204     */
205    static final String CATALOG_FILES = "javax.xml.catalog.files";
206
207    /**
208     * The javax.xml.catalog.prefer property. See the property table for more details.
209     */
210    static final String CATALOG_PREFER = "javax.xml.catalog.prefer";
211
212    /**
213     * Determines whether or not delegated catalogs and nextCatalog will be read
214     * when the current catalog is loaded.
215     */
216    static final String CATALOG_DEFER = "javax.xml.catalog.defer";
217
218    /**
219     * Determines the action if there is no matching entry found after
220     * all of the specified catalogs are exhausted.
221     */
222    static final String CATALOG_RESOLVE = "javax.xml.catalog.resolve";
223
224    //values for the prefer property
225    static final String PREFER_SYSTEM = "system";
226    static final String PREFER_PUBLIC = "public";
227
228    //values for the defer property
229    static final String DEFER_TRUE = "true";
230    static final String DEFER_FALSE = "false";
231
232    //values for the Resolve property
233    static final String RESOLVE_STRICT = "strict";
234    static final String RESOLVE_CONTINUE = "continue";
235    static final String RESOLVE_IGNORE = "ignore";
236
237    /**
238     * A Feature type as defined in the
239     * <a href="CatalogFeatures.html#CatalogFeatures">Catalog Features table</a>.
240     */
241    public static enum Feature {
242        /**
243         * The {@code javax.xml.catalog.files} property as described in
244         * item <a href="CatalogFeatures.html#FILES">FILES</a> of the
245         * Catalog Features table.
246         */
247        FILES(CATALOG_FILES, null, true),
248        /**
249         * The {@code javax.xml.catalog.prefer} property as described in
250         * item <a href="CatalogFeatures.html#PREFER">PREFER</a> of the
251         * Catalog Features table.
252         */
253        PREFER(CATALOG_PREFER, PREFER_PUBLIC, false),
254        /**
255         * The {@code javax.xml.catalog.defer} property as described in
256         * item <a href="CatalogFeatures.html#DEFER">DEFER</a> of the
257         * Catalog Features table.
258         */
259        DEFER(CATALOG_DEFER, DEFER_TRUE, true),
260        /**
261         * The {@code javax.xml.catalog.resolve} property as described in
262         * item <a href="CatalogFeatures.html#RESOLVE">RESOLVE</a> of the
263         * Catalog Features table.
264         */
265        RESOLVE(CATALOG_RESOLVE, RESOLVE_STRICT, true);
266
267        private final String name;
268        private final String defaultValue;
269        private String value;
270        private final boolean hasSystem;
271
272        /**
273         * Constructs a CatalogFeature instance.
274         * @param name the name of the feature
275         * @param value the value of the feature
276         * @param hasSystem a flag to indicate whether the feature is supported
277         * with a System property
278         */
279        Feature(String name, String value, boolean hasSystem) {
280            this.name = name;
281            this.defaultValue = value;
282            this.hasSystem = hasSystem;
283        }
284
285        /**
286         * Checks whether the specified property is equal to the current property.
287         * @param propertyName the name of a property
288         * @return true if the specified property is the current property, false
289         * otherwise
290         */
291        boolean equalsPropertyName(String propertyName) {
292            return name.equals(propertyName);
293        }
294
295        /**
296         * Returns the name of the corresponding System Property.
297         *
298         * @return the name of the System Property
299         */
300        public String getPropertyName() {
301            return name;
302        }
303
304        /**
305         * Returns the default value of the property.
306         * @return the default value of the property
307         */
308        String defaultValue() {
309            return defaultValue;
310        }
311
312        /**
313         * Returns the value of the property.
314         * @return the value of the property
315         */
316        String getValue() {
317            return value;
318        }
319
320        /**
321         * Checks whether System property is supported for the feature.
322         * @return true it is supported, false otherwise
323         */
324        boolean hasSystemProperty() {
325            return hasSystem;
326        }
327    }
328
329    /**
330     * States of the settings of a property, in the order: default value,
331     * jaxp.properties file, jaxp system properties, and jaxp api properties
332     */
333    static enum State {
334        /** represents the default state of a feature. */
335        DEFAULT("default"),
336        /** indicates the value of the feature is read from jaxp.properties. */
337        JAXPDOTPROPERTIES("jaxp.properties"),
338        /** indicates the value of the feature is read from its System property. */
339        SYSTEMPROPERTY("system property"),
340        /** indicates the value of the feature is specified through the API. */
341        APIPROPERTY("property"),
342        /** indicates the value of the feature is specified as a catalog attribute. */
343        CATALOGATTRIBUTE("catalog attribute");
344
345        final String literal;
346
347        State(String literal) {
348            this.literal = literal;
349        }
350
351        String literal() {
352            return literal;
353        }
354    }
355
356    /**
357     * Values of the properties
358     */
359    private String[] values;
360
361    /**
362     * States of the settings for each property
363     */
364    private State[] states;
365
366    /**
367     * Private class constructor
368     */
369    private CatalogFeatures() {
370    }
371
372    /**
373     * Returns a CatalogFeatures instance with default settings.
374     * @return a default CatalogFeatures instance
375     */
376    public static CatalogFeatures defaults() {
377        return CatalogFeatures.builder().build();
378    }
379
380    /**
381     * Constructs a new CatalogFeatures instance with the builder.
382     *
383     * @param builder the builder to build the CatalogFeatures
384     */
385    CatalogFeatures(Builder builder) {
386        init();
387        setProperties(builder);
388    }
389
390    /**
391     * Returns the value of the specified feature.
392     *
393     * @param cf the type of the Catalog feature
394     * @return the value of the feature
395     */
396    public String get(Feature cf) {
397        return values[cf.ordinal()];
398    }
399
400    /**
401     * Initializes the supported properties
402     */
403    private void init() {
404        values = new String[Feature.values().length];
405        states = new State[Feature.values().length];
406        for (Feature cf : Feature.values()) {
407            setProperty(cf.ordinal(), State.DEFAULT, cf.defaultValue());
408        }
409        //read system properties or jaxp.properties
410        readSystemProperties();
411    }
412
413    /**
414     * Sets properties by the Builder.
415     * @param builder the CatalogFeatures builder
416     */
417    private void setProperties(Builder builder) {
418        builder.values.entrySet().stream().forEach((entry) -> {
419            setProperty(entry.getKey().ordinal(), State.APIPROPERTY, entry.getValue());
420        });
421    }
422    /**
423     * Sets the value of a property by its index, updates only if it shall override.
424     *
425     * @param index the index of the property
426     * @param state the state of the property
427     * @param value the value of the property
428     * @throws IllegalArgumentException if the value is invalid
429     */
430    private void setProperty(int index, State state, String value) {
431        if (value != null && value.length() != 0) {
432            if (index == Feature.PREFER.ordinal()) {
433                if (!value.equals(PREFER_SYSTEM) && !value.equals(PREFER_PUBLIC)) {
434                    CatalogMessages.reportIAE(new Object[]{value, Feature.PREFER.name()}, null);
435                }
436            } else if (index == Feature.DEFER.ordinal()) {
437                if (!value.equals(DEFER_TRUE) && !value.equals(DEFER_FALSE)) {
438                    CatalogMessages.reportIAE(new Object[]{value, Feature.DEFER.name()}, null);
439                }
440            } else if (index == Feature.RESOLVE.ordinal()) {
441                if (!value.equals(RESOLVE_STRICT) && !value.equals(RESOLVE_CONTINUE)
442                         && !value.equals(RESOLVE_IGNORE)) {
443                    CatalogMessages.reportIAE(new Object[]{value, Feature.RESOLVE.name()}, null);
444                }
445            } else if (index == Feature.FILES.ordinal()) {
446                try {
447                    String[] catalogFile = value.split(";[ ]*");
448                    for (String temp : catalogFile) {
449                        if (Util.verifyAndGetURI(temp, null) == null) {
450                            CatalogMessages.reportIAE(new Object[]{value, Feature.FILES.name()}, null);
451                        }
452                    }
453                }catch (MalformedURLException | URISyntaxException | IllegalArgumentException ex) {
454                    CatalogMessages.reportIAE(new Object[]{value, Feature.FILES.name()}, ex);
455                }
456            }
457            if (states[index] == null || state.compareTo(states[index]) >= 0) {
458                values[index] = value;
459                states[index] = state;
460            }
461        } else {
462            if (state == State.SYSTEMPROPERTY || state == State.JAXPDOTPROPERTIES) {
463                CatalogMessages.reportIAE(new Object[]{value, Feature.values()[index].name()}, null);
464            }
465        }
466    }
467
468    /**
469     * Reads from system properties, or those in jaxp.properties
470     */
471    private void readSystemProperties() {
472        for (Feature cf : Feature.values()) {
473            getSystemProperty(cf, cf.getPropertyName());
474        }
475    }
476
477    /**
478     * Reads from system properties, or those in jaxp.properties
479     *
480     * @param cf the type of the property
481     * @param sysPropertyName the name of system property
482     */
483    private boolean getSystemProperty(Feature cf, String sysPropertyName) {
484        if (cf.hasSystemProperty()) {
485            String value = SecuritySupport.getSystemProperty(sysPropertyName);
486            if (value != null && !value.equals("")) {
487                setProperty(cf.ordinal(), State.SYSTEMPROPERTY, value);
488                return true;
489            }
490
491            value = SecuritySupport.readJAXPProperty(sysPropertyName);
492            if (value != null && !value.equals("")) {
493                setProperty(cf.ordinal(), State.JAXPDOTPROPERTIES, value);
494                return true;
495            }
496        }
497        return false;
498    }
499
500    /**
501     * Returns an instance of the builder for creating the CatalogFeatures object.
502     *
503     * @return an instance of the builder
504     */
505    public static Builder builder() {
506        return new CatalogFeatures.Builder();
507    }
508
509    /**
510     * The Builder class for building the CatalogFeatures object.
511     */
512    public static class Builder {
513        /**
514         * Values of the features supported by CatalogFeatures.
515         */
516        Map<Feature, String> values = new HashMap<>();
517
518        /**
519         * Instantiation of Builder is not allowed.
520         */
521        private Builder() {}
522
523        /**
524         * Sets the value to a specified Feature.
525         * @param feature the Feature to be set
526         * @param value the value to be set for the Feature
527         * @return this Builder instance
528         * @throws IllegalArgumentException if the value is not valid for the
529         * Feature or has the wrong syntax for the {@code javax.xml.catalog.files}
530         * property
531         */
532        public Builder with(Feature feature, String value) {
533            if (value == null || value.length() == 0) {
534                CatalogMessages.reportIAE(new Object[]{value, feature.name()}, null);
535            }
536            values.put(feature, value);
537            return this;
538        }
539
540        /**
541         * Returns a CatalogFeatures object built by this builder.
542         *
543         * @return an instance of CatalogFeatures
544         */
545        public CatalogFeatures build() {
546            return new CatalogFeatures(this);
547        }
548    }
549}
550