org.mycore.common.xml.MCRURIResolver Maven / Gradle / Ivy
/*
*
* $Revision$ $Date$
*
* This file is part of *** M y C o R e *** See http://www.mycore.de/ for
* details.
*
* This program is free software; you can use it, redistribute it and / or
* modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License or
* (at your option) any later version.
*
* This program 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 for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program, in a file called gpl.txt or license.txt. If not, write to the
* Free Software Foundation Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307 USA
*/
package org.mycore.common.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.URIResolver;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import org.apache.http.Header;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClients;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaders;
import org.jdom2.transform.JDOMSource;
import org.mycore.access.MCRAccessManager;
import org.mycore.common.MCRCache;
import org.mycore.common.MCRClassTools;
import org.mycore.common.MCRConstants;
import org.mycore.common.MCRDeveloperTools;
import org.mycore.common.MCRException;
import org.mycore.common.MCRSessionMgr;
import org.mycore.common.MCRUsageException;
import org.mycore.common.config.MCRConfiguration2;
import org.mycore.common.config.MCRConfigurationDir;
import org.mycore.common.content.MCRByteContent;
import org.mycore.common.content.MCRContent;
import org.mycore.common.content.MCRPathContent;
import org.mycore.common.content.MCRSourceContent;
import org.mycore.common.content.MCRStreamContent;
import org.mycore.common.content.transformer.MCRContentTransformer;
import org.mycore.common.content.transformer.MCRParameterizedTransformer;
import org.mycore.common.content.transformer.MCRXSLTransformer;
import org.mycore.common.events.MCRShutdownHandler;
import org.mycore.common.xsl.MCRLazyStreamSource;
import org.mycore.common.xsl.MCRParameterCollector;
import org.mycore.datamodel.classifications2.MCRCategory;
import org.mycore.datamodel.classifications2.MCRCategoryDAO;
import org.mycore.datamodel.classifications2.MCRCategoryDAOFactory;
import org.mycore.datamodel.classifications2.MCRCategoryID;
import org.mycore.datamodel.classifications2.utils.MCRCategoryTransformer;
import org.mycore.datamodel.common.MCRDataURL;
import org.mycore.datamodel.common.MCRXMLMetadataManager;
import org.mycore.datamodel.ifs2.MCRMetadataStore;
import org.mycore.datamodel.ifs2.MCRMetadataVersion;
import org.mycore.datamodel.ifs2.MCRStoredMetadata;
import org.mycore.datamodel.metadata.MCRDerivate;
import org.mycore.datamodel.metadata.MCRFileMetadata;
import org.mycore.datamodel.metadata.MCRMetadataManager;
import org.mycore.datamodel.metadata.MCRObjectDerivate;
import org.mycore.datamodel.metadata.MCRObjectID;
import org.mycore.datamodel.niofs.MCRPath;
import org.mycore.datamodel.niofs.MCRPathXML;
import org.mycore.services.http.MCRHttpUtils;
import org.mycore.services.i18n.MCRTranslation;
import org.mycore.tools.MCRObjectFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* Reads XML documents from various URI types. This resolver is used to read DTDs, XML Schema files, XSL document()
* usages, xsl:include usages and MyCoRe Editor include declarations. DTDs and Schema files are read from the CLASSPATH
* of the application when XML is parsed. XML document() calls and xsl:include calls within XSL stylesheets can be read
* from URIs of type resource, webapp, file, session, query or mcrobject. MyCoRe editor include declarations can read
* XML files from resource, webapp, file, session, http or https, query, or mcrobject URIs.
*
* @author Frank L\u00FCtzenkirchen
* @author Thomas Scheffler (yagee)
*/
public final class MCRURIResolver implements URIResolver {
static final Logger LOGGER = LogManager.getLogger(MCRURIResolver.class);
static final String SESSION_OBJECT_NAME = "URI_RESOLVER_DEBUG";
private static final String CONFIG_PREFIX = "MCR.URIResolver.";
private static Map SUPPORTED_SCHEMES;
private static MCRResolverProvider EXT_RESOLVER;
private static MCRURIResolver singleton;
private static ServletContext context;
static {
try {
EXT_RESOLVER = getExternalResolverProvider();
singleton = new MCRURIResolver();
} catch (Exception exc) {
LOGGER.error("Unable to initialize MCRURIResolver", exc);
}
}
/**
* Creates a new MCRURIResolver
*/
private MCRURIResolver() {
SUPPORTED_SCHEMES = Collections.unmodifiableMap(getResolverMapping());
}
private static MCRResolverProvider getExternalResolverProvider() {
return MCRConfiguration2.getClass(CONFIG_PREFIX + "ExternalResolver.Class")
.map(c -> {
try {
return (MCRResolverProvider) c.getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
LOGGER.warn("Could not instantiate external Resolver class", e);
return null;
}
}).orElse(HashMap::new);
}
/**
* Returns the MCRURIResolver singleton
*/
public static MCRURIResolver instance() {
return singleton;
}
/**
* Initializes the MCRURIResolver for servlet applications.
*
* @param ctx
* the servlet context of this web application
*/
public static synchronized void init(ServletContext ctx) {
context = ctx;
}
public static Hashtable getParameterMap(String key) {
String[] param;
StringTokenizer tok = new StringTokenizer(key, "&");
Hashtable params = new Hashtable<>();
while (tok.hasMoreTokens()) {
param = tok.nextToken().split("=");
params.put(param[0], param.length >= 2 ? param[1] : "");
}
return params;
}
static URI resolveURI(String href, String base) {
return Optional.ofNullable(base)
.map(URI::create)
.map(u -> u.resolve(href))
.orElse(URI.create(href));
}
public static ServletContext getServletContext() {
return context;
}
private HashMap getResolverMapping() {
final Map extResolverMapping = EXT_RESOLVER.getURIResolverMapping();
extResolverMapping.putAll(new MCRModuleResolverProvider().getURIResolverMapping());
// set Map to final size with loadfactor: full
HashMap supportedSchemes = new HashMap<>(10 + extResolverMapping.size(), 1);
// don't let interal mapping be overwritten
supportedSchemes.putAll(extResolverMapping);
supportedSchemes.put("webapp", new MCRWebAppResolver());
supportedSchemes.put("ifs", new MCRIFSResolver());
supportedSchemes.put("mcrfile", new MCRMCRFileResolver());
supportedSchemes.put("mcrobject", new MCRObjectResolver());
supportedSchemes.put("session", new MCRSessionResolver());
supportedSchemes.put("access", new MCRACLResolver());
supportedSchemes.put("resource", new MCRResourceResolver());
supportedSchemes.put("localclass", new MCRLocalClassResolver());
supportedSchemes.put("classification", new MCRClassificationResolver());
supportedSchemes.put("buildxml", new MCRBuildXMLResolver());
supportedSchemes.put("catchEx", new MCRExceptionAsXMLResolver());
supportedSchemes.put("notnull", new MCRNotNullResolver());
supportedSchemes.put("xslStyle", new MCRXslStyleResolver());
supportedSchemes.put("xslTransform", new MCRLayoutTransformerResolver());
supportedSchemes.put("xslInclude", new MCRXslIncludeResolver());
supportedSchemes.put("xslImport", new MCRXslImportResolver());
supportedSchemes.put("versioninfo", new MCRVersionInfoResolver());
supportedSchemes.put("deletedMcrObject", new MCRDeletedObjectResolver());
supportedSchemes.put("fileMeta", new MCRFileMetadataResolver());
supportedSchemes.put("basket", new org.mycore.frontend.basket.MCRBasketResolver());
supportedSchemes.put("language", new org.mycore.datamodel.language.MCRLanguageResolver());
supportedSchemes.put("chooseTemplate", new MCRChooseTemplateResolver());
supportedSchemes.put("redirect", new MCRRedirectResolver());
supportedSchemes.put("data", new MCRDataURLResolver());
supportedSchemes.put("i18n", new MCRI18NResolver());
MCRRESTResolver restResolver = new MCRRESTResolver();
supportedSchemes.put("http", restResolver);
supportedSchemes.put("https", restResolver);
supportedSchemes.put("file", new MCRFileResolver());
return supportedSchemes;
}
/**
* URI Resolver that resolves XSL document() or xsl:include calls.
*
* @see javax.xml.transform.URIResolver
*/
public Source resolve(String href, String base) throws TransformerException {
if (LOGGER.isDebugEnabled()) {
if (base != null) {
LOGGER.debug("Including {} from {}", href, base);
addDebugInfo(href, base);
} else {
LOGGER.debug("Including {}", href);
addDebugInfo(href, null);
}
}
if (!href.contains(":")) {
return tryResolveXSL(href, base);
}
String scheme = getScheme(href, base);
URIResolver uriResolver = SUPPORTED_SCHEMES.get(scheme);
if (uriResolver != null) {
return uriResolver.resolve(href, base);
} else { // try to handle as URL, use default resolver for file:// and
try {
InputSource entity = MCREntityResolver.instance().resolveEntity(null, href);
if (entity != null) {
LOGGER.debug("Resolved via EntityResolver: {}", entity.getSystemId());
return new MCRLazyStreamSource(entity::getByteStream, entity.getSystemId());
}
} catch (IOException e) {
LOGGER.debug("Error while resolving uri: {}", href);
}
// http://
if (href.endsWith("/") && scheme.equals("file")) {
//cannot stream directories
return null;
}
StreamSource streamSource = new StreamSource();
streamSource.setSystemId(href);
return streamSource;
}
}
private Source tryResolveXSL(String href, String base) throws TransformerException {
if (href.endsWith(".xsl")) {
final String uri = "resource:xsl/" + href;
LOGGER.debug("Trying to resolve {} from uri {}", href, uri);
return SUPPORTED_SCHEMES.get("resource").resolve(uri, base);
}
return null;
}
private void addDebugInfo(String href, String base) {
MCRURIResolverFilter.uriList.get().add(href + " from " + base);
}
/**
* Reads XML from URIs of various type.
*
* @param uri
* the URI where to read the XML from
* @return the root element of the XML document
*/
public Element resolve(String uri) {
if (LOGGER.isDebugEnabled()) {
addDebugInfo(uri, "JAVA method invocation");
}
MCRSourceContent content;
try {
content = MCRSourceContent.getInstance(uri);
return content == null ? null : content.asXML().getRootElement().detach();
} catch (Exception e) {
throw new MCRException("Error while resolving " + uri, e);
}
}
/**
* Returns the protocol or scheme for the given URI.
*
* @param uri
* the URI to parse
* @param base
* if uri is relative, resolve scheme from base parameter
* @return the protocol/scheme part before the ":"
*/
public String getScheme(String uri, String base) {
StringTokenizer uriTokenizer = new StringTokenizer(uri, ":");
if (uriTokenizer.hasMoreTokens()) {
return uriTokenizer.nextToken();
}
if (base != null) {
uriTokenizer = new StringTokenizer(base, ":");
if (uriTokenizer.hasMoreTokens()) {
return uriTokenizer.nextToken();
}
}
return null;
}
URIResolver getResolver(String scheme) {
if (SUPPORTED_SCHEMES.containsKey(scheme)) {
return SUPPORTED_SCHEMES.get(scheme);
}
String msg = "Unsupported scheme type: " + scheme;
throw new MCRUsageException(msg);
}
/**
* Reads xml from an InputStream and returns the parsed root element.
*
* @param in
* the InputStream that contains the XML document
* @return the root element of the parsed input stream
*/
protected Element parseStream(InputStream in) throws JDOMException, IOException {
SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
builder.setEntityResolver(MCREntityResolver.instance());
return builder.build(in).getRootElement();
}
/**
* provides a URI -- Resolver Mapping One can implement this interface to provide additional URI schemes this
* MCRURIResolver should handle, too. To add your mapping you have to set the
* MCR.URIResolver.ExternalResolver.Class
property to the implementing class.
*
* @author Thomas Scheffler
*/
public interface MCRResolverProvider {
/**
* provides a Map of URIResolver mappings. Key is the scheme, e.g. http
, where value is an
* implementation of {@link URIResolver}.
*
* @see URIResolver
* @return a Map of URIResolver mappings
*/
Map getURIResolverMapping();
}
public interface MCRCacheableURIResolver extends URIResolver {
MCRCacheableURIResponse getResponse(String href, String base) throws TransformerException;
@Override
default Source resolve(String href, String base) throws TransformerException {
return getResponse(href, base).getSource();
}
}
public interface MCRXslIncludeHrefs {
List getHrefs();
}
public static class MCRCacheableURIResponse {
private Source source;
private Supplier hash;
private boolean support;
public MCRCacheableURIResponse(Source source, Supplier hashSupplier,
Supplier supportSupplier) {
this.source = source;
this.hash = hashSupplier;
this.support = supportSupplier.get();
}
public Source getSource() {
return source;
}
public int getHash() {
return hash.get();
}
public boolean supportsHash() {
return support;
}
}
private static class MCRModuleResolverProvider implements MCRResolverProvider {
private final Map resolverMap = new HashMap<>();
MCRModuleResolverProvider() {
MCRConfiguration2.getSubPropertiesMap(CONFIG_PREFIX + "ModuleResolver.")
.forEach(this::registerUriResolver);
}
public Map getURIResolverMapping() {
return resolverMap;
}
private void registerUriResolver(String scheme, String className) {
try {
resolverMap.put(scheme, MCRConfiguration2.instantiateClass(className));
} catch (RuntimeException re) {
throw new MCRException("Cannot instantiate " + className + " for URI scheme " + scheme, re);
}
}
}
private static class MCRFileResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
URI hrefURI = MCRURIResolver.resolveURI(href, base);
if (!hrefURI.getScheme().equals("file")) {
throw new TransformerException("Unsupport file uri scheme: " + hrefURI.getScheme());
}
Path path = Paths.get(hrefURI);
StreamSource source;
try {
source = new StreamSource(Files.newInputStream(path), hrefURI.toASCIIString());
return source;
} catch (IOException e) {
throw new TransformerException(e);
}
}
}
private static class MCRRESTResolver implements MCRCacheableURIResolver {
private static final long MAX_OBJECT_SIZE = MCRConfiguration2.getLong(CONFIG_PREFIX + "REST.MaxObjectSize")
.orElse(128 * 1024l);
private static final int MAX_CACHE_ENTRIES = MCRConfiguration2.getInt(CONFIG_PREFIX + "REST.MaxCacheEntries")
.orElse(1000);
private static final int REQUEST_TIMEOUT = MCRConfiguration2.getInt(CONFIG_PREFIX + "REST.RequestTimeout")
.orElse(30000);
private CloseableHttpClient restClient;
private org.apache.logging.log4j.Logger logger;
MCRRESTResolver() {
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxObjectSize(MAX_OBJECT_SIZE)
.setMaxCacheEntries(MAX_CACHE_ENTRIES)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(REQUEST_TIMEOUT)
.setSocketTimeout(REQUEST_TIMEOUT)
.build();
this.restClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setDefaultRequestConfig(requestConfig)
.setUserAgent(MCRHttpUtils.getHttpUserAgent())
.useSystemProperties()
.build();
MCRShutdownHandler.getInstance().addCloseable(this::close);
this.logger = LogManager.getLogger();
}
private static int getHash(String lastModified, String eTag) {
final int prime = 31;
int result = 1;
result = prime * result + ((eTag == null) ? 0 : eTag.hashCode());
result = prime * result + ((lastModified == null) ? 0 : lastModified.hashCode());
return result;
}
public void close() {
try {
restClient.close();
} catch (IOException e) {
LogManager.getLogger().warn("Exception while closing http client.", e);
}
}
@Override
public MCRCacheableURIResponse getResponse(String href, String base) throws TransformerException {
URI hrefURI = MCRURIResolver.resolveURI(href, base);
InputStream responseStream;
String eTag, lastModified;
try {
HttpCacheContext context = HttpCacheContext.create();
HttpGet get = new HttpGet(hrefURI);
CloseableHttpResponse response = restClient.execute(get, context);
logger.debug(() -> {
String msg = hrefURI.toASCIIString() + ": ";
switch (context.getCacheResponseStatus()) {
case CACHE_HIT:
msg += "A response was generated from the cache with " +
"no requests sent upstream";
break;
case CACHE_MODULE_RESPONSE:
msg += "The response was generated directly by the " +
"caching module";
break;
case CACHE_MISS:
msg += "The response came from an upstream server";
break;
case VALIDATED:
msg += "The response was generated from the cache " +
"after validating the entry with the origin server";
break;
}
return msg;
});
eTag = Optional.ofNullable(response.getLastHeader("etag"))
.map(Header::getValue)
.orElse(null);
lastModified = Optional.ofNullable(response.getLastHeader("last-modified"))
.map(Header::getValue)
.orElse(null);
try (InputStream content = response.getEntity().getContent()) {
final Source source = new MCRStreamContent(content).getReusableCopy().getSource();
source.setSystemId(hrefURI.toASCIIString());
return new MCRCacheableURIResponse(source,
() -> getHash(lastModified, eTag), () -> lastModified != null || eTag != null);
} catch (Exception e) {
throw new MCRException(e);
} finally {
response.close();
get.reset();
}
} catch (IOException e) {
throw new TransformerException(e);
}
}
}
private static class MCRObjectResolver implements URIResolver {
/**
* Reads local MCRObject with a given ID from the store.
*
* @param href
* for example, "mcrobject:DocPortal_document_07910401"
* @returns XML representation from MCRXMLContainer
*/
@Override
public Source resolve(String href, String base) throws TransformerException {
String id = href.substring(href.indexOf(":") + 1);
LOGGER.debug("Reading MCRObject with ID {}", id);
Map params;
StringTokenizer tok = new StringTokenizer(id, "?");
id = tok.nextToken();
if (tok.hasMoreTokens()) {
params = getParameterMap(tok.nextToken());
} else {
params = Collections.emptyMap();
}
MCRObjectID mcrid = MCRObjectID.getInstance(id);
try {
MCRXMLMetadataManager xmlmm = MCRXMLMetadataManager.instance();
MCRContent content = params.containsKey("r")
? xmlmm.retrieveContent(mcrid, Long.valueOf(params.get("r")))
: xmlmm.retrieveContent(mcrid);
if (content == null) {
return null;
}
LOGGER.debug("end resolving {}", href);
return content.getSource();
} catch (IOException e) {
throw new TransformerException(e);
}
}
}
/**
* Reads XML from a static file within the web application. the URI in the format webapp:path/to/servlet
*/
private static class MCRWebAppResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String path = href.substring(href.indexOf(":") + 1);
if (path.charAt(0) != '/') {
path = '/' + path;
}
if (MCRDeveloperTools.overrideActive()) {
final Optional overriddenFilePath = MCRDeveloperTools.getOverriddenFilePath(path, true);
if (overriddenFilePath.isPresent()) {
return new StreamSource(overriddenFilePath.get().toFile());
}
}
LOGGER.debug("Reading xml from webapp {}", path);
try {
URL resource = context.getResource(path);
if (resource != null) {
return new StreamSource(resource.toURI().toASCIIString());
}
} catch (Exception ex) {
throw new TransformerException(ex);
}
LOGGER.error("File does not exist: {}", context.getRealPath(path));
throw new TransformerException("Could not find web resource: " + path);
}
}
private static class MCRChooseTemplateResolver implements URIResolver {
private static Document getStylesheets(List temps) {
Element rootOut = new Element("stylesheet", MCRConstants.XSL_NAMESPACE).setAttribute("version", "1.0");
Document jdom = new Document(rootOut);
if (temps.isEmpty()) {
return jdom;
}
for (String templateName : temps) {
rootOut.addContent(
new Element("include", MCRConstants.XSL_NAMESPACE).setAttribute("href", templateName + ".xsl"));
}
// first template named "chooseTemplate" in chooseTemplate.xsl
Element template = new Element("template", MCRConstants.XSL_NAMESPACE).setAttribute("name",
"chooseTemplate");
Element choose = new Element("choose", MCRConstants.XSL_NAMESPACE);
// second template named "get.templates" in chooseTemplate.xsl
Element template2 = new Element("template", MCRConstants.XSL_NAMESPACE).setAttribute("name",
"get.templates");
Element templates = new Element("templates");
for (String templateName : temps) {
// add elements in the first template
Element when = new Element("when", MCRConstants.XSL_NAMESPACE).setAttribute("test",
"$template = '" + templateName + "'");
when.addContent(
new Element("call-template", MCRConstants.XSL_NAMESPACE).setAttribute("name", templateName));
choose.addContent(when);
// add elements in the second template
templates.addContent(new Element("template").setAttribute("category", "master").setText(templateName));
}
// first
template.addContent(choose);
rootOut.addContent(template);
// second
template2.addContent(templates);
rootOut.addContent(template2);
return jdom;
}
@Override
public Source resolve(String href, String base) throws TransformerException {
String type = href.substring(href.indexOf(":") + 1);
String path = "/templates/" + type + "/";
LOGGER.debug("Reading templates from {}", path);
Set resourcePaths = context.getResourcePaths(path);
ArrayList templates = new ArrayList<>();
if (resourcePaths != null) {
for (String resourcePath : resourcePaths) {
if (!resourcePath.endsWith("/")) {
//only handle directories
continue;
}
String templateName = resourcePath.substring(path.length(), resourcePath.length() - 1);
LOGGER.debug("Checking if template: {}", templateName);
if (templateName.contains("/")) {
continue;
}
templates.add(templateName);
}
Collections.sort(templates);
}
LOGGER.info("Found theses templates: {}", templates);
return new JDOMSource(getStylesheets(templates));
}
}
/**
* Reads XML from the CLASSPATH of the application. the location of the file in the format resource:path/to/file
*/
private static class MCRResourceResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String path = href.substring(href.indexOf(":") + 1);
URL resource = MCRConfigurationDir.getConfigResource(path);
if (resource != null) {
//have to use SAX here to resolve entities
if (path.endsWith(".xsl")) {
XMLReader reader;
try {
reader = MCRXMLParserFactory.getNonValidatingParser().getXMLReader();
} catch (SAXException | ParserConfigurationException e) {
throw new TransformerException(e);
}
reader.setEntityResolver(MCREntityResolver.instance());
InputSource input = new InputSource(resource.toString());
SAXSource saxSource = new SAXSource(reader, input);
LOGGER.debug("include stylesheet: {}", saxSource.getSystemId());
return saxSource;
}
return MCRURIResolver.instance().resolve(resource.toString(), base);
}
return null;
}
}
/**
* Delivers a jdom Element created by any local class that implements URIResolver
* interface. the class name of the file in the format localclass:org.mycore.ClassName?mode=getAll
*/
private static class MCRLocalClassResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String classname = href.substring(href.indexOf(":") + 1, href.indexOf("?"));
Class cl = null;
LogManager.getLogger(this.getClass()).debug("Loading Class: {}", classname);
URIResolver resolver;
try {
cl = MCRClassTools.forName(classname);
resolver = cl.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new TransformerException(e);
}
return resolver.resolve(href, base);
}
}
private static class MCRSessionResolver implements URIResolver {
/**
* Reads XML from URIs of type session:key. The method MCRSession.get( key ) is called and must return a JDOM
* element.
*
* @see org.mycore.common.MCRSession#get(Object)
* @param href
* the URI in the format session:key
* @return the root element of the xml document
*/
@Override
public Source resolve(String href, String base) throws TransformerException {
String key = href.substring(href.indexOf(":") + 1);
LOGGER.debug("Reading xml from session using key {}", key);
Element value = (Element) MCRSessionMgr.getCurrentSession().get(key);
return new JDOMSource(value.clone());
}
}
private static class MCRIFSResolver implements URIResolver {
/**
* Reads XML from a http or https URL.
*
* @param href
* the URL of the xml document
* @return the root element of the xml document
*/
@Override
public Source resolve(String href, String base) throws TransformerException {
LOGGER.debug("Reading xml from url {}", href);
String path = href.substring(href.indexOf(":") + 1);
int i = path.indexOf("?host");
if (i > 0) {
path = path.substring(0, i);
}
StringTokenizer st = new StringTokenizer(path, "/");
String ownerID = st.nextToken();
try {
String aPath = MCRXMLFunctions.decodeURIPath(path.substring(ownerID.length() + 1));
// TODO: make this more pretty
if (ownerID.endsWith(":")) {
ownerID = ownerID.substring(0, ownerID.length() - 1);
}
LOGGER.debug("Get {} path: {}", ownerID, aPath);
return new JDOMSource(MCRPathXML.getDirectoryXML(MCRPath.getPath(ownerID, aPath)));
} catch (IOException | URISyntaxException e) {
throw new TransformerException(e);
}
}
}
private static class MCRMCRFileResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
LOGGER.debug("Reading xml from MCRFile {}", href);
MCRPath file = null;
String id = href.substring(href.indexOf(":") + 1);
if (id.contains("/")) {
// assume thats a derivate with path
try {
MCRObjectID derivateID = MCRObjectID.getInstance(id.substring(0, id.indexOf("/")));
String path = id.substring(id.indexOf("/"));
file = MCRPath.getPath(derivateID.toString(), path);
} catch (MCRException exc) {
// just check if the id is valid, don't care about the exception
}
}
if (file == null) {
throw new TransformerException("mcrfile: Resolver needs a path: " + href);
}
try {
return new MCRPathContent(file).getSource();
} catch (Exception e) {
throw new TransformerException(e);
}
}
}
private static class MCRACLResolver implements URIResolver {
private static final String ACTION_PARAM = "action";
private static final String OBJECT_ID_PARAM = "object";
/**
* Returns access controll rules as XML
*/
public Source resolve(String href, String base) throws TransformerException {
String key = href.substring(href.indexOf(":") + 1);
LOGGER.debug("Reading xml from query result using key :{}", key);
String[] param;
StringTokenizer tok = new StringTokenizer(key, "&");
Hashtable params = new Hashtable<>();
while (tok.hasMoreTokens()) {
param = tok.nextToken().split("=");
params.put(param[0], param[1]);
}
String action = params.get(ACTION_PARAM);
String objId = params.get(OBJECT_ID_PARAM);
if (action == null || objId == null) {
return null;
}
Element container = new Element("servacls").setAttribute("class", "MCRMetaAccessRule");
if (action.equals("all")) {
for (String permission : MCRAccessManager.getPermissionsForID(objId)) {
// one pool Element under access per defined AccessRule in
// Pool
// for (Object-)ID
addRule(container, permission, MCRAccessManager.getAccessImpl().getRule(objId, permission));
}
} else {
addRule(container, action, MCRAccessManager.getAccessImpl().getRule(objId, action));
}
return new JDOMSource(container);
}
private void addRule(Element root, String pool, Element rule) {
if (rule != null && pool != null) {
Element poolElement = new Element("servacl").setAttribute("permission", pool);
poolElement.addContent(rule);
root.addContent(poolElement);
}
}
}
private static class MCRClassificationResolver implements URIResolver {
private static final Pattern EDITORFORMAT_PATTERN = Pattern.compile("(\\[)([^\\]]*)(\\])");
private static final String FORMAT_CONFIG_PREFIX = CONFIG_PREFIX + "Classification.Format.";
private static final String SORT_CONFIG_PREFIX = CONFIG_PREFIX + "Classification.Sort.";
private static MCRCache categoryCache;
private static MCRCategoryDAO DAO;
static {
try {
DAO = MCRCategoryDAOFactory.getInstance();
categoryCache = new MCRCache<>(
MCRConfiguration2.getInt(CONFIG_PREFIX + "Classification.CacheSize").orElse(1000),
"URIResolver categories");
} catch (Exception exc) {
LOGGER.error("Unable to initialize classification resolver", exc);
}
}
MCRClassificationResolver() {
}
private static String getLabelFormat(String editorString) {
Matcher m = EDITORFORMAT_PATTERN.matcher(editorString);
if (m.find() && m.groupCount() == 3) {
String formatDef = m.group(2);
return MCRConfiguration2.getStringOrThrow(FORMAT_CONFIG_PREFIX + formatDef);
}
return null;
}
private static boolean shouldSortCategories(String classId) {
return MCRConfiguration2.getBoolean(SORT_CONFIG_PREFIX + classId).orElse(true);
}
private static long getSystemLastModified() {
long xmlLastModified = MCRXMLMetadataManager.instance().getLastModified();
long classLastModified = DAO.getLastModified();
return Math.max(xmlLastModified, classLastModified);
}
/**
* returns a classification in a specific format. Syntax:
* classification:{editor[Complete]['['formatAlias']']|metadata}:{Levels}[:noEmptyLeaves]:{parents|
* children}:{ClassID}[:CategID] formatAlias: MCRConfiguration property
* MCR.UURResolver.Classification.Format.FormatAlias
*
* @param href
* URI in the syntax above
* @return the root element of the XML document
* @see MCRCategoryTransformer
*/
public Source resolve(String href, String base) throws TransformerException {
LOGGER.debug("start resolving {}", href);
String cacheKey = getCacheKey(href);
Element returns = categoryCache.getIfUpToDate(cacheKey, getSystemLastModified());
if (returns == null) {
returns = getClassElement(href);
if (returns != null) {
categoryCache.put(cacheKey, returns);
}
}
return new JDOMSource(returns);
}
protected String getCacheKey(String uri) {
return uri;
}
private Element getClassElement(String uri) {
StringTokenizer pst = new StringTokenizer(uri, ":", true);
if (pst.countTokens() < 9) {
// sanity check
throw new IllegalArgumentException("Invalid format of uri for retrieval of classification: " + uri);
}
pst.nextToken(); // "classification"
pst.nextToken(); // :
String format = pst.nextToken();
pst.nextToken(); // :
String levelS = pst.nextToken();
pst.nextToken(); // :
int levels = "all".equals(levelS) ? -1 : Integer.parseInt(levelS);
String axis;
String token = pst.nextToken();
pst.nextToken(); // :
boolean emptyLeaves = !"noEmptyLeaves".equals(token);
if (!emptyLeaves) {
axis = pst.nextToken();
pst.nextToken(); // :
} else {
axis = token;
}
String classID = pst.nextToken();
StringBuilder categID = new StringBuilder();
if (pst.hasMoreTokens()) {
pst.nextToken(); // :
while (pst.hasMoreTokens()) {
categID.append(pst.nextToken());
}
}
String categ;
try {
categ = MCRXMLFunctions.decodeURIPath(categID.toString());
} catch (URISyntaxException e) {
categ = categID.toString();
}
MCRCategory cl = null;
LOGGER.debug("categoryCache entry invalid or not found: start MCRClassificationQuery");
if (axis.equals("children")) {
if (categ.length() > 0) {
cl = DAO.getCategory(new MCRCategoryID(classID, categ), levels);
} else {
cl = DAO.getCategory(MCRCategoryID.rootID(classID), levels);
}
} else if (axis.equals("parents")) {
if (categ.length() == 0) {
LOGGER.error("Cannot resolve parent axis without a CategID. URI: {}", uri);
throw new IllegalArgumentException(
"Invalid format (categID is required in mode 'parents') "
+ "of uri for retrieval of classification: "
+ uri);
}
cl = DAO.getRootCategory(new MCRCategoryID(classID, categ), levels);
}
if (cl == null) {
return null;
}
Element returns;
LOGGER.debug("start transformation of ClassificationQuery");
if (format.startsWith("editor")) {
boolean completeId = format.startsWith("editorComplete");
boolean sort = shouldSortCategories(classID);
String labelFormat = getLabelFormat(format);
if (labelFormat == null) {
returns = MCRCategoryTransformer.getEditorItems(cl, sort, emptyLeaves, completeId);
} else {
returns = MCRCategoryTransformer.getEditorItems(cl, labelFormat, sort, emptyLeaves, completeId);
}
} else if (format.equals("metadata")) {
returns = MCRCategoryTransformer.getMetaDataDocument(cl, false).getRootElement().detach();
} else {
LOGGER.error("Unknown target format given. URI: {}", uri);
throw new IllegalArgumentException(
"Invalid target format (" + format + ") in uri for retrieval of classification: " + uri);
}
LOGGER.debug("end resolving {}", uri);
return returns;
}
}
private static class MCRExceptionAsXMLResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String target = href.substring(href.indexOf(":") + 1);
try {
return MCRURIResolver.instance().resolve(target, base);
} catch (Exception ex) {
LOGGER.debug("Caught {}. Put it into XML to process in XSL!", ex.getClass().getName());
Element exception = new Element("exception");
Element message = new Element("message");
Element stacktraceElement = new Element("stacktrace");
stacktraceElement.setAttribute("space", "preserve", Namespace.XML_NAMESPACE);
exception.addContent(message);
exception.addContent(stacktraceElement);
message.setText(ex.getMessage());
try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
ex.printStackTrace(pw);
stacktraceElement.setText(pw.toString());
} catch (IOException e) {
throw new MCRException("Error while writing Exception to String!", e);
}
return new JDOMSource(exception);
}
}
}
/**
* Ensures that the return of the given uri is never null. When the return is null, or the uri throws an exception,
* this resolver will return an empty XML element instead. Usage: notnull:
*/
private static class MCRNotNullResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String target = href.substring(href.indexOf(":") + 1);
// fixes exceptions if suburi is empty like "mcrobject:"
String subUri = target.substring(target.indexOf(":") + 1);
if (subUri.length() == 0) {
return new JDOMSource(new Element("null"));
}
// end fix
LOGGER.debug("Ensuring xml is not null: {}", target);
try {
Source result = MCRURIResolver.instance().resolve(target, base);
if (result != null) {
return result;
} else {
LOGGER.debug("MCRNotNullResolver returning empty xml");
return new JDOMSource(new Element("null"));
}
} catch (Exception ex) {
LOGGER.info("MCRNotNullResolver caught exception: {}", ex.getLocalizedMessage());
LOGGER.debug(ex.getStackTrace());
LOGGER.debug("MCRNotNullResolver returning empty xml");
return new JDOMSource(new Element("null"));
}
}
}
/**
* Transform result of other resolver with stylesheet. Usage: xslStyle:<,stylesheet>>: To is extension .xsl added. File is searched in classpath.
*/
private static class MCRXslStyleResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String help = href.substring(href.indexOf(":") + 1);
String stylesheets = new StringTokenizer(help, ":").nextToken();
String target = help.substring(help.indexOf(":") + 1);
String subUri = target.substring(target.indexOf(":") + 1);
if (subUri.length() == 0) {
return new JDOMSource(new Element("null"));
}
Map params;
StringTokenizer tok = new StringTokenizer(stylesheets, "?");
stylesheets = tok.nextToken();
if (tok.hasMoreTokens()) {
params = getParameterMap(tok.nextToken());
} else {
params = Collections.emptyMap();
}
Source resolved = MCRURIResolver.instance().resolve(target, base);
try {
if (resolved != null) {
if (resolved.getSystemId() == null) {
resolved.setSystemId(target);
}
MCRSourceContent content = new MCRSourceContent(resolved);
MCRXSLTransformer transformer = getTransformer(stylesheets.split(","));
MCRParameterCollector paramcollector = MCRParameterCollector.getInstanceFromUserSession();
paramcollector.setParameters(params);
MCRContent result = transformer.transform(content, paramcollector);
return result.getSource();
} else {
LOGGER.debug("MCRXslStyleResolver returning empty xml");
return new JDOMSource(new Element("null"));
}
} catch (IOException e) {
Throwable cause = e.getCause();
while (cause != null) {
if (cause instanceof TransformerException) {
throw (TransformerException) cause;
}
cause = cause.getCause();
}
throw new TransformerException(e);
}
}
private MCRXSLTransformer getTransformer(String... stylesheet) {
String[] stylesheets = new String[stylesheet.length];
for (int i = 0; i < stylesheets.length; i++) {
stylesheets[i] = "xsl/" + stylesheet[i] + ".xsl";
}
return MCRXSLTransformer.getInstance(stylesheets);
}
}
/**
* Transform result of other resolver with stylesheet. Usage: xslTransform:>:
*/
private static class MCRLayoutTransformerResolver implements URIResolver {
private static final String TRANSFORMER_FACTORY_PROPERTY = "MCR.Layout.Transformer.Factory";
@Override
public Source resolve(String href, String base) throws TransformerException {
String help = href.substring(href.indexOf(":") + 1);
String transformerId = new StringTokenizer(help, ":").nextToken();
String target = help.substring(help.indexOf(":") + 1);
String subUri = target.substring(target.indexOf(":") + 1);
if (subUri.length() == 0) {
return new JDOMSource(new Element("null"));
}
Map params;
StringTokenizer tok = new StringTokenizer(transformerId, "?");
transformerId = tok.nextToken();
if (tok.hasMoreTokens()) {
params = getParameterMap(tok.nextToken());
} else {
params = Collections.emptyMap();
}
Source resolved = MCRURIResolver.instance().resolve(target, base);
try {
if (resolved != null) {
MCRSourceContent content = new MCRSourceContent(resolved);
MCRLayoutTransformerFactory factory = MCRConfiguration2
.getInstanceOf(TRANSFORMER_FACTORY_PROPERTY)
.orElseGet(MCRLayoutTransformerFactory::new);
MCRContentTransformer transformer = factory.getTransformer(transformerId);
MCRContent result;
if (transformer instanceof MCRParameterizedTransformer) {
MCRParameterCollector paramcollector = MCRParameterCollector.getInstanceFromUserSession();
paramcollector.setParameters(params);
result = ((MCRParameterizedTransformer) transformer).transform(content, paramcollector);
} else {
result = transformer.transform(content);
}
return result.getSource();
} else {
LOGGER.debug("MCRLayoutStyleResolver returning empty xml");
return new JDOMSource(new Element("null"));
}
} catch (Exception e) {
if (e instanceof TransformerException) {
throw (TransformerException) e;
}
Throwable cause = e.getCause();
while (cause != null) {
if (cause instanceof TransformerException) {
throw (TransformerException) cause;
}
cause = cause.getCause();
}
throw new TransformerException(e);
}
}
}
/**
*
* Includes xsl files which are set in the mycore.properties file.
*
* Example: MCR.URIResolver.xslIncludes.components=iview.xsl,wcms.xsl
*
* Or retrieve the include hrefs from a class implementing
* {@link org.mycore.common.xml.MCRURIResolver.MCRXslIncludeHrefs}. The class. part have to be set, everything after
* class. can be freely choosen.
*
* Example: MCR.URIResolver.xslIncludes.class.template=org.foo.XSLHrefs
*
* @returns A xsl file with the includes as href.
*/
private static class MCRXslIncludeResolver implements URIResolver {
private static Logger LOGGER = LogManager.getLogger(MCRXslIncludeResolver.class);
@Override
public Source resolve(String href, String base) throws TransformerException {
String includePart = href.substring(href.indexOf(":") + 1);
Namespace xslNamespace = Namespace.getNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
Element root = new Element("stylesheet", xslNamespace);
root.setAttribute("version", "1.0");
// get the parameters from mycore.properties
String propertyName = "MCR.URIResolver.xslIncludes." + includePart;
List propValue;
if (includePart.startsWith("class.")) {
MCRXslIncludeHrefs incHrefClass = MCRConfiguration2
.getOrThrow(propertyName, MCRConfiguration2::instantiateClass);
propValue = incHrefClass.getHrefs();
} else {
propValue = MCRConfiguration2.getString(propertyName)
.map(MCRConfiguration2::splitValue)
.map(s -> s.collect(Collectors.toList()))
.orElseGet(Collections::emptyList);
}
for (String include : propValue) {
// create a new include element
Element includeElement = new Element("include", xslNamespace);
includeElement.setAttribute("href", include);
root.addContent(includeElement);
LOGGER.debug("Resolved XSL include: {}", include);
}
return new JDOMSource(root);
}
}
/**
* Imports xsl files which are set in the mycore.properties file. Example:
* MCR.URIResolver.xslImports.components=first.xsl,second.xsl Every file must import this URIResolver to form a
* import chain:
*
*
* <xsl:import href="xslImport:components:first.xsl">
*
*
* @returns A xsl file with the import as href.
*/
private static class MCRXslImportResolver implements URIResolver {
URIResolver fallback = new MCRResourceResolver();
@Override
public Source resolve(String href, String base) throws TransformerException {
String importXSL = MCRXMLFunctions.nextImportStep(href.substring(href.indexOf(':') + 1));
if (importXSL.isEmpty()) {
LOGGER.debug("End of import queue: {}", href);
Namespace xslNamespace = Namespace.getNamespace("xsl", "http://www.w3.org/1999/XSL/Transform");
Element root = new Element("stylesheet", xslNamespace);
root.setAttribute("version", "1.0");
return new JDOMSource(root);
}
LOGGER.debug("xslImport importing {}", importXSL);
return fallback.resolve("resource:xsl/" + importXSL, base);
}
}
/**
* Builds XML trees from a string representation. Multiple XPath expressions can be separated by & Example:
* buildxml:_rootName_=mycoreobject&metadata/parents/parent/@href= 'FooBar_Document_4711' This will return:
* <mycoreobject> <metadata> <parents> <parent href="FooBar_Document_4711" />
* </parents> </metadata> </mycoreobject>
*/
private static class MCRBuildXMLResolver implements URIResolver {
private static Hashtable getParameterMap(String key) {
String[] param;
StringTokenizer tok = new StringTokenizer(key, "&");
Hashtable params = new Hashtable<>();
while (tok.hasMoreTokens()) {
param = tok.nextToken().split("=");
params.put(URLDecoder.decode(param[0], StandardCharsets.UTF_8),
URLDecoder.decode(param[1], StandardCharsets.UTF_8));
}
return params;
}
private static void constructElement(Element current, String xpath, String value) {
StringTokenizer st = new StringTokenizer(xpath, "/");
String name = null;
while (st.hasMoreTokens()) {
name = st.nextToken();
if (name.startsWith("@")) {
break;
}
String localName = getLocalName(name);
Namespace namespace = getNamespace(name);
Element child = current.getChild(localName, namespace);
if (child == null) {
child = new Element(localName, namespace);
current.addContent(child);
}
current = child;
}
if (name.startsWith("@")) {
name = name.substring(1);
String localName = getLocalName(name);
Namespace namespace = getNamespace(name);
current.setAttribute(localName, value, namespace);
} else {
current.setText(value);
}
}
private static Namespace getNamespace(String name) {
if (!name.contains(":")) {
return Namespace.NO_NAMESPACE;
}
String prefix = name.split(":")[0];
Namespace ns = MCRConstants.getStandardNamespace(prefix);
return ns == null ? Namespace.NO_NAMESPACE : ns;
}
private static String getLocalName(String name) {
if (!name.contains(":")) {
return name;
} else {
return name.split(":")[1];
}
}
/**
* Builds a simple xml node tree on basis of name value pair
*/
public Source resolve(String href, String base) throws TransformerException {
String key = href.substring(href.indexOf(":") + 1);
LOGGER.debug("Building xml from {}", key);
Hashtable params = getParameterMap(key);
Element defaultRoot = new Element("root");
Element root = defaultRoot;
String rootName = params.get("_rootName_");
if (rootName != null) {
root = new Element(getLocalName(rootName), getNamespace(rootName));
params.remove("_rootName_");
}
for (Map.Entry entry : params.entrySet()) {
constructElement(root, entry.getKey(), entry.getValue());
}
if (root == defaultRoot && root.getChildren().size() > 1) {
LOGGER.warn("More than 1 root node defined, returning first");
return new JDOMSource(root.getChildren().get(0).detach());
}
return new JDOMSource(root);
}
}
private static class MCRVersionInfoResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String id = href.substring(href.indexOf(":") + 1);
LOGGER.debug("Reading version info of MCRObject with ID {}", id);
MCRObjectID mcrId = MCRObjectID.getInstance(id);
MCRXMLMetadataManager metadataManager = MCRXMLMetadataManager.instance();
try {
List versions = metadataManager.listRevisions(mcrId);
if (versions != null && !versions.isEmpty()) {
return getSource(versions);
} else {
MCRMetadataStore metadataStore = metadataManager.getStore(id, true);
return getSource(metadataStore.retrieve(mcrId.getNumberAsInteger()));
}
} catch (Exception e) {
throw new TransformerException(e);
}
}
private Source getSource(MCRStoredMetadata retrieve) throws IOException {
Element e = new Element("versions");
Element v = new Element("version");
e.addContent(v);
v.setAttribute("date", MCRXMLFunctions.getISODate(retrieve.getLastModified(), null));
return new JDOMSource(e);
}
private Source getSource(List versions) {
Element e = new Element("versions");
for (MCRMetadataVersion version : versions) {
Element v = new Element("version");
v.setAttribute("user", version.getUser());
v.setAttribute("date", MCRXMLFunctions.getISODate(version.getDate(), null));
v.setAttribute("r", Long.toString(version.getRevision()));
v.setAttribute("action", Character.toString(version.getType()));
e.addContent(v);
}
return new JDOMSource(e);
}
}
private static class MCRDeletedObjectResolver implements URIResolver {
/**
* Returns a deleted mcr object xml for the given id. If there is no such object a dummy object with an empty
* metadata element is returned.
*
* @param href
* an uri starting with deletedMcrObject:
* @param base
* may be null
*/
@Override
public Source resolve(String href, String base) throws TransformerException {
String[] parts = href.split(":");
MCRObjectID mcrId = MCRObjectID.getInstance(parts[parts.length - 1]);
LOGGER.info("Resolving deleted object {}", mcrId);
try {
MCRContent lastPresentVersion = MCRXMLMetadataManager.instance().retrieveContent(mcrId, -1);
if (lastPresentVersion == null) {
LOGGER.warn("Could not resolve deleted object {}", mcrId);
return new JDOMSource(MCRObjectFactory.getSampleObject(mcrId));
}
return lastPresentVersion.getSource();
} catch (IOException e) {
throw new TransformerException(e);
}
}
}
private static class MCRFileMetadataResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
String[] parts = href.split(":");
String completePath = parts[1];
String[] pathParts = completePath.split("/", 2);
MCRObjectID derivateID = MCRObjectID.getInstance(pathParts[0]);
MCRDerivate derivate = MCRMetadataManager.retrieveMCRDerivate(derivateID);
MCRObjectDerivate objectDerivate = derivate.getDerivate();
if (pathParts.length == 1) {
//only derivate is given;
Element fileset = new Element("fileset");
if (objectDerivate.getURN() != null) {
fileset.setAttribute("urn", objectDerivate.getURN());
for (MCRFileMetadata fileMeta : objectDerivate.getFileMetadata()) {
fileset.addContent(fileMeta.createXML());
}
}
return new JDOMSource(fileset);
}
MCRFileMetadata fileMetadata = objectDerivate.getOrCreateFileMetadata("/" + pathParts[1]);
return new JDOMSource(fileMetadata.createXML());
}
}
/**
* Redirect to different URIResolver that is defined via property. This resolver is meant to serve static content as
* no variable substitution takes place Example: MCR.URIResolver.redirect.alias=webapp:path/to/alias.xml
*/
private static class MCRRedirectResolver implements URIResolver {
private static final Logger LOGGER = LogManager.getLogger(MCRRedirectResolver.class);
@Override
public Source resolve(String href, String base) throws TransformerException {
String configsuffix = href.substring(href.indexOf(":") + 1);
// get the parameters from mycore.properties
String propertyName = "MCR.URIResolver.redirect." + configsuffix;
String propValue = MCRConfiguration2.getStringOrThrow(propertyName);
LOGGER.info("Redirect {} to {}", href, propValue);
return singleton.resolve(propValue, base);
}
}
/**
* Resolves an data url and returns the content.
*
* @see MCRDataURL
*/
private static class MCRDataURLResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
try {
final MCRDataURL dataURL = MCRDataURL.parse(href);
final MCRByteContent content = new MCRByteContent(dataURL.getData());
content.setSystemId(href);
content.setMimeType(dataURL.getMimeType());
content.setEncoding(dataURL.getCharset().name());
return content.getSource();
} catch (IOException e) {
throw new TransformerException(e);
}
}
}
private static class MCRI18NResolver implements URIResolver {
/**
* Resolves the I18N String value for the given property.
*
* Syntax: i18n:{i18n-code},{i18n-prefix}*,{i18n-prefix}*...
or
* i18n:{i18n-code}
*
* Result:
* <i18n>
* <translation key="key1">translation1</translation>
* <translation key="key2">translation2</translation>
* <translation key="key3">translation3</translation>
* </i18n>
*
*
* If just one i18n-code is passed, then the translation element is skipped.
*
* <i18n>
translation</i18n>
*
* @param href
* URI in the syntax above
* @param base
* not used
*
* @return the element with result format above
* @see javax.xml.transform.URIResolver
*/
@Override
public Source resolve(String href, String base) {
String target = href.substring(href.indexOf(":") + 1);
final Element i18nElement = new Element("i18n");
if (!target.contains("*") && !target.contains(",")) {
i18nElement.addContent(MCRTranslation.translate(target));
return new JDOMSource(i18nElement);
}
final String[] translationKeys = target.split(",");
// Combine translations to prevent duplicates
HashMap translations = new HashMap<>();
for (String translationKey : translationKeys) {
if (translationKey.endsWith("*")) {
final String prefix = translationKey.substring(0, translationKey.length() - 1);
translations.putAll(MCRTranslation.translatePrefix(prefix));
} else {
translations.put(translationKey, MCRTranslation.translate(translationKey));
}
}
translations.forEach((key, value) -> {
final Element translation = new Element("translation");
translation.setAttribute("key", key);
translation.setText(value);
i18nElement.addContent(translation);
});
return new JDOMSource(i18nElement);
}
}
}