1 /* 2 * Copyright (c) 2015, 2019, 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 java.net.URI; 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 33 /** 34 * Represents a group entry. 35 * 36 * @since 9 37 */ 38 class GroupEntry extends BaseEntry { 39 static final int ATTRIBUTE_PREFER = 0; 40 static final int ATTRIBUTE_DEFFER = 1; 41 static final int ATTRIBUTE_RESOLUTION = 2; 42 43 //Unmodifiable features when the Catalog is created 44 CatalogFeatures features; 45 46 //Value of the prefer attribute 47 boolean isPreferPublic = true; 48 49 //The parent of the catalog instance 50 CatalogImpl parent = null; 51 52 //The catalog instance this group belongs to 53 CatalogImpl catalog; 54 55 //A list of all entries in a catalog or group 56 List<BaseEntry> entries = new ArrayList<>(); 57 58 //loaded delegated catalog by system id 59 Map<String, CatalogImpl> delegateCatalogs = new HashMap<>(); 60 61 //A list of all loaded Catalogs, including this, and next catalogs 62 Map<String, CatalogImpl> loadedCatalogs = new HashMap<>(); 63 64 /* 65 A list of Catalog Ids that have already been searched in a matching 66 operation. Check this list before constructing new Catalog to avoid circular 67 reference. 68 */ 69 List<String> catalogsSearched = new ArrayList<>(); 70 71 //A flag to indicate whether the current match is a system or uri 72 boolean isInstantMatch = false; 73 74 //A match of a rewrite type 75 String rewriteMatch = null; 76 77 //The length of the longest match of a rewrite type 78 int longestRewriteMatch = 0; 79 80 //A match of a suffix type 81 String suffixMatch = null; 82 83 //The length of the longest match of a suffix type 84 int longestSuffixMatch = 0; 85 86 //Indicate whether a system entry has been searched 87 boolean systemEntrySearched = false; 88 89 /** 90 * PreferType represents possible values of the prefer property 91 */ 92 public static enum PreferType { 93 PUBLIC("public"), 94 SYSTEM("system"); 95 96 final String literal; 97 PreferType(String literal)98 PreferType(String literal) { 99 this.literal = literal; 100 } 101 prefer(String prefer)102 public boolean prefer(String prefer) { 103 return literal.equals(prefer); 104 } 105 } 106 107 /** 108 * PreferType represents possible values of the resolve property 109 */ 110 public static enum ResolveType { 111 STRICT(CatalogFeatures.RESOLVE_STRICT), 112 CONTINUE(CatalogFeatures.RESOLVE_CONTINUE), 113 IGNORE(CatalogFeatures.RESOLVE_IGNORE); 114 115 final String literal; 116 ResolveType(String literal)117 ResolveType(String literal) { 118 this.literal = literal; 119 } 120 getType(String resolveType)121 static public ResolveType getType(String resolveType) { 122 for (ResolveType type : ResolveType.values()) { 123 if (type.isType(resolveType)) { 124 return type; 125 } 126 } 127 return null; 128 } 129 isType(String type)130 public boolean isType(String type) { 131 return literal.equals(type); 132 } 133 } 134 135 /** 136 * Constructs a GroupEntry 137 * 138 * @param type the type of the entry 139 * @param parent the parent Catalog 140 */ GroupEntry(CatalogEntryType type, CatalogImpl parent)141 public GroupEntry(CatalogEntryType type, CatalogImpl parent) { 142 super(type); 143 this.parent = parent; 144 } 145 146 /** 147 * Constructs a group entry. 148 * 149 * @param base The baseURI attribute 150 * @param attributes The attributes 151 */ GroupEntry(String base, String... attributes)152 public GroupEntry(String base, String... attributes) { 153 this(null, base, attributes); 154 } 155 156 /** 157 * Resets the group entry to its initial state. 158 */ reset()159 public void reset() { 160 isInstantMatch = false; 161 rewriteMatch = null; 162 longestRewriteMatch = 0; 163 suffixMatch = null; 164 longestSuffixMatch = 0; 165 systemEntrySearched = false; 166 } 167 /** 168 * Constructs a group entry. 169 * @param catalog the catalog this GroupEntry belongs to 170 * @param base the baseURI attribute 171 * @param attributes the attributes 172 */ GroupEntry(CatalogImpl catalog, String base, String... attributes)173 public GroupEntry(CatalogImpl catalog, String base, String... attributes) { 174 super(CatalogEntryType.GROUP, base); 175 setPrefer(attributes[ATTRIBUTE_PREFER]); 176 this.catalog = catalog; 177 } 178 179 /** 180 * Sets the catalog for this GroupEntry. 181 * 182 * @param catalog the catalog this GroupEntry belongs to 183 */ setCatalog(CatalogImpl catalog)184 void setCatalog(CatalogImpl catalog) { 185 this.catalog = catalog; 186 } 187 188 /** 189 * Adds an entry. 190 * 191 * @param entry The entry to be added. 192 */ addEntry(BaseEntry entry)193 public void addEntry(BaseEntry entry) { 194 entries.add(entry); 195 } 196 197 /** 198 * Sets the prefer property. If the value is null or empty, or any String 199 * other than the defined, it will be assumed as the default value. 200 * 201 * @param value The value of the prefer attribute 202 */ setPrefer(String value)203 public final void setPrefer(String value) { 204 isPreferPublic = PreferType.PUBLIC.prefer(value); 205 } 206 207 /** 208 * Queries the prefer attribute 209 * 210 * @return true if the prefer attribute is set to system, false if not. 211 */ isPreferPublic()212 public boolean isPreferPublic() { 213 return isPreferPublic; 214 } 215 216 /** 217 * Attempt to find a matching entry in the catalog by systemId. 218 * 219 * <p> 220 * The method searches through the system-type entries, including system, 221 * rewriteSystem, systemSuffix, delegateSystem, and group entries in the 222 * current catalog in order to find a match. 223 * 224 * 225 * @param systemId The system identifier of the external entity being 226 * referenced. 227 * 228 * @return a URI string if a mapping is found, or null otherwise. 229 */ matchSystem(String systemId)230 public String matchSystem(String systemId) { 231 systemEntrySearched = true; 232 String match = null; 233 for (BaseEntry entry : entries) { 234 switch (entry.type) { 235 case SYSTEM: 236 match = ((SystemEntry) entry).match(systemId); 237 //if there's a matching system entry, use it 238 if (match != null) { 239 isInstantMatch = true; 240 return match; 241 } 242 break; 243 case REWRITESYSTEM: 244 match = ((RewriteSystem) entry).match(systemId, longestRewriteMatch); 245 if (match != null) { 246 rewriteMatch = match; 247 longestRewriteMatch = ((RewriteSystem) entry).getSystemIdStartString().length(); 248 } 249 break; 250 case SYSTEMSUFFIX: 251 match = ((SystemSuffix) entry).match(systemId, longestSuffixMatch); 252 if (match != null) { 253 suffixMatch = match; 254 longestSuffixMatch = ((SystemSuffix) entry).getSystemIdSuffix().length(); 255 } 256 break; 257 case GROUP: 258 GroupEntry grpEntry = (GroupEntry) entry; 259 match = grpEntry.matchSystem(systemId); 260 if (grpEntry.isInstantMatch) { 261 //use it if there is a match of the system type 262 return match; 263 } else if (grpEntry.longestRewriteMatch > longestRewriteMatch) { 264 longestRewriteMatch = grpEntry.longestRewriteMatch; 265 rewriteMatch = match; 266 } else if (grpEntry.longestSuffixMatch > longestSuffixMatch) { 267 longestSuffixMatch = grpEntry.longestSuffixMatch; 268 suffixMatch = match; 269 } 270 break; 271 } 272 } 273 274 if (longestRewriteMatch > 0) { 275 return rewriteMatch; 276 } else if (longestSuffixMatch > 0) { 277 return suffixMatch; 278 } 279 280 //if no single match is found, try delegates 281 return matchDelegate(CatalogEntryType.DELEGATESYSTEM, systemId); 282 } 283 284 /** 285 * Attempt to find a matching entry in the catalog by publicId. 286 * 287 * <p> 288 * The method searches through the public-type entries, including public, 289 * delegatePublic, and group entries in the current catalog in order to find 290 * a match. 291 * 292 * 293 * @param publicId The public identifier of the external entity being 294 * referenced. 295 * 296 * @return a URI string if a mapping is found, or null otherwise. 297 */ matchPublic(String publicId)298 public String matchPublic(String publicId) { 299 /* 300 When both public and system identifiers are specified, and prefer is 301 not public (that is, system), only system entry will be used. 302 */ 303 if (!isPreferPublic && systemEntrySearched) { 304 return null; 305 } 306 //match public entries 307 String match = null; 308 for (BaseEntry entry : entries) { 309 switch (entry.type) { 310 case PUBLIC: 311 match = ((PublicEntry) entry).match(publicId); 312 break; 313 case URI: 314 match = ((UriEntry) entry).match(publicId); 315 break; 316 case GROUP: 317 match = ((GroupEntry) entry).matchPublic(publicId); 318 break; 319 } 320 if (match != null) { 321 return match; 322 } 323 } 324 325 //if no single match is found, try delegates 326 return matchDelegate(CatalogEntryType.DELEGATEPUBLIC, publicId); 327 } 328 329 /** 330 * Attempt to find a matching entry in the catalog by the uri element. 331 * 332 * <p> 333 * The method searches through the uri-type entries, including uri, 334 * rewriteURI, uriSuffix, delegateURI and group entries in the current 335 * catalog in order to find a match. 336 * 337 * 338 * @param uri The URI reference of a resource. 339 * 340 * @return a URI string if a mapping is found, or null otherwise. 341 */ matchURI(String uri)342 public String matchURI(String uri) { 343 String match = null; 344 for (BaseEntry entry : entries) { 345 switch (entry.type) { 346 case URI: 347 match = ((UriEntry) entry).match(uri); 348 if (match != null) { 349 isInstantMatch = true; 350 return match; 351 } 352 break; 353 case REWRITEURI: 354 match = ((RewriteUri) entry).match(uri, longestRewriteMatch); 355 if (match != null) { 356 rewriteMatch = match; 357 longestRewriteMatch = ((RewriteUri) entry).getURIStartString().length(); 358 } 359 break; 360 case URISUFFIX: 361 match = ((UriSuffix) entry).match(uri, longestSuffixMatch); 362 if (match != null) { 363 suffixMatch = match; 364 longestSuffixMatch = ((UriSuffix) entry).getURISuffix().length(); 365 } 366 break; 367 case GROUP: 368 GroupEntry grpEntry = (GroupEntry) entry; 369 match = grpEntry.matchURI(uri); 370 if (grpEntry.isInstantMatch) { 371 //use it if there is a match of the uri type 372 return match; 373 } else if (grpEntry.longestRewriteMatch > longestRewriteMatch) { 374 rewriteMatch = match; 375 longestRewriteMatch = grpEntry.longestRewriteMatch; 376 } else if (grpEntry.longestSuffixMatch > longestSuffixMatch) { 377 suffixMatch = match; 378 longestSuffixMatch = grpEntry.longestSuffixMatch; 379 } 380 break; 381 } 382 } 383 384 if (longestRewriteMatch > 0) { 385 return rewriteMatch; 386 } else if (longestSuffixMatch > 0) { 387 return suffixMatch; 388 } 389 390 //if no single match is found, try delegates 391 return matchDelegate(CatalogEntryType.DELEGATEURI, uri); 392 } 393 394 /** 395 * Matches delegatePublic or delegateSystem against the specified id 396 * 397 * @param type the type of the Catalog entry 398 * @param id the system or public id to be matched 399 * @return the URI string if a mapping is found, or null otherwise. 400 */ matchDelegate(CatalogEntryType type, String id)401 private String matchDelegate(CatalogEntryType type, String id) { 402 String match = null; 403 int longestMatch = 0; 404 URI catalogId = null; 405 URI temp; 406 407 //Check delegate types in the current catalog 408 for (BaseEntry entry : entries) { 409 if (entry.type == type) { 410 if (type == CatalogEntryType.DELEGATESYSTEM) { 411 temp = ((DelegateSystem)entry).matchURI(id, longestMatch); 412 } else if (type == CatalogEntryType.DELEGATEPUBLIC) { 413 temp = ((DelegatePublic)entry).matchURI(id, longestMatch); 414 } else { 415 temp = ((DelegateUri)entry).matchURI(id, longestMatch); 416 } 417 if (temp != null) { 418 longestMatch = entry.getMatchId().length(); 419 catalogId = temp; 420 } 421 } 422 } 423 424 //Check delegate Catalogs 425 if (catalogId != null) { 426 Catalog delegateCatalog = loadDelegateCatalog(catalog, catalogId); 427 428 if (delegateCatalog != null) { 429 if (type == CatalogEntryType.DELEGATESYSTEM) { 430 match = delegateCatalog.matchSystem(id); 431 } else if (type == CatalogEntryType.DELEGATEPUBLIC) { 432 match = delegateCatalog.matchPublic(id); 433 } else { 434 match = delegateCatalog.matchURI(id); 435 } 436 } 437 } 438 439 return match; 440 } 441 442 /** 443 * Loads all delegate catalogs. 444 * 445 * @param parent the parent catalog of the delegate catalogs 446 */ loadDelegateCatalogs(CatalogImpl parent)447 void loadDelegateCatalogs(CatalogImpl parent) { 448 entries.stream() 449 .filter((entry) -> (entry.type == CatalogEntryType.DELEGATESYSTEM || 450 entry.type == CatalogEntryType.DELEGATEPUBLIC || 451 entry.type == CatalogEntryType.DELEGATEURI)) 452 .map((entry) -> (AltCatalog)entry) 453 .forEach((altCatalog) -> { 454 loadDelegateCatalog(parent, altCatalog.getCatalogURI()); 455 }); 456 } 457 458 /** 459 * Loads a delegate catalog by the catalogId specified. 460 * 461 * @param parent the parent catalog of the delegate catalog 462 * @param catalogURI the URI to the catalog 463 */ loadDelegateCatalog(CatalogImpl parent, URI catalogURI)464 Catalog loadDelegateCatalog(CatalogImpl parent, URI catalogURI) { 465 CatalogImpl delegateCatalog = null; 466 if (catalogURI != null) { 467 String catalogId = catalogURI.toASCIIString(); 468 if (verifyCatalogFile(parent, catalogURI)) { 469 delegateCatalog = getLoadedCatalog(catalogId); 470 if (delegateCatalog == null) { 471 delegateCatalog = new CatalogImpl(parent, features, catalogURI); 472 delegateCatalog.load(); 473 delegateCatalogs.put(catalogId, delegateCatalog); 474 } 475 } 476 } 477 478 return delegateCatalog; 479 } 480 481 /** 482 * Returns a previously loaded Catalog object if found. 483 * 484 * @param catalogId The systemId of a catalog 485 * @return a Catalog object previously loaded, or null if none in the saved 486 * list 487 */ getLoadedCatalog(String catalogId)488 CatalogImpl getLoadedCatalog(String catalogId) { 489 CatalogImpl c = null; 490 491 //check delegate Catalogs 492 c = delegateCatalogs.get(catalogId); 493 if (c == null) { 494 //check other loaded Catalogs 495 c = loadedCatalogs.get(catalogId); 496 } 497 498 return c; 499 } 500 501 502 /** 503 * Verifies that the catalog file represented by the catalogId exists. If it 504 * doesn't, returns false to ignore it as specified in the Catalog 505 * specification, section 8. Resource Failures. 506 * <p> 507 * Verifies that the catalog represented by the catalogId has not been 508 * searched or is not circularly referenced. 509 * 510 * @param parent the parent of the catalog to be loaded 511 * @param catalogURI the URI to the catalog 512 * @throws CatalogException if circular reference is found. 513 * @return true if the catalogId passed verification, false otherwise 514 */ verifyCatalogFile(CatalogImpl parent, URI catalogURI)515 final boolean verifyCatalogFile(CatalogImpl parent, URI catalogURI) { 516 if (catalogURI == null) { 517 return false; 518 } 519 520 //Ignore it if it doesn't exist 521 if (Util.isFileUri(catalogURI) && 522 !Util.isFileUriExist(catalogURI, false)) { 523 return false; 524 } 525 526 String catalogId = catalogURI.toASCIIString(); 527 if (catalogsSearched.contains(catalogId) || isCircular(parent, catalogId)) { 528 CatalogMessages.reportRunTimeError(CatalogMessages.ERR_CIRCULAR_REFERENCE, 529 new Object[]{CatalogMessages.sanitize(catalogId)}); 530 } 531 532 return true; 533 } 534 535 /** 536 * Checks whether the catalog is circularly referenced 537 * 538 * @param parent the parent of the catalog to be loaded 539 * @param systemId the system identifier of the catalog to be loaded 540 * @return true if is circular, false otherwise 541 */ isCircular(CatalogImpl parent, String systemId)542 boolean isCircular(CatalogImpl parent, String systemId) { 543 // first, check the parent of the catalog to be loaded 544 if (parent == null) { 545 return false; 546 } 547 548 if (parent.systemId.equals(systemId)) { 549 return true; 550 } 551 552 // next, check parent's parent 553 return parent.isCircular(parent.parent, systemId); 554 } 555 } 556