com.tangosol.config.xml.DefaultProcessingContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of coherence Show documentation
Show all versions of coherence Show documentation
Oracle Coherence Community Edition
/*
* Copyright (c) 2000, 2022, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.config.xml;
import com.tangosol.config.ConfigurationException;
import com.tangosol.config.annotation.Injectable;
import com.tangosol.config.expression.Expression;
import com.tangosol.config.expression.ExpressionParser;
import com.tangosol.config.expression.LiteralExpression;
import com.tangosol.config.expression.ParameterResolver;
import com.tangosol.config.expression.Value;
import com.tangosol.config.xml.DocumentProcessor.Dependencies;
import com.tangosol.run.xml.QualifiedName;
import com.tangosol.run.xml.SimpleAttribute;
import com.tangosol.run.xml.XmlAttribute;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;
import com.tangosol.run.xml.XmlValue;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.ExternalizableHelper;
import com.tangosol.util.ResourceRegistry;
import com.tangosol.util.UUID;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
/**
* The default implementation of a {@link ProcessingContext}.
*
* @author bo 2011.06.14
* @since Coherence 12.1.2
*/
public class DefaultProcessingContext
implements ProcessingContext, AutoCloseable
{
// ----- constructors ----------------------------------------------------
/**
* Constructs a root {@link ProcessingContext} using default {@link DocumentProcessor} {@link DocumentProcessor.Dependencies}.
*/
public DefaultProcessingContext()
{
this((Dependencies) null);
}
/**
* Constructs a root {@link ProcessingContext} with the specified {@link DocumentProcessor} {@link DocumentProcessor.Dependencies}.
*
* @param dependencies the {@link DocumentProcessor.Dependencies} for the {@link ProcessingContext}
*/
public DefaultProcessingContext(Dependencies dependencies)
{
m_dependencies = dependencies == null ? new DocumentProcessor.DefaultDependencies() : dependencies;
m_ctxParent = null;
m_xmlElement = null;
m_mapNamespaceURIsByPrefix = new LinkedHashMap<>();
m_mapNamespaceHandlersByURI = new LinkedHashMap<>();
m_mapCookiesByType = new HashMap<>();
m_mapPropertyPaths = new HashMap<>();
m_mapAttributeProcessorsByType = new HashMap<>();
m_mapElementProcessorsByType = new HashMap<>();
m_setProcessedChildElements = new HashSet<>();
}
/**
* Constructs a root {@link ProcessingContext} for a given {@link XmlElement} using default DocumentProcessor
* dependencies.
*
* @param xmlElement the {@link XmlElement} for the {@link ProcessingContext}
*/
public DefaultProcessingContext(XmlElement xmlElement)
{
this(new DocumentProcessor.DefaultDependencies());
m_ctxParent = null;
m_xmlElement = xmlElement;
}
/**
* Constructs a sub-{@link ProcessingContext} of another {@link ProcessingContext}.
*
* @param ctxParent the parent {@link ProcessingContext} for this {@link ProcessingContext}
* @param xmlElement the {@link XmlElement} for the sub-{@link ProcessingContext}
*/
public DefaultProcessingContext(DefaultProcessingContext ctxParent, XmlElement xmlElement)
{
this(ctxParent.getDependencies());
m_ctxParent = ctxParent;
m_xmlElement = xmlElement;
}
/**
* Constructs a root {@link ProcessingContext} for a given {@link XmlElement}.
*
* @param dependencies the {@link DocumentProcessor.Dependencies} for the {@link ProcessingContext}
* @param xmlElement the {@link XmlElement} for the {@link ProcessingContext}
*/
public DefaultProcessingContext(Dependencies dependencies, XmlElement xmlElement)
{
this(dependencies);
m_ctxParent = null;
m_xmlElement = xmlElement;
if (m_xmlElement != null)
{
loadNamespaceHandlers(m_xmlElement);
}
}
// ----- ResourceResolver interface -------------------------------------
/**
* {@inheritDoc}
*/
@Override
public R getResource(Class clsResource)
{
R resource = getCookie(clsResource);
if (resource == null && getResourceRegistry() != null)
{
resource = getResourceRegistry().getResource(clsResource);
}
return resource;
}
/**
* {@inheritDoc}
*/
@Override
public R getResource(Class clsResource, String sResourceName)
{
R resource = getCookie(clsResource, sResourceName);
if (resource == null && getResourceRegistry() != null)
{
resource = getResourceRegistry().getResource(clsResource, sResourceName);
}
return resource;
}
// ----- ProcessingContext interface ------------------------------------
/**
* {@inheritDoc}
*/
@Override
public ResourceRegistry getResourceRegistry()
{
return m_dependencies.getResourceRegistry();
}
/**
* {@inheritDoc}
*/
@Override
public ParameterResolver getDefaultParameterResolver()
{
return m_dependencies.getDefaultParameterResolver();
}
/**
* {@inheritDoc}
*/
@Override
public ClassLoader getContextClassLoader()
{
return m_dependencies.getContextClassLoader();
}
/**
* {@inheritDoc}
*/
@Override
public void addCookie(Class clzCookie, String sName, T value)
{
HashMap mapCookiesByName = m_mapCookiesByType
.computeIfAbsent(clzCookie, k -> new HashMap<>());
mapCookiesByName.put(sName, value);
}
/**
* {@inheritDoc}
*/
@Override
public void addCookie(Class clzCookie, T cookie)
{
addCookie(clzCookie, clzCookie.getName(), cookie);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public T getCookie(Class clzCookie, String sName)
{
HashMap mapCookiesByName = m_mapCookiesByType.get(clzCookie);
T cookie = mapCookiesByName == null ? null : (T) mapCookiesByName.get(sName);
return cookie == null ? (isRootContext() ? null : m_ctxParent.getCookie(clzCookie, sName)) : cookie;
}
/**
* {@inheritDoc}
*/
@Override
public T getCookie(Class clzCookie)
{
return clzCookie == null ? null : getCookie(clzCookie, clzCookie.getName());
}
/**
* {@inheritDoc}
*/
@Override
public void definePropertyPath(String sBeanPropertyName, String sXmlPath)
{
m_mapPropertyPaths.put(sBeanPropertyName, sXmlPath);
}
/**
* {@inheritDoc}
*/
@Override
public void registerProcessor(Class clzType, AttributeProcessor processor)
{
m_mapAttributeProcessorsByType.put(clzType, processor);
}
/**
* {@inheritDoc}
*/
@Override
public void registerProcessor(Class clzType, ElementProcessor processor)
{
m_mapElementProcessorsByType.put(clzType, processor);
}
/**
* {@inheritDoc}
*/
@Override
public void registerAttributeType(Class clzType)
{
registerProcessor(clzType, new SimpleAttributeProcessor<>(clzType));
}
/**
* {@inheritDoc}
*/
@Override
public void registerElementType(Class clzType)
{
registerProcessor(clzType, new SimpleElementProcessor<>(clzType));
}
/**
* {@inheritDoc}
*/
@Override
public ExpressionParser getExpressionParser()
{
return m_dependencies.getExpressionParser();
}
/**
* {@inheritDoc}
*/
public Object processDocument(XmlElement xmlElement)
throws ConfigurationException
{
return processElement(xmlElement);
}
/**
* {@inheritDoc}
*/
public Object processDocumentAt(URI uri)
throws ConfigurationException
{
return processDocument(XmlHelper.loadFileOrResource(uri.toString(), "cache configuration",
m_dependencies.getContextClassLoader()));
}
/**
* {@inheritDoc}
*/
public Object processDocumentAt(String sLocation)
throws ConfigurationException
{
return processDocument(XmlHelper.loadFileOrResource(sLocation, "cache configuration",
m_dependencies.getContextClassLoader()));
}
/**
* {@inheritDoc}
*/
public Object processDocument(String sXml)
throws ConfigurationException
{
return processDocument(XmlHelper.loadXml(sXml));
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Object processElement(XmlElement xmlElement)
throws ConfigurationException
{
// NOTE: Seven sequential tasks are required to "process" an XmlElement. These must
// be performed in a specific order as they are dependent on each other.
Object oResult = null;
// we need a list of XmlAttributes for the XmlElement as we may use these in multiple places.
ArrayList lstAttributes = new ArrayList<>(xmlElement.getAttributeMap().size());
for (String sAttributeName : ((Set) xmlElement.getAttributeMap().keySet()))
{
lstAttributes.add(new SimpleAttribute(xmlElement, sAttributeName, xmlElement.getAttribute(sAttributeName)));
}
// we need to know if a new namespace was loaded or if this is the root
// element to determine if the element requires preprocessing
boolean fElementRequiresPreprocessing = isRootContext() || xmlElement.getParent() == null;
// TASK 1: Start a new sub context for the element we're about to process.
DefaultProcessingContext context = new DefaultProcessingContext(this, xmlElement);
// TASK 2: Ensure that all of NamespaceHandler(s) declared in the element (using xmlns) are defined
// in the sub-context.
for (XmlAttribute attribute : lstAttributes)
{
QualifiedName qnAttribute = attribute.getQualifiedName();
// only process "xmlns" declarations
if (qnAttribute.getLocalName().equals("xmlns"))
{
String sURI = attribute.getXmlValue().getString();
try
{
// ensure that the declared Xml Namespaces are available in the context of this element.
context.ensureNamespaceHandler(qnAttribute.getPrefix(), new URI(sURI));
// a new namespace was loaded so we must perform pre-processing
fElementRequiresPreprocessing = true;
}
catch (URISyntaxException uriSyntaxException)
{
throw new ConfigurationException(String.format("Invalid URI '%s' specified for Xml Namespace '%s'",
sURI, qnAttribute.getPrefix()), "You must specify a valid URI for the Xml Namespace.",
uriSyntaxException);
}
}
}
// TASK 3: Pre-process the element using all visible NamespaceHandler
// DocumentPreprocessors until no more pre-precessing is required
if (fElementRequiresPreprocessing)
{
Iterable namespaceHandlers = context.getNamespaceHandlers();
boolean fRevisit;
do
{
fRevisit = false;
for (NamespaceHandler namespaceHandler : namespaceHandlers)
{
DocumentPreprocessor preprocessor = namespaceHandler.getDocumentPreprocessor();
if (preprocessor != null)
{
//noinspection ConstantConditions
fRevisit = fRevisit || preprocessor.preprocess(context, xmlElement);
if (fRevisit)
{
break;
}
}
}
}
while (fRevisit);
}
// TASK 4: Find an appropriate ElementProcessor for the element we're about to process
QualifiedName qnElement = xmlElement.getQualifiedName();
NamespaceHandler nsElement = context.getNamespaceHandler(qnElement.getPrefix());
ElementProcessor> procElement = nsElement == null ? null : nsElement.getElementProcessor(xmlElement);
if (nsElement == null)
{
throw new ConfigurationException(String.format(
"A NamespaceHandler could not be located for the namespace [%s] in the element [%s]",
qnElement.getPrefix(),
xmlElement), "A NamespaceHandler implementation for the namespace must be defined.");
}
else if (procElement == null)
{
throw new ConfigurationException(String.format("An ElementProcessor could not be located for the element [%s]",
qnElement), "The specified element is unknown to the NamespaceHandler implementation. " + "Perhaps the xml element is foreign to the Xml Namespace?");
}
else
{
// TASK 5: Process all the xml attributes declared in the element.
for (XmlAttribute attribute : lstAttributes)
{
QualifiedName qnAttribute = attribute.getQualifiedName();
NamespaceHandler nsAttribute;
// locate the AttributeProcessor for the XmlAttribute using the appropriate NamespaceHandler
if (qnAttribute.hasPrefix())
{
// when an attribute is defined in a specific namespace, use the namespace of the attribute
nsAttribute = getNamespaceHandler(qnAttribute.getPrefix());
}
else
{
// when an attribute is not defined in a specific namespace, use the namespace of the element.
nsAttribute = nsElement;
}
AttributeProcessor> procAttribute = nsAttribute == null
? null : nsAttribute.getAttributeProcessor(attribute);
//noinspection StatementWithEmptyBody
if (nsAttribute == null || procAttribute == null)
{
// SKIP: when we don't have an NamespaceHandler or AttributeProcessor for the attribute
// we simply ignore the request because the dependency injection framework may/can try to access
// the content directly.
}
else
{
procAttribute.process(context, attribute);
}
}
// TASK 6: Use the located ElementProcessor to process the element in its context.
ConditionalElementProcessor> procConditional = procElement instanceof ConditionalElementProcessor
? ((ConditionalElementProcessor>) procElement) : null;
if (procConditional == null || procConditional.accepts(context, xmlElement))
{
oResult = procElement.process(context, xmlElement);
}
}
// TASK 7: Terminate the current context as it's now out of scope.
context.terminate();
// remember that we've processed this element so that if a call to
// processRemainingElements is made, we can skip this one
m_setProcessedChildElements.add(xmlElement);
return oResult;
}
/**
* {@inheritDoc}
*/
public Object processElement(String sXml)
throws ConfigurationException
{
return processElement(XmlHelper.loadXml(sXml));
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public T processOnlyElementOf(XmlElement xmlElement)
throws ConfigurationException
{
// the xmlElement must only have a single child
if (xmlElement.getElementList().size() == 1)
{
// process the child element
return (T) processElement((XmlElement) xmlElement.getElementList().get(0));
}
else
{
// expected only a single element in custom-provider
throw new ConfigurationException(String.format("Only a single element is permitted in the %s element.",
xmlElement), String.format("Please consult the documentation regarding use of the '%s' namespace",
new QualifiedName(xmlElement).getPrefix()));
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Map processElementsOf(XmlElement xmlElement)
throws ConfigurationException
{
// process all the children of the xmlElement
LinkedHashMap mapResult = new LinkedHashMap<>();
for (XmlElement xmlChild : (Iterable) xmlElement.getElementList())
{
String sId = xmlChild.getAttributeMap().containsKey("id")
? xmlChild.getAttribute("id").getString() : new UUID().toString();
if (sId.trim().length() == 0)
{
sId = new UUID().toString();
}
mapResult.put(sId, processElement(xmlChild));
}
return mapResult;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public Map processForeignElementsOf(XmlElement xmlElement)
throws ConfigurationException
{
String sPrefix = xmlElement.getQualifiedName().getPrefix();
// process all the children of the xmlElement
LinkedHashMap mapResult = new LinkedHashMap<>();
for (XmlElement xmlChild : (Iterable) xmlElement.getElementList())
{
if (!xmlChild.getQualifiedName().getPrefix().equals(sPrefix))
{
String sId = xmlChild.getAttributeMap().containsKey("id")
? xmlChild.getAttribute("id").getString() : new UUID().toString();
if (sId.trim().length() == 0)
{
sId = new UUID().toString();
}
mapResult.put(sId, processElement(xmlChild));
}
}
return mapResult;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public Map processRemainingElementsOf(XmlElement xmlElement)
throws ConfigurationException
{
// process all the children of the xmlElement that aren't in the set of elements we've already processed
LinkedHashMap mapResult = new LinkedHashMap<>();
for (XmlElement xmlChild : (Iterable) xmlElement.getElementList())
{
if (!m_setProcessedChildElements.contains(xmlChild))
{
String sId = xmlChild.getAttributeMap().containsKey("id")
? xmlChild.getAttribute("id").getString() : new UUID().toString();
if (sId.trim().length() == 0)
{
sId = new UUID().toString();
}
mapResult.put(sId, processElement(xmlChild));
}
}
return mapResult;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public T processRemainingElementOf(XmlElement xmlElement)
throws ConfigurationException
{
Map mapResults = processRemainingElementsOf(xmlElement);
int cSize = mapResults.size();
if (cSize == 1)
{
return (T) mapResults.values().iterator().next();
}
else
{
throw new ConfigurationException(String.format(
"Expected a single remaining element to process in %s after processing the elements %s but there were %d elements remaining",
xmlElement, xmlElement.getQualifiedName(), m_setProcessedChildElements.size()), String.format(
"The ElementProcessor implementation for %s makes an incorrect assumption about the number of remaining elements.",
xmlElement.getQualifiedName()));
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isPropertyDefined(String sPropertyName, XmlElement xmlParent)
throws ConfigurationException
{
if (sPropertyName == null || xmlParent == null)
{
return false;
}
// assume that a property in "this context" (ie: .) is always found
boolean fPropertyFound = sPropertyName.equals(".");
// STEP 1: Attempt to find the property value using an XmlAttribute defined by the XmlElement
if (!fPropertyFound)
{
fPropertyFound = getPropertyAttribute(xmlParent, sPropertyName) != null;
}
// STEP 2: Attempt to find the property value using content defined by the XmlElement
if (!fPropertyFound)
{
fPropertyFound = getPropertyElement(xmlParent, sPropertyName) != null;
}
return fPropertyFound;
}
/**
* {@inheritDoc}
*/
@Override
public B inject(B bean, XmlElement xmlElement)
throws ConfigurationException
{
for (Method method : bean.getClass().getMethods())
{
Type[] aParameterTypes = method.getGenericParameterTypes();
// we can only inject into methods annotated with @Injectable
Injectable annInjectable = method.getAnnotation(Injectable.class);
if (annInjectable != null)
{
if (aParameterTypes.length == 0 && !method.getReturnType().equals(Void.TYPE))
{
// perform getter injection (ie: inject into the value returned by the getter)
Object oInjectable = null;
try
{
// determine the injectable from the getter
oInjectable = method.invoke(bean);
// we can only inject into an object that's not a bean
if (oInjectable != null && oInjectable != bean)
{
// decide how to locate the XmlElement containing the content to use for injection
String sPropertyName = annInjectable.value();
XmlElement xmlProperty;
if (sPropertyName.isEmpty())
{
// when no name is specified we (automatically)
// determine the property name based on the method name
sPropertyName = getPropertyName(method);
}
if (sPropertyName.equals("."))
{
// when a "." we use "this" context for injection content
xmlProperty = xmlElement;
}
else
{
// find the element based on the name of the property
xmlProperty = getPropertyElement(xmlElement, sPropertyName);
}
if (xmlProperty != null)
{
inject(oInjectable, xmlProperty);
}
}
}
catch (ConfigurationException e)
{
throw e;
}
catch (Exception e)
{
throw new ConfigurationException(String.format(
"Could not inject a value into the instance '%s' using reflection "
+ " produced by the annotated method '%s' of '%s'", oInjectable, method,
bean.getClass().getName()), "Please resolve the causing exception.", e);
}
}
else if (aParameterTypes.length == 1)
{
// perform setter injection (ie: call a setter with a value)
// use the method name (or @Injectable) to determine the property name
String sPropertyName = getPropertyName(method);
// use the setter parameter type to determine the property type
// the type of the property
Type typeProperty = aParameterTypes[0];
// the value of the property
Value value = getPropertyValue(sPropertyName, typeProperty, xmlElement, false);
if (value != null)
{
// use the defined value to inject
Object oPropertyValue = value.get();
{
try
{
method.invoke(bean, oPropertyValue);
}
catch (Exception e)
{
throw new ConfigurationException(String.format(
"Could not inject the property '%s' using reflection "
+ "with the annotated method '%s' of '%s'", sPropertyName, method,
bean.getClass().getName()), "Please resolve the causing exception.", e);
}
}
}
}
}
}
return bean;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({"unchecked"})
@Override
public T getMandatoryProperty(String sPath, Type typeProperty, XmlElement xmlParent)
throws ConfigurationException
{
Value value = getPropertyValue(sPath, typeProperty, xmlParent, true);
if (value == null)
{
// when we can't find a value as an attribute or element we must throw an exception.
throw new ConfigurationException(String.format(
"The expected property [%s] is not defined in element [%s].", sPath, xmlParent), String.format(
"Please consult the documentation for the use of the %s namespace",
m_xmlElement.getQualifiedName().getPrefix()));
}
else
{
return (T) value.get();
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public T getOptionalProperty(String sPropertyName, Type typeProperty, T defaultValue, XmlElement xmlElement)
throws ConfigurationException
{
Value value = getPropertyValue(sPropertyName, typeProperty, xmlElement, true);
if (value == null)
{
return defaultValue;
}
else
{
return (T) value.get();
}
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public void loadNamespaceHandlers(XmlElement xmlElement)
{
int cAttribute = xmlElement.getAttributeMap().size();
if (cAttribute == 0)
{
// nothing to load
return;
}
ArrayList lstAttributes = new ArrayList<>(cAttribute);
for (String sAttributeName : ((Set) xmlElement.getAttributeMap().keySet()))
{
lstAttributes.add(new SimpleAttribute(xmlElement, sAttributeName, xmlElement.getAttribute(sAttributeName)));
}
for (XmlAttribute attribute : lstAttributes)
{
QualifiedName qnAttribute = attribute.getQualifiedName();
// only process "xmlns" declarations
if (qnAttribute.getLocalName().equals("xmlns"))
{
String sURI = attribute.getXmlValue().getString();
try
{
// ensure that the declared Xml Namespaces are available in the context of this element.
ensureNamespaceHandler(qnAttribute.getPrefix(), new URI(sURI));
}
catch (URISyntaxException uriSyntaxException)
{
throw new ConfigurationException(String.format("Invalid URI '%s' specified for Xml Namespace '%s'",
sURI, qnAttribute.getPrefix()), "You must specify a valid URI for the Xml Namespace.",
uriSyntaxException);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public NamespaceHandler ensureNamespaceHandler(String sPrefix, NamespaceHandler handler)
throws ConfigurationException
{
URI uri = m_mapNamespaceURIsByPrefix.get(sPrefix);
if (uri == null)
{
try
{
uri = new URI("class://" + handler.getClass().getName());
}
catch (URISyntaxException e)
{
throw new ConfigurationException(String.format(
"Failed to create a valid URI for the specified namespace class [%s] with prefix [%s]",
handler.getClass().getName(), sPrefix), "The implemented URI encoding is invalid", e);
}
m_mapNamespaceURIsByPrefix.put(sPrefix, uri);
m_mapNamespaceHandlersByURI.put(uri, handler);
// call-back the NamespaceHandler
handler.onStartNamespace(this, m_xmlElement, sPrefix, uri);
return handler;
}
else
{
return m_mapNamespaceHandlersByURI.get(uri);
}
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public NamespaceHandler ensureNamespaceHandler(String sPrefix, URI uri)
throws ConfigurationException
{
// determine if the NamespaceHandler for the specified URI is already defined
NamespaceHandler namespaceHandler = getNamespaceHandler(uri);
if (namespaceHandler == null || !getNamespaceURI(sPrefix).equals(uri))
{
String sScheme = uri.getScheme();
// ensure that we don't already have a NamespaceHandler with the specified prefix in the context
if (m_mapNamespaceURIsByPrefix.containsKey(sPrefix))
{
throw new ConfigurationException(String.format(
"Duplicate definition for the namespace prefix [%s] and URI [%s] encountered in the element [%s]",
sPrefix, uri, m_xmlElement), "Duplicate definitions of namespaces is not permitted.");
}
else if (sScheme.equals("class"))
{
String sClassName = (uri.getHost() == null) ? uri.getSchemeSpecificPart() : uri.getHost();
try
{
Class> clzNamespaceHandler = ExternalizableHelper.loadClass(sClassName,
m_dependencies.getContextClassLoader(), null);
// ensure that the class is a NamespaceHandler
if (NamespaceHandler.class.isAssignableFrom(clzNamespaceHandler))
{
try
{
// find the no args constructor for the NamespaceHandler
Constructor constructor =
(Constructor) clzNamespaceHandler.getConstructor();
// instantiate the NamespaceHandler
namespaceHandler = constructor.newInstance();
// register the NamespaceHandler with this context
m_mapNamespaceHandlersByURI.put(uri, namespaceHandler);
// register the prefix for the NamespaceHandler
m_mapNamespaceURIsByPrefix.put(sPrefix, uri);
// call-back the NamespaceHandler
namespaceHandler.onStartNamespace(this, m_xmlElement, sPrefix, uri);
return namespaceHandler;
}
catch (Exception exception)
{
throw new ConfigurationException(String.format("Can't instantiate the NamespaceHandler [%s]\n",
sClassName), "Please ensure that the specified class is public and has a no-args constructor", exception);
}
}
else
{
throw new ConfigurationException(String
.format("The declared class [%s] does not implement the %s interface", sClassName, NamespaceHandler.class
.getName()), "To use a class as a NamespaceHandler it must implement the appropriate interface.");
}
}
catch (ClassNotFoundException e)
{
throw new ConfigurationException(String.format(
"Can't instantiate the NamespaceHandler [%s] as the class is not found\n",
sClassName), "Please ensure that the specified class is an instance of NamespaceHandler interface", e);
}
}
else if (sScheme.equalsIgnoreCase("http") || sScheme.equalsIgnoreCase("https"))
{
// for http/https-based schemes, we assume there is a NamespaceHandler already registered
return getNamespaceHandler(sPrefix);
}
else
{
throw new ConfigurationException(String.format(
"Can't instantiate a suitable NamespaceHandler as the URI [%s] scheme is unknown.\n",
uri), "Please ensure that the specified URI refers to a class that implements the NamespaceHandler interface");
}
}
else
{
return namespaceHandler;
}
}
/**
* {@inheritDoc}
*/
@Override
public NamespaceHandler getNamespaceHandler(String sPrefix)
{
URI uri = getNamespaceURI(sPrefix);
return uri == null ? null : getNamespaceHandler(uri);
}
/**
* {@inheritDoc}
*/
@Override
public NamespaceHandler getNamespaceHandler(URI uri)
{
NamespaceHandler namespaceHandler = m_mapNamespaceHandlersByURI.get(uri);
return namespaceHandler == null
? (isRootContext() ? null : m_ctxParent.getNamespaceHandler(uri)) : namespaceHandler;
}
/**
* {@inheritDoc}
*/
@Override
public URI getNamespaceURI(String sPrefix)
{
URI uri = m_mapNamespaceURIsByPrefix.get(sPrefix);
return uri == null ? (isRootContext() ? null : m_ctxParent.getNamespaceURI(sPrefix)) : uri;
}
/**
* {@inheritDoc}
*/
@Override
public Iterable getNamespaceHandlers()
{
LinkedHashMap mapNamespaceHandlersByURI = new LinkedHashMap<>();
DefaultProcessingContext ctx = this;
while (ctx != null)
{
for (Entry e : ctx.m_mapNamespaceHandlersByURI.entrySet())
{
if (!mapNamespaceHandlersByURI.containsKey(e.getKey()))
{
mapNamespaceHandlersByURI.put(e.getKey(), e.getValue());
}
}
ctx = ctx.m_ctxParent;
}
return mapNamespaceHandlersByURI.values();
}
// ----- DefaultProcessingContext methods -------------------------------
/**
* Obtains the {@link DocumentProcessor} {@link DocumentProcessor.Dependencies} for the {@link ProcessingContext}.
*
* @return the {@link DocumentProcessor.Dependencies}
*/
public Dependencies getDependencies()
{
return m_dependencies;
}
/**
* Attempts to resolve the named property of the specified type in the current context and
* if required will parse the specified {@link XmlElement} in order to do so.
*
* @param sPropertyName the name or xml path to the property
* @param typeProperty the required type of the property value
* @param xmlParent the parent element in which the property may be found
* @param fOnlyUsePropertyName when true
the specified property name must be
* used resolve the property value. when false
* attempts may be made to resolve the property just the type
* name if the specified property name doesn't resolve a property
*
* @return The {@link Value} representing the property or null
if the property
* could not be located
* @throws ConfigurationException if the property was but could not be processed or is
* of the incorrect type
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public Value getPropertyValue(String sPropertyName, Type typeProperty, XmlElement xmlParent,
boolean fOnlyUsePropertyName)
throws ConfigurationException
{
if (sPropertyName == null)
{
throw new NullPointerException("Property Name can't be null");
}
if (typeProperty == null)
{
throw new NullPointerException("Property Type can't be null");
}
if (xmlParent == null)
{
throw new NullPointerException("XmlElement in which to locate a property can't be null");
}
// assume we haven't resolved the property value
Object oValue = null;
boolean fValueResolved = false;
// determine concrete class for the property
Class> clzProperty = ClassHelper.getClass(typeProperty);
// STEP 1: Attempt to resolve the property value using an XmlAttribute defined by the XmlElement
if (!sPropertyName.equals("."))
{
XmlAttribute xmlValue = getPropertyAttribute(xmlParent, sPropertyName);
if (xmlValue != null)
{
// no matter what happens now we assume we can and have resolved the property value
fValueResolved = true;
// determine the AttributeProcessor to process the XmlAttribute content into the property value
NamespaceHandler nsAttribute = getNamespaceHandler(xmlValue.getQualifiedName().getPrefix());
AttributeProcessor> procAttribute = nsAttribute == null
? null : nsAttribute.getAttributeProcessor(xmlValue);
// when there is no AttributeProcessor, attempt to locate a type-based one
if (procAttribute == null)
{
procAttribute = getAttributeProcessor(clzProperty);
}
if (procAttribute == null)
{
// when we can't find a suitable AttributeProcessor, assume we can use the attribute as a String
oValue = xmlValue.getXmlValue().getString();
}
else
{
oValue = procAttribute.process(this, xmlValue);
}
}
}
// STEP 2: Attempt to resolve the property value using content defined by the XmlElement
if (!fValueResolved)
{
XmlElement xmlValue = sPropertyName.equals(".") ? xmlParent : getPropertyElement(xmlParent, sPropertyName);
if (xmlValue != null)
{
// remember that we've processed this element (when it's not the parent!)
if (xmlValue != xmlParent)
{
m_setProcessedChildElements.add(xmlValue);
}
// determine the ElementProcessor to process the XmlElement content into the property value
NamespaceHandler nsElement = getNamespaceHandler(xmlValue.getQualifiedName().getPrefix());
ElementProcessor> procElement = nsElement == null ? null : nsElement.getElementProcessor(xmlValue);
// when there is no ElementProcessor, attempt to locate a type-based one
if (procElement == null)
{
procElement = getElementProcessor(clzProperty);
}
// when we can't find a suitable ElementProcessor, try to resolve the value another way
// (perhaps it's a collection, an array or just plain xml)
if (procElement == null)
{
// determine if we are dealing with a collection (or array) of some type?
Class> clzComponent = ClassHelper.getComponentType(typeProperty);
// the style of processing is somewhat dependent on the number of children in the element
int cChildren = xmlValue.getElementList().size();
if (clzComponent == null)
{
if (cChildren == 0)
{
// when there is no processor for the element and the element value is simply an XmlValue
// assume we can use the value as a String (if it's not empty)
if (!XmlHelper.isEmpty(xmlValue))
{
oValue = xmlValue.getString();
// we have now resolved the property value
fValueResolved = true;
}
}
else if (cChildren == 1)
{
// when there is no processor for the property but the property value contains a single
// element, process the element itself
oValue = processOnlyElementOf(xmlValue);
// we have now resolved the property value
fValueResolved = true;
}
else
{
// when there is no processor and there's a collection, we assume the required value is
// just the element (xml).
oValue = xmlValue;
// we have now resolved the property value
fValueResolved = true;
}
}
else
{
// create a collection from the element based on the children
// we have now resolved the property value
fValueResolved = true;
// instantiate the collection for the property (or something that is assignment compatible)
boolean fArray = clzProperty.isArray();
if (fArray)
{
oValue = Array.newInstance(clzComponent, cChildren);
}
else if (clzProperty.isInterface())
{
if (clzProperty.equals(List.class))
{
oValue = new ArrayList(cChildren);
}
else if (clzProperty.equals(Set.class))
{
oValue = new LinkedHashSet(cChildren);
}
else if (clzProperty.equals(Queue.class))
{
oValue = new ArrayDeque(cChildren);
}
else if (clzProperty.equals(SortedSet.class))
{
oValue = new TreeSet();
}
else
{
throw new ConfigurationException(String.format(
"Unsupported collection type [%s] encountered with the property [%s] in [%s]",
typeProperty, sPropertyName,
xmlValue), "The specified interface type is not supported. Please use a more specific type.");
}
}
else
{
try
{
oValue = clzProperty.getDeclaredConstructor().newInstance();
}
catch (Exception e)
{
throw new ConfigurationException(String.format(
"Failed to instantiate the required type [%s] for the property [%s] in [%s]",
typeProperty, sPropertyName,
xmlValue), "Ensure that the specified type has a public no-args constructor", e);
}
}
// process each of the children, adding them to the collection and ensuring they
// are of the correct type
int idx = 0;
for (XmlElement xmlChild : (Iterable) xmlValue.getElementList())
{
Object oChild = processElement(xmlChild);
// ensure the child value is compatible with the component type
if (clzComponent.isInstance(oChild))
{
if (fArray)
{
((Object[]) oValue)[idx] = oChild;
}
else
{
((Collection