diff --git a/src/java.xml/share/classes/javax/xml/catalog/CatalogImpl.java b/src/java.xml/share/classes/javax/xml/catalog/CatalogImpl.java index 72cd64fca3d..540ddb4931f 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/CatalogImpl.java +++ b/src/java.xml/share/classes/javax/xml/catalog/CatalogImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ import static javax.xml.catalog.BaseEntry.CatalogEntryType; import static javax.xml.catalog.CatalogFeatures.DEFER_TRUE; import javax.xml.catalog.CatalogFeatures.Feature; import static javax.xml.catalog.CatalogMessages.formatMessage; +import javax.xml.catalog.CatalogResolver.NotFoundAction; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; @@ -59,8 +60,8 @@ class CatalogImpl extends GroupEntry implements Catalog { //Value of the defer attribute to determine if alternative catalogs are read boolean isDeferred = true; - //Value of the resolve attribute - ResolveType resolveType = ResolveType.STRICT; + //Value of the resolve attribute mapped to the resolver's action type + NotFoundAction resolveType = NotFoundAction.STRICT; //indicate whether the Catalog is empty boolean isEmpty; @@ -259,7 +260,7 @@ class CatalogImpl extends GroupEntry implements Catalog { * @param value The value of the resolve attribute */ public final void setResolve(String value) { - resolveType = ResolveType.getType(value); + resolveType = NotFoundAction.getType(value); } /** @@ -267,7 +268,7 @@ class CatalogImpl extends GroupEntry implements Catalog { * * @return The value of the resolve attribute */ - public final ResolveType getResolve() { + public final NotFoundAction getResolve() { return resolveType; } diff --git a/src/java.xml/share/classes/javax/xml/catalog/CatalogManager.java b/src/java.xml/share/classes/javax/xml/catalog/CatalogManager.java index f8ce697b266..7491410a0af 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/CatalogManager.java +++ b/src/java.xml/share/classes/javax/xml/catalog/CatalogManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -77,6 +77,12 @@ public final class CatalogManager { /** * Creates an instance of a {@code CatalogResolver} using the specified catalog. * + * @apiNote The {@code CatalogResolver} created by this method delegates to + * the underlying {@code catalog}'s RESOLVE property. The {@code CatalogResolver} + * created by {@link #catalogResolver(Catalog, CatalogResolver.NotFoundAction) + * catalogResover(Catalog, CatalogResolver.NotFoundAction)} is based on the + * specified action type when it is unable to resolve a reference. + * * @param catalog the catalog instance * @return an instance of a {@code CatalogResolver} */ @@ -85,6 +91,28 @@ public final class CatalogManager { return new CatalogResolverImpl(catalog); } + /** + * Creates a {@code CatalogResolver} that resolves external references with the given + * {@code catalog} and {@link CatalogResolver.NotFoundAction action} type + * that determines the behavior when unable to resolve a reference. + *

+ * The {@link CatalogResolver.NotFoundAction action} types are mapped to the values + * of the {@link CatalogFeatures.Feature#RESOLVE RESOLVE} property. + * + * @param catalog the catalog instance + * @param action the action to be taken when unable to resolve a reference + * + * @return a {@code CatalogResolver} with the {@code catalog} and {@code action} type + * + * @since 22 + */ + public static CatalogResolver catalogResolver(Catalog catalog, CatalogResolver.NotFoundAction action) { + if (catalog == null) CatalogMessages.reportNPEOnNull("catalog", null); + if (action == null) CatalogMessages.reportNPEOnNull("action", null); + + return new CatalogResolverImpl(catalog, action); + } + /** * Creates an instance of a {@code CatalogResolver} using the specified feature * settings and uri(s) to one or more catalog files. diff --git a/src/java.xml/share/classes/javax/xml/catalog/CatalogReader.java b/src/java.xml/share/classes/javax/xml/catalog/CatalogReader.java index 64b2ea85071..1305beeceaa 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/CatalogReader.java +++ b/src/java.xml/share/classes/javax/xml/catalog/CatalogReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -159,7 +159,7 @@ class CatalogReader extends DefaultHandler implements EntityResolver, URIResolve CatalogFeatures.DEFER_TRUE : CatalogFeatures.DEFER_FALSE; } if (resolve == null) { - resolve = catalog.getResolve().literal; + resolve = catalog.getResolve().toString(); } //override property settings with those from the catalog file catalog.setResolve(resolve); @@ -172,7 +172,7 @@ class CatalogReader extends DefaultHandler implements EntityResolver, URIResolve return; } else { inGroup = true; - group = new GroupEntry(catalog, base, prefer); + group = new GroupEntry(catalog, Util.getAbsoluteURI(catalog.systemId, base), prefer); catalog.addEntry(group); return; } diff --git a/src/java.xml/share/classes/javax/xml/catalog/CatalogResolver.java b/src/java.xml/share/classes/javax/xml/catalog/CatalogResolver.java index f542f429c0a..3fd4b6a7858 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/CatalogResolver.java +++ b/src/java.xml/share/classes/javax/xml/catalog/CatalogResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -235,4 +235,55 @@ public interface CatalogResolver extends EntityResolver, XMLResolver, public LSInput resolveResource(String type, String namespaceUri, String publicId, String systemId, String baseUri); + /** + * Defines the actions that a CatalogResolver may take when it is unable to + * resolve an external reference. The actions are mapped to the string values + * of the {@link CatalogFeatures.Feature#RESOLVE RESOLVE} property. + * + * @since 22 + */ + public static enum NotFoundAction { + /** + * Indicates that the processing should continue as defined by the + * {@link CatalogFeatures.Feature#RESOLVE RESOLVE} property. + */ + CONTINUE { + @Override + public String toString() { return "continue"; } + }, + /** + * Indicates that the reference is skipped as defined by the + * {@link CatalogFeatures.Feature#RESOLVE RESOLVE} property. + */ + IGNORE { + @Override + public String toString() { return "ignore"; } + }, + /** + * Indicates that the resolver should throw a CatalogException as defined + * by the {@link CatalogFeatures.Feature#RESOLVE RESOLVE} property. + */ + STRICT { + @Override + public String toString() { return "strict"; } + }; + + /** + * Returns the action type mapped to the specified + * {@link CatalogFeatures.Feature#RESOLVE resolve} property. + * + * @param resolve the value of the RESOLVE property + * @return the action type + */ + static public NotFoundAction getType(String resolve) { + for (NotFoundAction type : NotFoundAction.values()) { + if (type.toString().equals(resolve)) { + return type; + } + } + CatalogMessages.reportIAE(CatalogMessages.ERR_INVALID_ARGUMENT, + new Object[]{resolve, "RESOLVE"}, null); + return null; + } + } } diff --git a/src/java.xml/share/classes/javax/xml/catalog/CatalogResolverImpl.java b/src/java.xml/share/classes/javax/xml/catalog/CatalogResolverImpl.java index 062148e406a..8026da21043 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/CatalogResolverImpl.java +++ b/src/java.xml/share/classes/javax/xml/catalog/CatalogResolverImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,6 +51,8 @@ import org.xml.sax.XMLReader; */ final class CatalogResolverImpl implements CatalogResolver { Catalog catalog; + // resolution action type + NotFoundAction resolveType; /** * Construct an instance of the CatalogResolver from a Catalog. @@ -58,9 +60,25 @@ final class CatalogResolverImpl implements CatalogResolver { * @param catalog A Catalog. */ public CatalogResolverImpl(Catalog catalog) { - this.catalog = catalog; + this(catalog, null); } + /** + * Construct an instance of the CatalogResolver from a Catalog and the + * {@link CatalogResolver.NotFoundAction action} type. + * + * @param catalog a Catalog object + * @param action the action type + */ + public CatalogResolverImpl(Catalog catalog, NotFoundAction action) { + this.catalog = catalog; + // Note: can only happen in this impl + if (action == null) { + resolveType = ((CatalogImpl) catalog).getResolve(); + } else { + resolveType = action; + } + } /* Implements the EntityResolver interface */ @@ -91,7 +109,6 @@ final class CatalogResolverImpl implements CatalogResolver { return new InputSource(resolvedSystemId); } - GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve(); switch (resolveType) { case IGNORE: return new InputSource(new StringReader("")); @@ -145,7 +162,6 @@ final class CatalogResolverImpl implements CatalogResolver { //Report error or return the URI as is when no match is found if (result == null) { - GroupEntry.ResolveType resolveType = c.getResolve(); switch (resolveType) { case IGNORE: return new SAXSource(new InputSource(new StringReader(""))); @@ -229,7 +245,6 @@ final class CatalogResolverImpl implements CatalogResolver { } - GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve(); switch (resolveType) { case IGNORE: return null; @@ -250,7 +265,6 @@ final class CatalogResolverImpl implements CatalogResolver { return new LSInputImpl(is.getSystemId()); } - GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve(); switch (resolveType) { case IGNORE: return null; diff --git a/src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java b/src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java index 1082f00450e..3350a0dc8bf 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java +++ b/src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,34 +107,6 @@ class GroupEntry extends BaseEntry { } } - /** - * PreferType represents possible values of the resolve property - */ - public static enum ResolveType { - STRICT(CatalogFeatures.RESOLVE_STRICT), - CONTINUE(CatalogFeatures.RESOLVE_CONTINUE), - IGNORE(CatalogFeatures.RESOLVE_IGNORE); - - final String literal; - - ResolveType(String literal) { - this.literal = literal; - } - - static public ResolveType getType(String resolveType) { - for (ResolveType type : ResolveType.values()) { - if (type.isType(resolveType)) { - return type; - } - } - return null; - } - - public boolean isType(String type) { - return literal.equals(type); - } - } - /** * Constructs a GroupEntry * diff --git a/src/java.xml/share/classes/javax/xml/catalog/Util.java b/src/java.xml/share/classes/javax/xml/catalog/Util.java index 50a72aed620..3bf6e31f98b 100644 --- a/src/java.xml/share/classes/javax/xml/catalog/Util.java +++ b/src/java.xml/share/classes/javax/xml/catalog/Util.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; +import java.net.URL; import java.util.Iterator; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -267,4 +268,32 @@ class Util { Util.validateUrisSyntax(value.split(";")); } } + + /** + * Returns the absolute form of the specified uri after resolving it against + * the base. Returns the uri as is if it's already absolute. + * + * @param base the base, that is the system id of the catalog within the + * Catalog implementation + * @param uri the specified uri + * @return the absolute form of the specified uri + */ + @SuppressWarnings("deprecation") + static String getAbsoluteURI(String base, String uri) { + String temp = ""; + try { + URL baseURL = new URL(base); + URI specURI = URI.create(uri); + + if (specURI.isAbsolute()) { + temp = specURI.toURL().toString(); + } else { + temp = (new URL(baseURL, uri)).toString(); + } + } catch (MalformedURLException ex) { + // shouldn't happen since inputs are validated, report error in case + CatalogMessages.reportError(CatalogMessages.ERR_INVALID_CATALOG); + } + return temp; + } } diff --git a/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogResolverTest.java b/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogResolverTest.java new file mode 100644 index 00000000000..2eba917506e --- /dev/null +++ b/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogResolverTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package catalog; + +import java.net.URI; +import java.nio.file.Paths; +import javax.xml.catalog.Catalog; +import javax.xml.catalog.CatalogException; +import javax.xml.catalog.CatalogFeatures; +import javax.xml.catalog.CatalogManager; +import javax.xml.catalog.CatalogResolver; +import javax.xml.catalog.CatalogResolver.NotFoundAction; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; +import org.xml.sax.InputSource; + +/* + * @test + * @bug 8316996 + * @library /javax/xml/jaxp/libs /javax/xml/jaxp/unittest + * @run testng/othervm catalog.CatalogResolverTest + * @summary Tests CatalogResolver functions. See CatalogTest for existing basic + * functional tests. + */ +@Listeners({jaxp.library.FilePolicy.class}) +public class CatalogResolverTest extends CatalogSupportBase { + static final String KEY_FILES = "javax.xml.catalog.files"; + static final String SYSTEM_ID = "http://openjdk_java_net/xml/catalog/dtd/system.dtd"; + + /* + * Initializing fields + */ + @BeforeClass + public void setUpClass() throws Exception { + super.setUp(); + } + + /* + DataProvider: data used to verify the RESOLVE property, including the valid + values and the effect of overriding that on the Catalog. + Data columns: + resolve property for the Catalog, resolve property for the CatalogResolver, + system ID to be resolved, expected result, expected exception + */ + @DataProvider(name = "factoryMethodInput") + public Object[][] getInputs() throws Exception { + + return new Object[][]{ + // Valid values and overriding verification + // RESOLVE=strict but expected match + {"continue", NotFoundAction.STRICT, SYSTEM_ID, "system.dtd", null}, + // RESOLVE=strict plus no match: expect exception + {"continue", NotFoundAction.STRICT, "bogusID", "", CatalogException.class}, + // RESOLVE=ignore, continue: expect no match but without an exception + // Note that these tests do not differentiate empty InputSource from + // null, in both cases, the returned ID is null + {"strict", NotFoundAction.IGNORE, "bogusID", null, null}, + {"strict", NotFoundAction.CONTINUE, "bogusID", null, null}, + }; + } + + @DataProvider(name = "NPETest") + public Object[][] getNPETest() throws Exception { + return new Object[][]{ + {null, null}, + {getCatalog("ignore"), null}, + }; + } + + /** + * Tests the factory method for creating CatalogResolver with an + * {@link javax.xml.catalog.CatalogResolver.NotFoundAction action} type. + * The 2-arg {@link javax.xml.catalog.CatalogManager#catalogResolver( + * javax.xml.catalog.Catalog, javax.xml.catalog.CatalogResolver.NotFoundAction) + * catalogResolver} method adds the action type to be used for determining + * the behavior instead of relying on the underlying catalog. + * + * @param cResolve the resolve property set on the Catalog object + * @param action the resolve property set on the CatalogResolver to override + * that of the Catalog + * @param systemId the system ID to be resolved + * @param expectedResult the expected result + * @param expectedThrow the expected exception + * @throws Exception if the test fails + */ + @Test(dataProvider = "factoryMethodInput") + public void testResolveProperty(String cResolve, NotFoundAction action, + String systemId, String expectedResult, Class expectedThrow) + throws Exception { + Catalog c = getCatalog(cResolve); + + if (expectedThrow != null) { + Assert.assertThrows(expectedThrow, + () -> resolveRef(c, action, systemId)); + } else { + + String sysId = resolveRef(c, action, systemId); + System.out.println(sysId); + Assert.assertEquals(sysId, + (expectedResult == null) ? null : Paths.get(filepath + expectedResult).toUri().toString().replace("///", "/"), + "System ID match not right"); + } + } + + /** + * Verifies that the catalogResolver method throws NullPointerException if + * any of the parameters is null. + */ + @Test(dataProvider = "NPETest", expectedExceptions = NullPointerException.class) + public void testCatalogProperty(Catalog c, NotFoundAction action) { + CatalogManager.catalogResolver(c, action); + } + + private String resolveRef(Catalog c, NotFoundAction action, String systemId) throws Exception { + CatalogResolver cr = CatalogManager.catalogResolver(c, action); + InputSource is = cr.resolveEntity("", systemId); + return is == null ? null : is.getSystemId(); + } + + private Catalog getCatalog(String cResolve) throws Exception { + URI catalogFile = getClass().getResource("catalog.xml").toURI(); + Catalog c = CatalogManager.catalog( + CatalogFeatures.builder().with(CatalogFeatures.Feature.RESOLVE, cResolve).build(), + catalogFile); + return c; + } +} diff --git a/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogTest.java b/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogTest.java index 499a5b23e88..dbbc9d88e7b 100644 --- a/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogTest.java +++ b/test/jaxp/javax/xml/jaxp/unittest/catalog/CatalogTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -506,7 +506,7 @@ public class CatalogTest extends CatalogSupportBase { */ @Test(expectedExceptions = NullPointerException.class) public void testFeatureNull() { - CatalogResolver resolver = CatalogManager.catalogResolver(null, null); + CatalogResolver resolver = CatalogManager.catalogResolver((CatalogFeatures)null, (URI)null); }