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 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 98 PreferType(String literal) { 99 this.literal = literal; 100 } 101 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 117 ResolveType(String literal) { 118 this.literal = literal; 119 } 120 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 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 */ 140 public GroupEntry(CatalogEntryType type, CatalogImpl parent) { 141 super(type); 142 this.parent = parent; 143 } 144 145 /** 146 * Constructs a group entry. 147 * 148 * @param base The baseURI attribute 149 * @param attributes The attributes 150 */ 151 public GroupEntry(String base, String... attributes) { 152 this(null, base, attributes); 153 } 154 155 /** 156 * Resets the group entry to its initial state. 157 */ 158 public void reset() { 159 isInstantMatch = false; 160 rewriteMatch = null; 161 longestRewriteMatch = 0; 162 suffixMatch = null; 163 longestSuffixMatch = 0; 164 systemEntrySearched = false; 165 } 166 /** 167 * Constructs a group entry. 168 * @param catalog The catalog this GroupEntry belongs 169 * @param base The baseURI attribute 170 * @param attributes The attributes 171 */ 172 public GroupEntry(CatalogImpl catalog, String base, String... attributes) { 173 super(CatalogEntryType.GROUP, base); 174 setPrefer(attributes[ATTRIBUTE_PREFER]); 175 this.catalog = catalog; 176 } 177 178 /** 179 * Adds an entry. 180 * 181 * @param entry The entry to be added. 182 */ 183 public void addEntry(BaseEntry entry) { 184 entries.add(entry); 185 } 186 187 /** 188 * Sets the prefer property. If the value is null or empty, or any String 189 * other than the defined, it will be assumed as the default value. 190 * 191 * @param value The value of the prefer attribute 192 */ 193 public final void setPrefer(String value) { 194 isPreferPublic = PreferType.PUBLIC.prefer(value); 195 } 196 197 /** 198 * Queries the prefer attribute 199 * 200 * @return true if the prefer attribute is set to system, false if not. 201 */ 202 public boolean isPreferPublic() { 203 return isPreferPublic; 204 } 205 206 /** 207 * Attempt to find a matching entry in the catalog by systemId. 208 * 209 * <p> 210 * The method searches through the system-type entries, including system, 211 * rewriteSystem, systemSuffix, delegateSystem, and group entries in the 212 * current catalog in order to find a match. 213 * 214 * 215 * @param systemId The system identifier of the external entity being 216 * referenced. 217 * 218 * @return a URI string if a mapping is found, or null otherwise. 219 */ 220 public String matchSystem(String systemId) { 221 systemEntrySearched = true; 222 String match = null; 223 for (BaseEntry entry : entries) { 224 switch (entry.type) { 225 case SYSTEM: 226 match = ((SystemEntry) entry).match(systemId); 227 //if there's a matching system entry, use it 228 if (match != null) { 229 isInstantMatch = true; 230 return match; 231 } 232 break; 233 case REWRITESYSTEM: 234 match = ((RewriteSystem) entry).match(systemId, longestRewriteMatch); 235 if (match != null) { 236 rewriteMatch = match; 237 longestRewriteMatch = ((RewriteSystem) entry).getSystemIdStartString().length(); 238 } 239 break; 240 case SYSTEMSUFFIX: 241 match = ((SystemSuffix) entry).match(systemId, longestSuffixMatch); 242 if (match != null) { 243 suffixMatch = match; 244 longestSuffixMatch = ((SystemSuffix) entry).getSystemIdSuffix().length(); 245 } 246 break; 247 case GROUP: 248 GroupEntry grpEntry = (GroupEntry) entry; 249 match = grpEntry.matchSystem(systemId); 250 if (grpEntry.isInstantMatch) { 251 //use it if there is a match of the system type 252 return match; 253 } else if (grpEntry.longestRewriteMatch > longestRewriteMatch) { 254 longestRewriteMatch = grpEntry.longestRewriteMatch; 255 rewriteMatch = match; 256 } else if (grpEntry.longestSuffixMatch > longestSuffixMatch) { 257 longestSuffixMatch = grpEntry.longestSuffixMatch; 258 suffixMatch = match; 259 } 260 break; 261 } 262 } 263 264 if (longestRewriteMatch > 0) { 265 return rewriteMatch; 266 } else if (longestSuffixMatch > 0) { 267 return suffixMatch; 268 } 269 270 //if no single match is found, try delegates 271 return matchDelegate(CatalogEntryType.DELEGATESYSTEM, systemId); 272 } 273 274 /** 275 * Attempt to find a matching entry in the catalog by publicId. 276 * 277 * <p> 278 * The method searches through the public-type entries, including public, 279 * delegatePublic, and group entries in the current catalog in order to find 280 * a match. 281 * 282 * 283 * @param publicId The public identifier of the external entity being 284 * referenced. 285 * 286 * @return a URI string if a mapping is found, or null otherwise. 287 */ 288 public String matchPublic(String publicId) { 289 /* 290 When both public and system identifiers are specified, and prefer is 291 not public (that is, system), only system entry will be used. 292 */ 293 if (!isPreferPublic && systemEntrySearched) { 294 return null; 295 } 296 //match public entries 297 String match = null; 298 for (BaseEntry entry : entries) { 299 switch (entry.type) { 300 case PUBLIC: 301 match = ((PublicEntry) entry).match(publicId); 302 break; 303 case URI: 304 match = ((UriEntry) entry).match(publicId); 305 break; 306 case GROUP: 307 match = ((GroupEntry) entry).matchPublic(publicId); 308 break; 309 } 310 if (match != null) { 311 return match; 312 } 313 } 314 315 //if no single match is found, try delegates 316 return matchDelegate(CatalogEntryType.DELEGATEPUBLIC, publicId); 317 } 318 319 /** 320 * Attempt to find a matching entry in the catalog by the uri element. 321 * 322 * <p> 323 * The method searches through the uri-type entries, including uri, 324 * rewriteURI, uriSuffix, delegateURI and group entries in the current 325 * catalog in order to find a match. 326 * 327 * 328 * @param uri The URI reference of a resource. 329 * 330 * @return a URI string if a mapping is found, or null otherwise. 331 */ 332 public String matchURI(String uri) { 333 String match = null; 334 for (BaseEntry entry : entries) { 335 switch (entry.type) { 336 case URI: 337 match = ((UriEntry) entry).match(uri); 338 if (match != null) { 339 isInstantMatch = true; 340 return match; 341 } 342 break; 343 case REWRITEURI: 344 match = ((RewriteUri) entry).match(uri, longestRewriteMatch); 345 if (match != null) { 346 rewriteMatch = match; 347 longestRewriteMatch = ((RewriteUri) entry).getURIStartString().length(); 348 } 349 break; 350 case URISUFFIX: 351 match = ((UriSuffix) entry).match(uri, longestSuffixMatch); 352 if (match != null) { 353 suffixMatch = match; 354 longestSuffixMatch = ((UriSuffix) entry).getURISuffix().length(); 355 } 356 break; 357 case GROUP: 358 GroupEntry grpEntry = (GroupEntry) entry; 359 match = grpEntry.matchURI(uri); 360 if (grpEntry.isInstantMatch) { 361 //use it if there is a match of the uri type 362 return match; 363 } else if (grpEntry.longestRewriteMatch > longestRewriteMatch) { 364 rewriteMatch = match; 365 } else if (grpEntry.longestSuffixMatch > longestSuffixMatch) { 366 suffixMatch = match; 367 } 368 break; 369 } 370 } 371 372 if (longestRewriteMatch > 0) { 373 return rewriteMatch; 374 } else if (longestSuffixMatch > 0) { 375 return suffixMatch; 376 } 377 378 //if no single match is found, try delegates 379 return matchDelegate(CatalogEntryType.DELEGATEURI, uri); 380 } 381 382 /** 383 * Matches delegatePublic or delegateSystem against the specified id 384 * 385 * @param isSystem The flag to indicate whether the delegate is system or 386 * public 387 * @param id The system or public id to be matched 388 * @return The URI string if a mapping is found, or null otherwise. 389 */ 390 private String matchDelegate(CatalogEntryType type, String id) { 391 String match = null; 392 int longestMatch = 0; 393 URI catalogId = null; 394 URI temp; 395 396 //Check delegate types in the current catalog 397 for (BaseEntry entry : entries) { 398 if (entry.type == type) { 399 if (type == CatalogEntryType.DELEGATESYSTEM) { 400 temp = ((DelegateSystem)entry).matchURI(id, longestMatch); 401 } else if (type == CatalogEntryType.DELEGATEPUBLIC) { 402 temp = ((DelegatePublic)entry).matchURI(id, longestMatch); 403 } else { 404 temp = ((DelegateUri)entry).matchURI(id, longestMatch); 405 } 406 if (temp != null) { 407 longestMatch = entry.getMatchId().length(); 408 catalogId = temp; 409 } 410 } 411 } 412 413 //Check delegate Catalogs 414 if (catalogId != null) { 415 Catalog delegateCatalog = loadCatalog(catalogId); 416 417 if (delegateCatalog != null) { 418 if (type == CatalogEntryType.DELEGATESYSTEM) { 419 match = delegateCatalog.matchSystem(id); 420 } else if (type == CatalogEntryType.DELEGATEPUBLIC) { 421 match = delegateCatalog.matchPublic(id); 422 } else { 423 match = delegateCatalog.matchURI(id); 424 } 425 } 426 } 427 428 return match; 429 } 430 431 /** 432 * Loads all delegate catalogs. 433 */ 434 void loadDelegateCatalogs() { 435 entries.stream() 436 .filter((entry) -> (entry.type == CatalogEntryType.DELEGATESYSTEM || 437 entry.type == CatalogEntryType.DELEGATEPUBLIC || 438 entry.type == CatalogEntryType.DELEGATEURI)) 439 .map((entry) -> (AltCatalog)entry) 440 .forEach((altCatalog) -> { 441 loadCatalog(altCatalog.getCatalogURI()); 442 }); 443 } 444 445 /** 446 * Loads a delegate catalog by the catalogId specified. 447 * @param catalogId the catalog Id 448 */ 449 Catalog loadCatalog(URI catalogURI) { 450 CatalogImpl delegateCatalog = null; 451 if (catalogURI != null) { 452 String catalogId = catalogURI.toASCIIString(); 453 delegateCatalog = getLoadedCatalog(catalogId); 454 if (delegateCatalog == null) { 455 if (verifyCatalogFile(catalogURI)) { 456 delegateCatalog = new CatalogImpl(catalog, features, catalogURI); 457 delegateCatalog.load(); 458 delegateCatalogs.put(catalogId, delegateCatalog); 459 } 460 } 461 } 462 463 return delegateCatalog; 464 } 465 466 /** 467 * Returns a previously loaded Catalog object if found. 468 * 469 * @param catalogId The systemId of a catalog 470 * @return a Catalog object previously loaded, or null if none in the saved 471 * list 472 */ 473 CatalogImpl getLoadedCatalog(String catalogId) { 474 CatalogImpl c = null; 475 476 //checl delegate Catalogs 477 c = delegateCatalogs.get(catalogId); 478 if (c == null) { 479 //check other loaded Catalogs 480 c = loadedCatalogs.get(catalogId); 481 } 482 483 return c; 484 } 485 486 487 /** 488 * Verifies that the catalog file represented by the catalogId exists. If it 489 * doesn't, returns false to ignore it as specified in the Catalog 490 * specification, section 8. Resource Failures. 491 * <p> 492 * Verifies that the catalog represented by the catalogId has not been 493 * searched or is not circularly referenced. 494 * 495 * @param catalogId The URI to a catalog 496 * @throws CatalogException if circular reference is found. 497 * @return true if the catalogId passed verification, false otherwise 498 */ 499 final boolean verifyCatalogFile(URI catalogURI) { 500 if (catalogURI == null) { 501 return false; 502 } 503 504 //Ignore it if it doesn't exist 505 if (Util.isFileUri(catalogURI) && 506 !Util.isFileUriExist(catalogURI, false)) { 507 return false; 508 } 509 510 String catalogId = catalogURI.toASCIIString(); 511 if (catalogsSearched.contains(catalogId) || isCircular(catalogId)) { 512 CatalogMessages.reportRunTimeError(CatalogMessages.ERR_CIRCULAR_REFERENCE, 513 new Object[]{CatalogMessages.sanitize(catalogId)}); 514 } 515 516 return true; 517 } 518 519 /** 520 * Checks whether the catalog is circularly referenced 521 * @param systemId the system identifier of the catalog to be loaded 522 * @return true if is circular, false otherwise 523 */ 524 boolean isCircular(String systemId) { 525 if (parent == null) { 526 return false; 527 } 528 529 if (parent.systemId.equals(systemId)) { 530 return true; 531 } 532 533 return parent.isCircular(systemId); 534 } 535 }