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