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