/*
* Copyright 2014 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Eclipse Public License version 1.0, available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jboss.windup.config.parser;
import java.io.IOException;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.inject.Inject;
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.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.RuleProvider;
import org.jboss.windup.config.builder.RuleProviderBuilder;
import org.jboss.windup.config.loader.RuleLoaderContext;
import org.jboss.windup.config.loader.RuleProviderLoader;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.exception.WindupException;
import org.jboss.windup.util.furnace.FileExtensionFilter;
import org.jboss.windup.util.furnace.FurnaceClasspathScanner;
import org.w3c.dom.Document;
/**
* This {@link RuleProviderLoader} searches for and loads {@link AbstractRuleProvider}s from XML files that within all
* addons, with filenames that end in ".windup.xml".
*
* @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
*
*/
public class XMLRuleProviderLoader implements RuleProviderLoader
{
private static final Logger LOG = Logging.get(XMLRuleProviderLoader.class);
private static final String XML_RULES_EXTENSION = "windup.xml";
@Inject
private Furnace furnace;
@Inject
private FurnaceClasspathScanner scanner;
@Override
public boolean isFileBased()
{
return true;
}
@Override
public List<RuleProvider> getProviders(RuleLoaderContext ruleLoaderContext)
{
List<RuleProvider> providers = new ArrayList<>();
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
dbFactory.setNamespaceAware(true);
DocumentBuilder dBuilder;
try
{
dBuilder = dbFactory.newDocumentBuilder();
}
catch (Exception e)
{
throw new WindupException("Failed to build xml parser due to: " + e.getMessage(), e);
}
for (Map.Entry<Addon, List<URL>> addonFiles : getAddonWindupXmlFiles().entrySet())
{
Addon addon = addonFiles.getKey();
List<URL> urls = addonFiles.getValue();
for (URL resource : urls)
{
try
{
Document doc = dBuilder.parse(resource.toURI().toString());
ParserContext parser = new ParserContext(furnace, ruleLoaderContext);
parser.setAddonContainingInputXML(addon);
parser.processElement(doc.getDocumentElement());
List<AbstractRuleProvider> parsedProviders = parser.getRuleProviders();
setOrigin(parsedProviders, resource);
providers.addAll(parsedProviders);
}
catch (Exception e)
{
throw new WindupException("Failed to parse XML configuration at: " + resource.toString()
+ " due to: " + e.getMessage(), e);
}
}
}
for (Path userRulesPath : ruleLoaderContext.getRulePaths())
{
// Log the files found
final Collection<URL> userXmlRulesetFiles = getWindupUserDirectoryXmlFiles(userRulesPath);
StringBuilder sb = new StringBuilder("\nFound " + userXmlRulesetFiles.size() + " user XML rules in: " + userRulesPath);
for (URL resource : userXmlRulesetFiles)
sb.append("\n\t" + resource.toString());
LOG.info(sb.toString());
// Parse each file
for (URL resource : userXmlRulesetFiles)
{
try
{
Document doc = dBuilder.parse(resource.toURI().toString());
ParserContext parser = new ParserContext(furnace, ruleLoaderContext);
parser.setXmlInputPath(Paths.get(resource.toURI()));
parser.setXmlInputRootPath(userRulesPath);
parser.processElement(doc.getDocumentElement());
List<AbstractRuleProvider> parsedProviders = parser.getRuleProviders();
setOrigin(parsedProviders, resource);
providers.addAll(parsedProviders);
}
catch (Exception e)
{
throw new WindupException("Failed to parse XML configuration at: " + resource.toString() + " due to: " + e.getMessage(), e);
}
}
}
return providers;
}
private void setOrigin(List<AbstractRuleProvider> providers, URL resource)
{
for (AbstractRuleProvider provider : providers)
{
if (provider instanceof RuleProviderBuilder)
{
((RuleProviderBuilder) provider).setOrigin(resource.toExternalForm());
}
}
}
private Map<Addon, List<URL>> getAddonWindupXmlFiles()
{
return scanner.scanForAddonMap(new FileExtensionFilter(XML_RULES_EXTENSION));
}
private Collection<URL> getWindupUserDirectoryXmlFiles(Path userRulesPath)
{
// no user dir, so just return the ones that we found in the classpath
if (userRulesPath == null)
return Collections.emptyList();
try
{
// Deal with the case of a single file here
if (Files.isRegularFile(userRulesPath) && pathMatchesNamePattern(userRulesPath))
return Collections.singletonList(userRulesPath.toUri().toURL());
if (!Files.isDirectory(userRulesPath))
{
LOG.warning("Not scanning: " + userRulesPath.normalize().toString() + " for rules as the directory could not be read!");
return Collections.emptyList();
}
// create the results as a copy (as we will be adding user xml files to them)
final List<URL> results = new ArrayList<>();
Files.walkFileTree(userRulesPath, new SimpleFileVisitor<Path>()
{
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException
{
if (pathMatchesNamePattern(file))
{
results.add(file.toUri().toURL());
}
return FileVisitResult.CONTINUE;
}
});
return results;
}
catch (IOException e)
{
throw new WindupException("Failed to search userdir: \"" + userRulesPath + "\" for XML rules due to: "
+ e.getMessage(), e);
}
}
private boolean pathMatchesNamePattern(Path file) {
return file.getFileName().toString().toLowerCase().endsWith("." + XML_RULES_EXTENSION);
}
}