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