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  */
  25 package javax.xml.catalog;
  26 
  27 import java.net.MalformedURLException;
  28 import java.net.URISyntaxException;
  29 import java.util.HashMap;
  30 import java.util.Map;
  31 import 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  */
 200 public 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 }