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