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 }