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