1 /*
   2  * Copyright (c) 2015, 2017, 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 com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
  28 import java.io.IOException;
  29 import java.net.MalformedURLException;
  30 import java.net.URI;
  31 import java.net.URISyntaxException;
  32 import java.net.URL;
  33 import java.util.ArrayList;
  34 import java.util.HashMap;
  35 import java.util.Iterator;
  36 import java.util.List;
  37 import java.util.NoSuchElementException;
  38 import java.util.Spliterator;
  39 import java.util.Spliterators;
  40 import java.util.stream.Stream;
  41 import java.util.stream.StreamSupport;
  42 import static javax.xml.catalog.BaseEntry.CatalogEntryType;
  43 import static javax.xml.catalog.CatalogFeatures.DEFER_TRUE;
  44 import javax.xml.catalog.CatalogFeatures.Feature;
  45 import static javax.xml.catalog.CatalogMessages.formatMessage;
  46 import javax.xml.parsers.ParserConfigurationException;
  47 import javax.xml.parsers.SAXParser;
  48 import javax.xml.parsers.SAXParserFactory;
  49 import org.xml.sax.SAXException;
  50 
  51 /**
  52  * Implementation of the Catalog.
  53  *
  54  * @since 9
  55  */
  56 class CatalogImpl extends GroupEntry implements Catalog {
  57 
  58     //Catalog level, 0 means the top catalog
  59     int level = 0;
  60 
  61     //Value of the defer attribute to determine if alternative catalogs are read
  62     boolean isDeferred = true;
  63 
  64     //Value of the resolve attribute
  65     ResolveType resolveType = ResolveType.STRICT;
  66 
  67     //indicate whether the Catalog is empty
  68     boolean isEmpty;
  69 
  70     //Current parsed Catalog
  71     int current = 0;
  72 
  73     //System Id for this catalog
  74     String systemId;
  75 
  76     /*
  77      A list of catalog entry files from the input, excluding the current catalog.
  78      URIs in the List are verified during input validation or property retrieval.
  79      */
  80     List<String> inputFiles;
  81 
  82     //A list of catalogs specified using the nextCatalog element
  83     List<NextCatalog> nextCatalogs;
  84 
  85     //reuse the parser
  86     SAXParser parser;
  87 
  88     /**
  89      * Construct a Catalog with specified URI.
  90      *
  91      * @param uris the uri(s) to one or more catalogs
  92      * @throws CatalogException If an error happens while parsing the specified
  93      * catalog file.
  94      */
  95     public CatalogImpl(CatalogFeatures f, URI... uris) throws CatalogException {
  96         this(null, f, uris);
  97     }
  98 
  99     /**
 100      * Construct a Catalog with specified URI.
 101      *
 102      * @param parent The parent catalog
 103      * @param uris the uri(s) to one or more catalogs
 104      * @throws CatalogException If an error happens while parsing the specified
 105      * catalog file.
 106      */
 107     public CatalogImpl(CatalogImpl parent, CatalogFeatures f, URI... uris) throws CatalogException {
 108         super(CatalogEntryType.CATALOG, parent);
 109         if (f == null) {
 110             throw new NullPointerException(
 111                     formatMessage(CatalogMessages.ERR_NULL_ARGUMENT, new Object[]{"CatalogFeatures"}));
 112         }
 113 
 114         init(f);
 115 
 116         //Path of catalog files
 117         String[] catalogFile = null;
 118         if (level == 0 && uris.length == 0) {
 119             String files = features.get(Feature.FILES);
 120             if (files != null) {
 121                 catalogFile = files.split(";");
 122             }
 123         } else {
 124             catalogFile = new String[uris.length];
 125             for (int i=0; i<uris.length; i++) {
 126                 catalogFile[i] = uris[i].toASCIIString();
 127             }
 128         }
 129 
 130         /*
 131          In accordance with 8. Resource Failures of the Catalog spec, missing
 132          Catalog entry files are to be ignored.
 133          */
 134         if ((catalogFile != null && catalogFile.length > 0)) {
 135             int start = 0;
 136             URI uri = null;
 137             for (String temp : catalogFile) {
 138                 uri = URI.create(temp);
 139                 start++;
 140                 if (verifyCatalogFile(uri)) {
 141                     systemId = temp;
 142                     try {
 143                         baseURI = new URL(systemId);
 144                     } catch (MalformedURLException e) {
 145                         CatalogMessages.reportRunTimeError(CatalogMessages.ERR_INVALID_PATH,
 146                                 new Object[]{temp}, e);
 147                     }
 148                     break;
 149                 }
 150             }
 151 
 152             //Save the rest of input files as alternative catalogs
 153             if (level == 0 && catalogFile.length > start) {
 154                 inputFiles = new ArrayList<>();
 155                 for (int i = start; i < catalogFile.length; i++) {
 156                     if (catalogFile[i] != null) {
 157                         inputFiles.add(catalogFile[i]);
 158                     }
 159                 }
 160             }
 161         }
 162     }
 163 
 164     /**
 165      * Loads the catalog
 166      */
 167     void load() {
 168         if (systemId != null) {
 169             parse(systemId);
 170         }
 171 
 172         //save this catalog before loading the next
 173         loadedCatalogs.put(systemId, this);
 174 
 175         //Load delegate and alternative catalogs if defer is false.
 176         if (!isDeferred()) {
 177            loadDelegateCatalogs();
 178            loadNextCatalogs();
 179         }
 180     }
 181 
 182     private void init(CatalogFeatures f) {
 183         if (parent == null) {
 184             level = 0;
 185         } else {
 186             level = parent.level + 1;
 187             this.loadedCatalogs = parent.loadedCatalogs;
 188             this.catalogsSearched = parent.catalogsSearched;
 189         }
 190         if (f == null) {
 191             this.features = CatalogFeatures.defaults();
 192         } else {
 193             this.features = f;
 194         }
 195         setPrefer(features.get(Feature.PREFER));
 196         setDeferred(features.get(Feature.DEFER));
 197         setResolve(features.get(Feature.RESOLVE));
 198     }
 199 
 200     /**
 201      * Resets the Catalog instance to its initial state.
 202      */
 203     @Override
 204     public void reset() {
 205         super.reset();
 206         current = 0;
 207         if (level == 0) {
 208             catalogsSearched.clear();
 209         }
 210         entries.stream().filter((entry) -> (entry.type == CatalogEntryType.GROUP)).forEach((entry) -> {
 211             ((GroupEntry) entry).reset();
 212         });
 213     }
 214 
 215     /**
 216      * Returns whether this Catalog instance is the top (main) Catalog.
 217      *
 218      * @return true if the instance is the top Catalog, false otherwise
 219      */
 220     boolean isTop() {
 221         return level == 0;
 222     }
 223 
 224     /**
 225      * Gets the parent of this catalog.
 226      *
 227      * @returns The parent catalog
 228      */
 229     public Catalog getParent() {
 230         return this.parent;
 231     }
 232 
 233     /**
 234      * Sets the defer property. If the value is null or empty, or any String
 235      * other than the defined, it will be assumed as the default value.
 236      *
 237      * @param value The value of the defer attribute
 238      */
 239     public final void setDeferred(String value) {
 240         isDeferred = DEFER_TRUE.equals(value);
 241     }
 242 
 243     /**
 244      * Queries the defer attribute
 245      *
 246      * @return true if the prefer attribute is set to system, false if not.
 247      */
 248     public boolean isDeferred() {
 249         return isDeferred;
 250     }
 251 
 252     /**
 253      * Sets the resolve property. If the value is null or empty, or any String
 254      * other than the defined, it will be assumed as the default value.
 255      *
 256      * @param value The value of the resolve attribute
 257      */
 258     public final void setResolve(String value) {
 259         resolveType = ResolveType.getType(value);
 260     }
 261 
 262     /**
 263      * Gets the value of the resolve attribute
 264      *
 265      * @return The value of the resolve attribute
 266      */
 267     public final ResolveType getResolve() {
 268         return resolveType;
 269     }
 270 
 271     /**
 272      * Marks the Catalog as being searched already.
 273      */
 274     void markAsSearched() {
 275         catalogsSearched.add(systemId);
 276     }
 277 
 278     /**
 279      * Parses the catalog.
 280      *
 281      * @param systemId The systemId of the catalog
 282      * @throws CatalogException if parsing the catalog failed
 283      */
 284     private void parse(String systemId) {
 285         if (parser == null) {
 286             parser = getParser();
 287         }
 288 
 289         try {
 290             CatalogReader reader = new CatalogReader(this, parser);
 291             parser.parse(systemId, reader);
 292         } catch (SAXException | IOException ex) {
 293             CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSING_FAILED, ex);
 294         }
 295     }
 296 
 297     /**
 298      * Returns a SAXParser instance
 299      * @return a SAXParser instance
 300      * @throws CatalogException if constructing a SAXParser failed
 301      */
 302     private SAXParser getParser() {
 303         SAXParser p = null;
 304         try {
 305             SAXParserFactory spf = new SAXParserFactoryImpl();
 306             spf.setNamespaceAware(true);
 307             spf.setValidating(false);
 308             spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
 309             p = spf.newSAXParser();
 310         } catch (ParserConfigurationException | SAXException e) {
 311             CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSING_FAILED, e);
 312         }
 313         return p;
 314     }
 315 
 316     /**
 317      * Indicate that the catalog is empty
 318      *
 319      * @return True if the catalog is empty; False otherwise.
 320      */
 321     public boolean isEmpty() {
 322         return isEmpty;
 323     }
 324 
 325     @Override
 326     public Stream<Catalog> catalogs() {
 327         Iterator<Catalog> iter = new Iterator<Catalog>() {
 328             Catalog nextCatalog = null;
 329 
 330             //Current index of the input files
 331             int inputFilesIndex = 0;
 332 
 333             //Next catalog
 334             int nextCatalogIndex = 0;
 335 
 336             @Override
 337             public boolean hasNext() {
 338                 if (nextCatalog != null) {
 339                     return true;
 340                 } else {
 341                     nextCatalog = nextCatalog();
 342                     return (nextCatalog != null);
 343                 }
 344             }
 345 
 346             @Override
 347             public Catalog next() {
 348                 if (nextCatalog != null || hasNext()) {
 349                     Catalog catalog = nextCatalog;
 350                     nextCatalog = null;
 351                     return catalog;
 352                 } else {
 353                     throw new NoSuchElementException();
 354                 }
 355             }
 356 
 357             /**
 358              * Returns the next alternative catalog.
 359              *
 360              * @return the next catalog if any
 361              */
 362             private Catalog nextCatalog() {
 363                 Catalog c = null;
 364 
 365                 //Check those specified in nextCatalogs
 366                 if (nextCatalogs != null) {
 367                     while (c == null && nextCatalogIndex < nextCatalogs.size()) {
 368                         c = getCatalog(nextCatalogs.get(nextCatalogIndex++).getCatalogURI());
 369                     }
 370                 }
 371 
 372                 //Check the input list
 373                 if (c == null && inputFiles != null) {
 374                     while (c == null && inputFilesIndex < inputFiles.size()) {
 375                         c = getCatalog(URI.create(inputFiles.get(inputFilesIndex++)));
 376                     }
 377                 }
 378 
 379                 return c;
 380             }
 381         };
 382 
 383         return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
 384                 iter, Spliterator.ORDERED | Spliterator.NONNULL), false);
 385     }
 386 
 387     /**
 388      * Adds the catalog to the nextCatalog list
 389      *
 390      * @param catalog a catalog specified in a nextCatalog entry
 391      */
 392     void addNextCatalog(NextCatalog catalog) {
 393         if (catalog == null) {
 394             return;
 395         }
 396 
 397         if (nextCatalogs == null) {
 398             nextCatalogs = new ArrayList<>();
 399         }
 400 
 401         nextCatalogs.add(catalog);
 402     }
 403 
 404     /**
 405      * Loads all alternative catalogs.
 406      */
 407     void loadNextCatalogs() {
 408         //loads catalogs specified in nextCatalogs
 409         if (nextCatalogs != null) {
 410             nextCatalogs.stream().forEach((next) -> {
 411                 getCatalog(next.getCatalogURI());
 412             });
 413         }
 414 
 415         //loads catalogs from the input list
 416         if (inputFiles != null) {
 417             inputFiles.stream().forEach((uri) -> {
 418                 getCatalog(URI.create(uri));
 419             });
 420         }
 421     }
 422 
 423     /**
 424      * Returns a Catalog object by the specified path.
 425      *
 426      * @param path the path to a catalog
 427      * @return a Catalog object
 428      */
 429     Catalog getCatalog(URI uri) {
 430         if (uri == null) {
 431             return null;
 432         }
 433 
 434         CatalogImpl c = null;
 435 
 436         if (verifyCatalogFile(uri)) {
 437             c = getLoadedCatalog(uri.toASCIIString());
 438             if (c == null) {
 439                 c = new CatalogImpl(this, features, uri);
 440                 c.load();
 441             }
 442         }
 443         return c;
 444     }
 445 
 446     /**
 447      * Saves a loaded Catalog.
 448      *
 449      * @param catalogId the catalogId associated with the Catalog object
 450      * @param c the Catalog to be saved
 451      */
 452     void saveLoadedCatalog(String catalogId, CatalogImpl c) {
 453         loadedCatalogs.put(catalogId, c);
 454     }
 455 
 456     /**
 457      * Returns a count of all loaded catalogs, including delegate catalogs.
 458      *
 459      * @return a count of all loaded catalogs
 460      */
 461     int loadedCatalogCount() {
 462         return loadedCatalogs.size() + delegateCatalogs.size();
 463     }
 464 }