8316996: Catalog API Enhancement: add a factory method

Reviewed-by: naoto, lancea
This commit is contained in:
Joe Wang 2023-10-28 03:38:30 +00:00
parent d2260146c9
commit 96bec3584c
9 changed files with 293 additions and 48 deletions

View File

@ -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;
}

View File

@ -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.
* <p>
* 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.

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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
*

View File

@ -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;
}
}

View File

@ -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<Throwable> 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;
}
}

View File

@ -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);
}