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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package catalog;
  24 
  25 import java.io.File;
  26 import java.io.FileInputStream;
  27 import java.io.IOException;
  28 import java.io.InputStream;
  29 import java.io.StringReader;
  30 import java.io.StringWriter;
  31 import java.net.URI;
  32 import java.nio.file.Paths;
  33 import javax.xml.XMLConstants;
  34 import javax.xml.catalog.Catalog;
  35 import javax.xml.catalog.CatalogException;
  36 import javax.xml.catalog.CatalogFeatures;
  37 import javax.xml.catalog.CatalogFeatures.Feature;
  38 import javax.xml.catalog.CatalogManager;
  39 import javax.xml.catalog.CatalogResolver;
  40 import javax.xml.parsers.ParserConfigurationException;
  41 import javax.xml.parsers.SAXParser;
  42 import javax.xml.parsers.SAXParserFactory;
  43 import javax.xml.stream.XMLInputFactory;
  44 import javax.xml.stream.XMLStreamConstants;
  45 import javax.xml.stream.XMLStreamReader;
  46 import javax.xml.transform.Source;
  47 import javax.xml.transform.Transformer;
  48 import javax.xml.transform.TransformerFactory;
  49 import javax.xml.transform.sax.SAXSource;
  50 import javax.xml.transform.stream.StreamResult;
  51 import javax.xml.transform.stream.StreamSource;
  52 import javax.xml.validation.Schema;
  53 import javax.xml.validation.SchemaFactory;
  54 import javax.xml.validation.Validator;
  55 import static jaxp.library.JAXPTestUtilities.clearSystemProperty;
  56 import static jaxp.library.JAXPTestUtilities.setSystemProperty;
  57 import org.testng.Assert;
  58 import org.testng.annotations.BeforeClass;
  59 import org.testng.annotations.DataProvider;
  60 import org.testng.annotations.Listeners;
  61 import org.testng.annotations.Test;
  62 import org.xml.sax.Attributes;
  63 import org.xml.sax.ErrorHandler;
  64 import org.xml.sax.InputSource;
  65 import org.xml.sax.SAXException;
  66 import org.xml.sax.XMLReader;
  67 import org.xml.sax.ext.DefaultHandler2;
  68 
  69 /*
  70  * @test
  71  * @bug 8081248 8144966 8146606 8146237 8150969 8151162 8152527 8154220 8163232
  72  * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest
  73  * @run testng/othervm -DrunSecMngr=true catalog.CatalogTest
  74  * @run testng/othervm catalog.CatalogTest
  75  * @summary Tests basic Catalog functions.
  76  */
  77 @Listeners({jaxp.library.FilePolicy.class})
  78 public class CatalogTest extends CatalogSupportBase {
  79     static final String KEY_FILES = "javax.xml.catalog.files";
  80 
  81 
  82     /*
  83      * Initializing fields
  84      */
  85     @BeforeClass
  86     public void setUpClass() throws Exception {
  87         super.setUp();
  88     }
  89 
  90     /*
  91      * @bug 8162431
  92      * Verifies that circular references are caught and
  93      * CatalogException is thrown.
  94      */
  95     @Test(dataProvider = "getFeatures", expectedExceptions = CatalogException.class)
  96     public void testCircularRef(CatalogFeatures cf, String xml) throws Exception {
  97         CatalogResolver catalogResolver = CatalogManager.catalogResolver(
  98                 cf,
  99                 getClass().getResource(xml).toURI());
 100         catalogResolver.resolve("anyuri", "");
 101     }
 102 
 103     /*
 104        DataProvider: used to verify circular reference
 105         Data columns: CatalogFeatures, catalog
 106      */
 107     @DataProvider(name = "getFeatures")
 108     public Object[][] getFeatures() {
 109         String self = "catalogReferCircle-itself.xml";
 110         String left = "catalogReferCircle-left.xml";
 111         return new Object[][]{
 112             {CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "false").build(), self},
 113             {CatalogFeatures.defaults(), self},
 114             {CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "false").build(), left},
 115             {CatalogFeatures.defaults(), left}
 116         };
 117     }
 118 
 119     /*
 120      * @bug 8163232
 121      * Verifies that the CatalogResolver supports the following XML Resolvers:
 122           javax.xml.stream.XMLResolver
 123           javax.xml.transform.URIResolver
 124           org.w3c.dom.ls.LSResourceResolver
 125           org.xml.sax.EntityResolver
 126      *
 127      * Plus, system and uri entries can equally be used.
 128      */
 129 
 130     /*
 131      * Verifies the support for org.xml.sax.EntityResolver.
 132      * Expected: the parser returns the expected string.
 133     */
 134     @Test(dataProvider = "supportXMLResolver")
 135     public void supportEntityResolver(URI catalogFile, String xml, String expected) throws Exception {
 136         String xmlSource = getClass().getResource(xml).getFile();
 137 
 138         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 139         MyCatalogHandler handler = new MyCatalogHandler(cr, elementInSystem);
 140         SAXParser parser = getSAXParser(false, true, null);
 141         parser.parse(xmlSource, handler);
 142 
 143         Assert.assertEquals(handler.getResult().trim(), expected);
 144     }
 145 
 146     /*
 147      * Verifies the support for javax.xml.stream.XMLResolver.
 148      * Expected: the parser returns the expected string.
 149     */
 150     @Test(dataProvider = "supportXMLResolver")
 151     public void supportXMLResolver(URI catalogFile, String xml, String expected) throws Exception {
 152         String xmlSource = getClass().getResource(xml).getFile();
 153 
 154         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 155 
 156         XMLInputFactory xifactory = XMLInputFactory.newInstance();
 157         xifactory.setProperty(XMLInputFactory.IS_COALESCING, true);
 158         xifactory.setProperty(XMLInputFactory.RESOLVER, cr);
 159         File file = new File(xmlSource);
 160         String systemId = file.toURI().toASCIIString();
 161         InputStream entityxml = new FileInputStream(file);
 162         XMLStreamReader streamReader = xifactory.createXMLStreamReader(systemId, entityxml);
 163         String result = null;
 164         while (streamReader.hasNext()) {
 165             int eventType = streamReader.next();
 166             if (eventType == XMLStreamConstants.START_ELEMENT) {
 167                 eventType = streamReader.next();
 168                 if (eventType == XMLStreamConstants.CHARACTERS) {
 169                     result = streamReader.getText();
 170                 }
 171             }
 172         }
 173         System.out.println(": expected [" + expected + "] <> actual [" + result.trim() + "]");
 174 
 175         Assert.assertEquals(result.trim(), expected);
 176     }
 177 
 178     /*
 179      * Verifies the support for org.w3c.dom.ls.LSResourceResolver by ShemaFactory.
 180      * Success: parsing goes through with no error
 181      * Fail: throws Exception if references are not resolved (by the CatalogResolver)
 182     */
 183     @Test(dataProvider = "supportLSResourceResolver")
 184     public void supportLSResourceResolver(URI catalogFile, Source schemaSource) throws SAXException {
 185 
 186         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 187 
 188         SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 189         factory.setResourceResolver(cr);
 190         Schema schema = factory.newSchema(schemaSource);
 191 
 192     }
 193 
 194     /*
 195      * Verifies the support for org.w3c.dom.ls.LSResourceResolver by Validator.
 196      * Success: parsing goes through with no error
 197      * Fail: throws Exception if references are not resolved (by the CatalogResolver)
 198     */
 199     @Test(dataProvider = "supportLSResourceResolver1")
 200     public void supportLSResourceResolver1(URI catalogFile, Source source) throws Exception {
 201 
 202         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 203 
 204         SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
 205         Validator validator = factory.newSchema().newValidator();
 206         validator.setResourceResolver(cr);
 207         validator.validate(source);
 208     }
 209 
 210     /*
 211      * Verifies the support for javax.xml.transform.URIResolver.
 212      * Success: parsing goes through with no error
 213      * Fail: throws Exception if references are not resolved (by the CatalogResolver)
 214     */
 215     @Test(dataProvider = "supportURIResolver")
 216     public void supportURIResolver(URI catalogFile, Source xsl, Source xml, String expected) throws Exception {
 217 
 218         CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 219 
 220             TransformerFactory factory = TransformerFactory.newInstance();
 221             factory.setURIResolver(cr);
 222             Transformer transformer = factory.newTransformer(xsl);
 223             StringWriter out = new StringWriter();
 224             transformer.transform(xml, new StreamResult(out));
 225             if (expected != null) {
 226                 Assert.assertTrue(out.toString().contains(expected), "supportURIResolver");
 227             }
 228     }
 229 
 230     /*
 231        DataProvider: used to verify the support of XML Resolvers.
 232         Data columns:
 233         catalog filepath, xml source file, expected result
 234      */
 235     @DataProvider(name = "supportXMLResolver")
 236     public Object[][] supportXMLResolver() throws Exception {
 237         URI catalogFile = getClass().getResource("catalog.xml").toURI();
 238         URI catalogFileUri = getClass().getResource("catalog_uri.xml").toURI();
 239 
 240         return new Object[][]{
 241             {catalogFile, "system.xml", "Test system entry"},
 242             {catalogFile, "rewritesystem.xml", "Test rewritesystem entry"},
 243             {catalogFile, "rewritesystem1.xml", "Test rewritesystem entry"},
 244             {catalogFile, "systemsuffix.xml", "Test systemsuffix entry"},
 245             {catalogFile, "delegatesystem.xml", "Test delegatesystem entry"},
 246             {catalogFile, "public.xml", "Test public entry"},
 247             {catalogFile, "delegatepublic.xml", "Test delegatepublic entry"},
 248             // using uri entries
 249             {catalogFileUri, "system.xml", "Test system entry"},
 250             {catalogFileUri, "rewritesystem.xml", "Test rewritesystem entry"},
 251             {catalogFileUri, "rewritesystem1.xml", "Test rewritesystem entry"},
 252             {catalogFileUri, "systemsuffix.xml", "Test systemsuffix entry"},
 253             {catalogFileUri, "delegateuri.xml", "Test delegateuri entry"},
 254             {catalogFileUri, "public.xml", "Test public entry"},
 255          };
 256     }
 257 
 258     /*
 259        DataProvider: used to verify the support of LSResourceResolver by SchemaFactory.
 260         Data columns:
 261         catalog filepath, schema source file
 262      */
 263     @DataProvider(name = "supportLSResourceResolver")
 264     public Object[][] supportLSResourceResolver() throws Exception {
 265         URI catalogFile = getClass().getResource("CatalogSupport.xml").toURI();
 266         URI catalogFileUri = getClass().getResource("CatalogSupport_uri.xml").toURI();
 267 
 268         /*
 269          * XMLSchema.xsd has a reference to XMLSchema.dtd which in turn refers to
 270          * datatypes.dtd
 271         */
 272         return new Object[][]{
 273             {catalogFile, new StreamSource(new StringReader(xsd_xmlSchema))},
 274             {catalogFile, new StreamSource(new StringReader(xsd_xmlSchema_import))},
 275             {catalogFile, new StreamSource(new StringReader(xsd_include_company))},
 276             {catalogFileUri, new StreamSource(new StringReader(xsd_xmlSchema))},
 277             {catalogFileUri, new StreamSource(new StringReader(xsd_xmlSchema_import))},
 278             {catalogFileUri, new StreamSource(new StringReader(xsd_include_company))},
 279          };
 280     }
 281 
 282     /*
 283        DataProvider: used to verify the support of LSResourceResolver by Validator.
 284         Data columns:
 285         catalog filepath, source file
 286      */
 287     @DataProvider(name = "supportLSResourceResolver1")
 288     public Object[][] supportLSResourceResolver1() throws Exception {
 289         URI catalogFile = getClass().getResource("CatalogSupport.xml").toURI();
 290         URI catalogFileUri = getClass().getResource("CatalogSupport_uri.xml").toURI();
 291 
 292         /*
 293          * val_test.xml has a reference to system.dtd and val_test.xsd
 294         */
 295         SAXSource ss = new SAXSource(new InputSource(xml_val_test));
 296         ss.setSystemId(xml_val_test_id);
 297 
 298         return new Object[][]{
 299             {catalogFile, ss},
 300             {catalogFileUri, ss},
 301          };
 302     }
 303 
 304 
 305     /*
 306        DataProvider: used to verify the support of LSResourceResolver by Validator.
 307         Data columns:
 308         catalog filepath, xsl source, xml source file
 309      */
 310     @DataProvider(name = "supportURIResolver")
 311     public Object[][] supportURIResolver() throws Exception {
 312         URI catalogFile = getClass().getResource("CatalogSupport.xml").toURI();
 313         URI catalogFileUri = getClass().getResource("CatalogSupport_uri.xml").toURI();
 314         SAXSource xslSource = new SAXSource(new InputSource(new File(xsl_doc).toURI().toASCIIString()));
 315 
 316         /*
 317          * val_test.xml has a reference to system.dtd and val_test.xsd
 318         */
 319         SAXSource ss = new SAXSource(new InputSource(xml_val_test));
 320         ss.setSystemId(xml_val_test_id);
 321 
 322         return new Object[][]{
 323             {catalogFile, new SAXSource(new InputSource(new File(xsl_doc).toURI().toASCIIString())),
 324                 new StreamSource(new File(xml_doc)), "Resolved by a catalog"},
 325             {catalogFileUri, new SAXSource(new InputSource(new StringReader(xsl_include))),
 326                 new StreamSource(new StringReader(xml_xsl)), null},
 327          };
 328     }
 329 
 330     /*
 331      * @bug 8150187
 332      * NPE is expected if the systemId is null. The specification for systemId
 333      * is as follows:
 334      * A system identifier is required on all external entities. XML
 335      * requires a system identifier on all external entities, so this value is
 336      * always specified.
 337      */
 338     @Test(expectedExceptions = NullPointerException.class)
 339     public void sysIdCantBeNull() {
 340         CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults());
 341         InputSource is = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", null);
 342     }
 343 
 344     /*
 345      * @bug 8156845
 346      * Verifies that an URI reference with a urn:publicid is correctly resolved
 347      * with an uri entry with a publicId.
 348      *
 349      * @param expectedFile is not used in this test, it's kept since we're
 350      * copying the JCK test and its dataProvider. This test may be reused for
 351      * other cases in that test.
 352      */
 353     @Test(dataProvider = "resolveUri")
 354     public void testMatch1(String cFile, String href, String expectedFile,
 355             String expectedUri, String msg) throws Exception {
 356         URI catalogFile = getClass().getResource(cFile).toURI();
 357         CatalogResolver cur = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalogFile);
 358         Source source = cur.resolve(href, null);
 359         Assert.assertNotNull(source, "Source returned is null");
 360         Assert.assertEquals(expectedUri, source.getSystemId(), msg);
 361     }
 362 
 363     /*
 364      * @bug 8154220
 365      * Verifies that the file input is validated properly. Valid input includes
 366      * multiple file paths separated by semicolon.
 367      */
 368     @Test(dataProvider = "hierarchyOfCatFilesData")
 369     public void hierarchyOfCatFiles2(String systemId, String expectedUri) {
 370         String file1 = getClass().getResource("first_cat.xml").toExternalForm();
 371         String file2 = getClass().getResource("second_cat.xml").toExternalForm();
 372         String files = file1 + ";" + file2;
 373 
 374         try {
 375             setSystemProperty(KEY_FILES, files);
 376             CatalogResolver catalogResolver = CatalogManager.catalogResolver(CatalogFeatures.defaults());
 377             String sysId = catalogResolver.resolveEntity(null, systemId).getSystemId();
 378             Assert.assertEquals(sysId, Paths.get(filepath + expectedUri).toUri().toString().replace("///", "/"),
 379                     "System ID match not right");
 380         } finally {
 381             clearSystemProperty(KEY_FILES);
 382         }
 383 
 384     }
 385 
 386     /*
 387      * @bug 8152527
 388      * This test is the same as the JDK test ResolveEntityTests:testMatch1.
 389      * Verifies that the CatalogResolver resolves a publicId and/or systemId as
 390      * expected.
 391      */
 392     @Test(dataProvider = "resolveEntity")
 393     public void testMatch1(String cfile, String prefer, String sysId, String pubId,
 394             String expectedUri, String expectedFile, String msg) throws Exception {
 395         URI catalogFile = getClass().getResource(cfile).toURI();
 396         CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build();
 397         CatalogResolver catalogResolver = CatalogManager.catalogResolver(features, catalogFile);
 398         InputSource is = catalogResolver.resolveEntity(pubId, sysId);
 399         Assert.assertNotNull(is, msg);
 400         String expected = (expectedUri == null) ? expectedFile : expectedUri;
 401         Assert.assertEquals(expected, is.getSystemId(), msg);
 402     }
 403 
 404     /*
 405      * @bug 8151162
 406      * Verifies that the Catalog matches specified publicId or systemId and returns
 407      * results as expected.
 408      */
 409     @Test(dataProvider = "matchWithPrefer")
 410     public void matchWithPrefer(String prefer, String cfile, String publicId,
 411             String systemId, String expected) throws Exception {
 412         URI catalogFile = getClass().getResource(cfile).toURI();
 413         Catalog c = CatalogManager.catalog(
 414                 CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).build(),
 415                 catalogFile);
 416         String result;
 417         if (publicId != null && publicId.length() > 0) {
 418             result = c.matchPublic(publicId);
 419         } else {
 420             result = c.matchSystem(systemId);
 421         }
 422         Assert.assertEquals(expected, result);
 423     }
 424 
 425     /*
 426      * @bug 8151162
 427      * Verifies that the CatalogResolver resolves specified publicId or systemId
 428      * in accordance with the prefer setting.
 429      * prefer "system": resolves with a system entry.
 430      *                  Exception: use the public entry when the catalog contains
 431      *                  only public entry and only publicId is specified.
 432      * prefer "public": attempts to resolve with a system entry;
 433      *                  attempts to resolve with a public entry if no matching
 434      *                  system entry is found.
 435      */
 436     @Test(dataProvider = "resolveWithPrefer")
 437     public void resolveWithPrefer(String prefer, String cfile, String publicId,
 438             String systemId, String expected) throws Exception {
 439         URI catalogFile = getClass().getResource(cfile).toURI();
 440         CatalogFeatures f = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, prefer).with(CatalogFeatures.Feature.RESOLVE, "ignore").build();
 441         CatalogResolver catalogResolver = CatalogManager.catalogResolver(f, catalogFile);
 442         String result = catalogResolver.resolveEntity(publicId, systemId).getSystemId();
 443         Assert.assertEquals(expected, result);
 444     }
 445 
 446     /**
 447      * @bug 8150969
 448      * Verifies that the defer attribute set in the catalog file takes precedence
 449      * over other settings, in which case, whether next and delegate Catalogs will
 450      * be loaded is determined by the defer attribute.
 451      */
 452     @Test(dataProvider = "invalidAltCatalogs", expectedExceptions = CatalogException.class)
 453     public void testDeferAltCatalogs(String file) throws Exception {
 454         URI catalogFile = getClass().getResource(file).toURI();
 455         CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.DEFER, "true").build();
 456         /*
 457           Since the defer attribute is set to false in the specified catalog file,
 458           the parent catalog will try to load the alt catalog, which will fail
 459           since it points to an invalid catalog.
 460         */
 461         Catalog catalog = CatalogManager.catalog(features, catalogFile);
 462     }
 463 
 464 
 465     /**
 466      * @bug 8146237
 467      * PREFER from Features API taking precedence over catalog file
 468      */
 469     @Test
 470     public void testJDK8146237() throws Exception {
 471         URI catalogFile = getClass().getResource("JDK8146237_catalog.xml").toURI();
 472 
 473         try {
 474             CatalogFeatures features = CatalogFeatures.builder().with(CatalogFeatures.Feature.PREFER, "system").build();
 475             Catalog catalog = CatalogManager.catalog(features, catalogFile);
 476             CatalogResolver catalogResolver = CatalogManager.catalogResolver(catalog);
 477             String actualSystemId = catalogResolver.resolveEntity("-//FOO//DTD XML Dummy V0.0//EN", "http://www.oracle.com/alt1sys.dtd").getSystemId();
 478             Assert.assertTrue(actualSystemId.contains("dummy.dtd"), "Resulting id should contain dummy.dtd, indicating a match by publicId");
 479 
 480         } catch (Exception e) {
 481             Assert.fail(e.getMessage());
 482         }
 483     }
 484 
 485     /*
 486        @bug 8146606
 487        Verifies that the resulting systemId does not contain duplicate slashes
 488     */
 489     @Test
 490     public void testRewriteSystem() throws Exception {
 491         URI catalog = getClass().getResource("rewriteCatalog.xml").toURI();
 492 
 493         try {
 494             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 495             String actualSystemId = resolver.resolveEntity(null, "http://remote.com/dtd/book.dtd").getSystemId();
 496             Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes");
 497         } catch (Exception e) {
 498             Assert.fail(e.getMessage());
 499         }
 500 
 501     }
 502 
 503     /*
 504        @bug 8146606
 505        Verifies that the resulting systemId does not contain duplicate slashes
 506     */
 507     @Test
 508     public void testRewriteUri() throws Exception {
 509         URI catalog = getClass().getResource("rewriteCatalog.xml").toURI();
 510 
 511         try {
 512 
 513             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 514             String actualSystemId = resolver.resolve("http://remote.com/import/import.xsl", null).getSystemId();
 515             Assert.assertTrue(!actualSystemId.contains("//"), "result contains duplicate slashes");
 516         } catch (Exception e) {
 517             Assert.fail(e.getMessage());
 518         }
 519     }
 520 
 521     /*
 522        @bug 8144966
 523        Verifies that passing null as CatalogFeatures will result in a NPE.
 524     */
 525     @Test(expectedExceptions = NullPointerException.class)
 526     public void testFeatureNull() {
 527         CatalogResolver resolver = CatalogManager.catalogResolver(null, null);
 528 
 529     }
 530 
 531     /*
 532        @bug 8144966
 533        Verifies that passing null as the URI will result in a NPE.
 534     */
 535     @Test(expectedExceptions = NullPointerException.class)
 536     public void testPathNull() {
 537         URI uri = null;
 538         CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), uri);
 539     }
 540 
 541     /*
 542        Tests basic catalog feature by using a CatalogResolver instance to
 543     resolve a DTD reference to a locally specified DTD file. If the resolution
 544     is successful, the Handler shall return the value of the entity reference
 545     that matches the expected value.
 546      */
 547     @Test(dataProvider = "catalog")
 548     public void testCatalogResolver(String test, String expected, String catalogFile,
 549             String xml, SAXParser saxParser) throws Exception {
 550         URI catalog = null;
 551         if (catalogFile != null) {
 552             catalog = getClass().getResource(catalogFile).toURI();
 553         }
 554         String url = getClass().getResource(xml).getFile();
 555         try {
 556             CatalogResolver cr = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 557             XMLReader reader = saxParser.getXMLReader();
 558             reader.setEntityResolver(cr);
 559             MyHandler handler = new MyHandler(saxParser);
 560             reader.setContentHandler(handler);
 561             reader.parse(url);
 562             System.out.println(test + ": expected [" + expected + "] <> actual [" + handler.getResult() + "]");
 563             Assert.assertEquals(handler.getResult(), expected);
 564         } catch (SAXException | IOException e) {
 565             Assert.fail(e.getMessage());
 566         }
 567     }
 568 
 569     /*
 570        Verifies that when there's no match, in this case only an invalid
 571     catalog is provided, the resolver will throw an exception by default.
 572     */
 573     @Test
 574     public void testInvalidCatalog() throws Exception {
 575         URI catalog = getClass().getResource("catalog_invalid.xml").toURI();
 576 
 577         String test = "testInvalidCatalog";
 578         try {
 579             CatalogResolver resolver = CatalogManager.catalogResolver(CatalogFeatures.defaults(), catalog);
 580             String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId();
 581         } catch (Exception e) {
 582             String msg = e.getMessage();
 583             if (msg != null) {
 584                 if (msg.contains("No match found for publicId")) {
 585                     Assert.assertEquals(msg, "No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.");
 586                     System.out.println(test + ": expected [No match found for publicId 'null' and systemId 'http://remote/xml/dtd/sys/alice/docAlice.dtd'.]");
 587                     System.out.println("actual [" + msg + "]");
 588                 }
 589             }
 590         }
 591     }
 592 
 593     /*
 594        Verifies that if resolve is "ignore", an empty InputSource will be returned
 595     when there's no match. The systemId is then null.
 596     */
 597     @Test
 598     public void testIgnoreInvalidCatalog() {
 599         String catalog = getClass().getResource("catalog_invalid.xml").toExternalForm();
 600         CatalogFeatures f = CatalogFeatures.builder()
 601                 .with(Feature.FILES, catalog)
 602                 .with(Feature.PREFER, "public")
 603                 .with(Feature.DEFER, "true")
 604                 .with(Feature.RESOLVE, "ignore")
 605                 .build();
 606 
 607         String test = "testInvalidCatalog";
 608         try {
 609             CatalogResolver resolver = CatalogManager.catalogResolver(f);
 610             String actualSystemId = resolver.resolveEntity(null, "http://remote/xml/dtd/sys/alice/docAlice.dtd").getSystemId();
 611             System.out.println("testIgnoreInvalidCatalog: expected [null]");
 612             System.out.println("testIgnoreInvalidCatalog: expected [null]");
 613             System.out.println("actual [" + actualSystemId + "]");
 614             Assert.assertEquals(actualSystemId, null);
 615         } catch (Exception e) {
 616             Assert.fail(e.getMessage());
 617         }
 618     }
 619 
 620 
 621     /*
 622         DataProvider: used to verify CatalogResolver's resolve function.
 623         Data columns:
 624         catalog, uri or publicId, expectedFile, expectedUri, msg
 625 
 626         This DataProvider is copied from JCK ResolveTests' dataMatch1
 627      */
 628     @DataProvider(name = "resolveUri")
 629     public Object[][] getDataForUriResolver() {
 630         return new Object[][]{
 631             {"uri.xml", "urn:publicid:-:Acme,+Inc.:DTD+Book+Version+1.0", null, "http://local/base/dtd/book.dtd", "Uri in publicId namespace is incorrectly unwrapped"},
 632         };
 633     }
 634 
 635     /*
 636         DataProvider: used to verify hierarchical catalogs. Refer to JCK test
 637     hierarchyOfCatFiles2.
 638      */
 639     @DataProvider(name = "hierarchyOfCatFilesData")
 640     public Object[][] getHierarchyOfCatFilesData() {
 641         return new Object[][]{
 642             {"http://www.oracle.com/sequence.dtd", "first.dtd"},
 643             {"http://www.oracle.com/sequence_next.dtd", "next.dtd"},
 644             {"http://www.oracle.com/sequence_second.dtd", "second.dtd"}
 645         };
 646     }
 647 
 648     /*
 649         DataProvider: used to verify CatalogResolver's resolveEntity function.
 650         Data columns:
 651         catalog, prefer, systemId, publicId, expectedUri, expectedFile, msg
 652      */
 653     @DataProvider(name = "resolveEntity")
 654     public Object[][] getDataForMatchingBothIds() {
 655         String expected = "http://www.groupxmlbase.com/dtds/rewrite.dtd";
 656         return new Object[][]{
 657             {"rewriteSystem_id.xml", "system", "http://www.sys00test.com/rewrite.dtd", "PUB-404", expected, expected, "Relative rewriteSystem with xml:base at group level failed"},
 658         };
 659     }
 660 
 661     static String id = "http://openjdk.java.net/xml/catalog/dtd/system.dtd";
 662     /*
 663        DataProvider: used to verify how prefer settings affect the result of the
 664         Catalog's matching operation.
 665         Data columns:
 666         prefer, catalog, publicId, systemId, expected result
 667      */
 668     @DataProvider(name = "matchWithPrefer")
 669     public Object[][] getDataForMatch() {
 670         return new Object[][]{
 671             {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 672             {"public", "sysOnly.xml", id, "", null},
 673             {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 674             {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 675             {"system", "sysOnly.xml", id, "", null},
 676             {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 677             {"public", "pubOnly.xml", "", id, null},
 678             {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 679             {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 680             {"system", "pubOnly.xml", "", id, null},
 681             {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 682             {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 683         };
 684     }
 685 
 686     /*
 687        DataProvider: used to verify how prefer settings affect the result of the
 688         CatalogResolver's resolution operation.
 689         Data columns:
 690         prefer, catalog, publicId, systemId, expected result
 691      */
 692     @DataProvider(name = "resolveWithPrefer")
 693     public Object[][] getDataForResolve() {
 694         return new Object[][]{
 695             {"system", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 696             {"system", "pubOnly.xml", "", id, null},
 697             {"system", "pubOnly.xml", id, id, null},
 698             {"public", "pubOnly.xml", id, "", "http://local/base/dtd/public.dtd"},
 699             {"public", "pubOnly.xml", "", id, null},
 700             {"public", "pubOnly.xml", id, id, "http://local/base/dtd/public.dtd"},
 701             {"system", "sysOnly.xml", id, "", null},
 702             {"system", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 703             {"system", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"},
 704             {"public", "sysOnly.xml", id, "", null},
 705             {"public", "sysOnly.xml", "", id, "http://local/base/dtd/system.dtd"},
 706             {"public", "sysOnly.xml", id, id, "http://local/base/dtd/system.dtd"},
 707             {"system", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 708             {"system", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 709             {"system", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"},
 710             {"public", "sysAndPub.xml", id, "", "http://local/base/dtd/public.dtd"},
 711             {"public", "sysAndPub.xml", "", id, "http://local/base/dtd/system.dtd"},
 712             {"public", "sysAndPub.xml", id, id, "http://local/base/dtd/system.dtd"},
 713         };
 714     }
 715     /*
 716        DataProvider: catalogs that contain invalid next or delegate catalogs.
 717                      The defer attribute is set to false.
 718      */
 719     @DataProvider(name = "invalidAltCatalogs")
 720     public Object[][] getCatalogs() {
 721         return new Object[][]{
 722             {"defer_false_2.xml"},
 723             {"defer_del_false.xml"}
 724         };
 725     }
 726 
 727 
 728     /*
 729        DataProvider: provides test name, expected string, the catalog, and XML
 730        document.
 731      */
 732     @DataProvider(name = "catalog")
 733     public Object[][] getCatalog() {
 734         return new Object[][]{
 735             {"testSystem", "Test system entry", "catalog.xml", "system.xml", getParser()},
 736             {"testRewriteSystem", "Test rewritesystem entry", "catalog.xml", "rewritesystem.xml", getParser()},
 737             {"testRewriteSystem1", "Test rewritesystem entry", "catalog.xml", "rewritesystem1.xml", getParser()},
 738             {"testSystemSuffix", "Test systemsuffix entry", "catalog.xml", "systemsuffix.xml", getParser()},
 739             {"testDelegateSystem", "Test delegatesystem entry", "catalog.xml", "delegatesystem.xml", getParser()},
 740             {"testPublic", "Test public entry", "catalog.xml", "public.xml", getParser()},
 741             {"testDelegatePublic", "Test delegatepublic entry", "catalog.xml", "delegatepublic.xml", getParser()},
 742         };
 743     }
 744 
 745     SAXParser getParser() {
 746         SAXParser saxParser = null;
 747         try {
 748             SAXParserFactory factory = SAXParserFactory.newInstance();
 749             factory.setNamespaceAware(true);
 750             saxParser = factory.newSAXParser();
 751         } catch (ParserConfigurationException | SAXException e) {
 752         }
 753 
 754         return saxParser;
 755     }
 756 
 757     /**
 758      * SAX handler
 759      */
 760     public class MyHandler extends DefaultHandler2 implements ErrorHandler {
 761 
 762         StringBuilder textContent = new StringBuilder();
 763         SAXParser saxParser;
 764 
 765         MyHandler(SAXParser saxParser) {
 766             textContent.setLength(0);
 767             this.saxParser = saxParser;
 768         }
 769 
 770         String getResult() {
 771             return textContent.toString();
 772         }
 773 
 774         @Override
 775         public void startElement(String uri, String localName, String qName, Attributes attributes)
 776                 throws SAXException {
 777             textContent.delete(0, textContent.length());
 778             try {
 779                 System.out.println("Element: " + uri + ":" + localName + " " + qName);
 780             } catch (Exception e) {
 781                 throw new SAXException(e);
 782             }
 783 
 784         }
 785 
 786         @Override
 787         public void characters(char ch[], int start, int length) throws SAXException {
 788             textContent.append(ch, start, length);
 789         }
 790     }
 791 }