org.eclipse.jetty.xml.BaseClassCatalog Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.xml;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import javax.xml.catalog.Catalog;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.jetty.util.TypeUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
*
* A catalog implementation where the {@code xml:base} is defined externally
* of the catalog XML, allowing for runtime determination of the {@code xml:base}
* (such as pointing to the contents of a remote location)
*
*
* This is a temporary Catalog implementation, and should be removed once
* all of our usages of {@code servlet-api-.jar} have their own
* {@code catalog.xml} files.
*
*/
public class BaseClassCatalog implements Catalog, EntityResolver
{
public static BaseClassCatalog load(URI uriToCatalogXml, Class> baseClass) throws IOException
{
return new CatalogReader(uriToCatalogXml, baseClass).parse();
}
private static class CatalogReader
{
private final URI catalogUri;
private final Class> baseClass;
public CatalogReader(URI uriToCatalogXml, Class> baseClass)
{
this.catalogUri = Objects.requireNonNull(uriToCatalogXml, "Catalog XML");
this.baseClass = Objects.requireNonNull(baseClass, "Base Class");
}
public BaseClassCatalog parse() throws IOException
{
try (InputStream in = catalogUri.toURL().openStream())
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setValidating(false);
DocumentBuilder db = dbf.newDocumentBuilder();
dbf.setFeature("http://apache.org/xml/features/validation/schema", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
InputSource input = new InputSource(in);
Document doc = db.parse(input);
Element root = doc.getDocumentElement();
Map publicIdMap = getMapping(root, "public", "publicId");
Map systemIdMap = getMapping(root, "system", "systemId");
return new BaseClassCatalog(publicIdMap, systemIdMap);
}
catch (ParserConfigurationException | SAXException e)
{
throw new IOException("Unable to parse: " + catalogUri, e);
}
}
private Map getMapping(Element root, String elementName, String attrIdName) throws IOException
{
Map mapping = new HashMap<>();
NodeList nodeList = root.getElementsByTagNameNS("*", elementName);
for (int i = 0; i < nodeList.getLength(); i++)
{
Element elem = (Element)nodeList.item(i);
String id = elem.getAttribute(attrIdName);
String ref = elem.getAttribute("uri");
try
{
if (new URI(ref).isAbsolute())
mapping.put(id, ref);
else
{
URL url = baseClass.getResource(ref);
if (url == null)
throw new FileNotFoundException("Unable to find ref [%s/%s] in same archive as %s: %s"
.formatted(baseClass.getPackageName().replace('.', '/'),
ref, baseClass.getName(),
TypeUtil.getLocationOfClass(baseClass)));
mapping.put(id, url.toExternalForm());
}
}
catch (URISyntaxException e)
{
throw new IOException("Unable to parse %s - bad URI in: %s".formatted(catalogUri, elem), e);
}
}
return mapping;
}
}
private final Map publicIdMap;
private final Map systemIdMap;
private BaseClassCatalog(Map publicIdMap, Map systemIdMap)
{
this.publicIdMap = Objects.requireNonNull(publicIdMap, "Public ID Map");
this.systemIdMap = Objects.requireNonNull(systemIdMap, "System ID Map");
}
@Override
public Stream catalogs()
{
// empty, we have no alternative catalogs
return Stream.of();
}
@Override
public String matchPublic(String publicId)
{
return publicIdMap.get(publicId);
}
@Override
public String matchSystem(String systemId)
{
return systemIdMap.get(systemId);
}
@Override
public String matchURI(String uri)
{
for (Map.Entry entry : publicIdMap.entrySet())
{
if (entry.getValue().equals(uri))
return entry.getKey();
}
for (Map.Entry entry : systemIdMap.entrySet())
{
if (entry.getValue().equals(uri))
return entry.getKey();
}
return null;
}
/**
* Implementation of {@link org.xml.sax.EntityResolver}
*
* @see org.xml.sax.EntityResolver#resolveEntity(String, String)
*/
@Override
public InputSource resolveEntity(String publicId, String systemId)
{
String resolvedSystemId = null;
if (systemId != null)
{
// DTD's need to be searched in a simple filename only form to maintain
// backward compat with older DTD spec in regard to SYSTEM identifiers.
if (systemId.toLowerCase(Locale.ENGLISH).endsWith(".dtd"))
{
int idx = systemId.lastIndexOf('/');
if (idx >= 0)
{
resolvedSystemId = matchSystem(systemId.substring(idx + 1));
}
}
// Search full systemId
if (resolvedSystemId == null)
resolvedSystemId = matchSystem(systemId);
}
if (resolvedSystemId == null && publicId != null)
{
resolvedSystemId = matchPublic(publicId);
}
if (resolvedSystemId == null && systemId != null)
{
resolvedSystemId = matchURI(systemId);
}
if (resolvedSystemId != null)
{
return new InputSource(resolvedSystemId);
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy