package org.jboss.windup.config.parser;
import static org.joox.JOOX.$;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.jboss.forge.furnace.Furnace;
import org.jboss.forge.furnace.addons.Addon;
import org.jboss.forge.furnace.proxy.Proxies;
import org.jboss.forge.furnace.services.Imported;
import org.jboss.forge.furnace.util.Annotations;
import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.builder.RuleProviderBuilder;
import org.jboss.windup.config.exception.ConfigurationException;
import org.jboss.windup.config.loader.RuleLoaderContext;
import org.jboss.windup.util.exception.WindupException;
import org.ocpsoft.rewrite.config.ConfigurationRuleBuilder;
import org.ocpsoft.rewrite.config.ConfigurationRuleParameterWhere;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Handles maintaining the list of handlers associated with each tag/namespace pair, as well as selecting the right
* handler for element. This also maintains the current {@link RuleProviderBuilder} being constructed.
*/
public class ParserContext
{
private final List<AbstractRuleProvider> ruleProviders = new ArrayList<>();
private RuleProviderBuilder builder;
private ConfigurationRuleBuilder rule;
private ConfigurationRuleParameterWhere where;
private final Map<HandlerId, ElementHandler<?>> handlers = new HashMap<>();
private final RuleLoaderContext ruleLoaderContext;
/**
* The addon containing the xml file currently being parsed. This is needed mainly because of the classloader that
* loaded the Addon (XSLTTransformation needs it.)
*/
private Addon addonContainingInputXML;
/**
* The folder containing the xml file currently being parse. This should be the root folder from which any other
* resource lookups should be based. Eg, it may be the user scripts folder.
*
* If this is set, it should take precedent over the Addon for resource lookups.
*/
private Path xmlInputRootPath;
private Path xmlInputPath;
/**
* Initialize tag handlers based upon the provided furnace instance.
*/
public ParserContext(Furnace furnace, RuleLoaderContext ruleLoaderContext)
{
this.ruleLoaderContext = ruleLoaderContext;
@SuppressWarnings("rawtypes")
Imported<ElementHandler> loadedHandlers = furnace.getAddonRegistry().getServices(ElementHandler.class);
for (ElementHandler<?> handler : loadedHandlers)
{
NamespaceElementHandler annotation = Annotations.getAnnotation(handler.getClass(),
NamespaceElementHandler.class);
if (annotation != null)
{
HandlerId handlerID = new HandlerId(annotation.namespace(), annotation.elementName());
if (handlers.containsKey(handlerID))
{
String className1 = Proxies.unwrapProxyClassName(handlers.get(handlerID).getClass());
String className2 = Proxies.unwrapProxyClassName(handler.getClass());
throw new WindupException("Multiple handlers registered with id: " + handlerID + " Classes are: "
+ className1 + " and " + className2);
}
handlers.put(handlerID, handler);
}
}
}
/**
* Process the provided {@link Element} with the appropriate handler for it's namespace and tag name.
*/
@SuppressWarnings("unchecked")
public <T> T processElement(Element element) throws ConfigurationException
{
String namespace = $(element).namespaceURI();
String tagName = $(element).tag();
ElementHandler<?> handler = handlers.get(new HandlerId(namespace, tagName));
if (handler != null)
{
Object o = handler.processElement(this, element);
return (T) o;
}
throw new ConfigurationException("No Handler registered for element named [" + tagName
+ "] in namespace: [" + namespace + "]");
}
/**
* Processes the XML document at the provided {@link URL} and returns a result from the namespace element handlers.
*/
public <T> T processDocument(URI uri) throws ConfigurationException
{
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
DocumentBuilder dBuilder = null;
try
{
dBuilder = dbFactory.newDocumentBuilder();
}
catch (Exception e)
{
throw new WindupException("Failed to build xml parser due to: " + e.getMessage(), e);
}
try
{
Document doc = dBuilder.parse(uri.toString());
return processElement(doc.getDocumentElement());
}
catch (Exception e)
{
throw new WindupException("Failed to parse document at: " + uri + ", due to: " + e.getMessage(), e);
}
}
/**
* Gets a {@link List} of all {@link AbstractRuleProvider}s found so far.
*/
public List<AbstractRuleProvider> getRuleProviders()
{
return this.ruleProviders;
}
/**
* Adds the constructed {@link AbstractRuleProvider}.
*/
public void addRuleProvider(AbstractRuleProvider provider)
{
this.ruleProviders.add(provider);
}
/**
* Gets the {@link RuleProviderBuilder} that is currently in the process of being built.
*/
public RuleProviderBuilder getBuilder()
{
return builder;
}
/**
* Sets the {@link RuleProviderBuilder} that is currently in the process of being built.
*/
public void setBuilder(RuleProviderBuilder builder)
{
this.builder = builder;
}
/**
* Gets the current where conditional
*/
public ConfigurationRuleParameterWhere getWhere()
{
return where;
}
/**
* Sets the current where conditional
*/
public void setWhere(ConfigurationRuleParameterWhere where)
{
this.where = where;
}
/**
* Sets the current Rule
*/
public void setRule(ConfigurationRuleBuilder rule)
{
this.rule = rule;
}
/**
* Gets the current Rule
*/
public ConfigurationRuleBuilder getRule()
{
return rule;
}
/**
* The addon containing the xml file currently being parsed.
*/
public void setAddonContainingInputXML(Addon addonContainingInputXML)
{
this.addonContainingInputXML = addonContainingInputXML;
}
/**
* The addon containing the xml file currently being parsed.
*/
public Addon getAddonContainingInputXML()
{
return addonContainingInputXML;
}
/**
* The path to the rule xml file itself (eg, /path/to/rule.windup.xml).
*/
public void setXmlInputPath(Path xmlInputPath)
{
this.xmlInputPath = xmlInputPath;
}
/**
* The path to the rule xml file itself (eg, /path/to/rule.windup.xml).
*/
public Path getXmlInputPath()
{
return this.xmlInputPath;
}
/**
* The folder containing the xml file currently being parsed. This should be the root folder from which any other
* resource lookups should be based. Eg, it may be the user scripts folder.
*
* If this is set, it should take precedent over the Addon for resource lookups.
*/
public void setXmlInputRootPath(Path xmlRootInputPath)
{
this.xmlInputRootPath = xmlRootInputPath;
}
/**
* The folder containing the xml file currently being parsed. This should be the root folder from which any other
* resource lookups should be based. Eg, it may be the user scripts folder.
*
* If this is set, it should take precedent over the Addon for resource lookups.
*/
public Path getXmlInputRootPath()
{
return xmlInputRootPath;
}
/**
* Get access to rule loader information (paths, context variables, etc).
*/
public RuleLoaderContext getRuleLoaderContext()
{
return ruleLoaderContext;
}
}