package org.jboss.windup.rules.apps.xml.operation.xslt;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.util.ClassLoaders;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.operation.iteration.AbstractIterationOperation;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.FileReferenceModel;
import org.jboss.windup.graph.model.LinkModel;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.reporting.model.ClassificationModel;
import org.jboss.windup.reporting.service.ClassificationService;
import org.jboss.windup.rules.apps.xml.model.XmlFileModel;
import org.jboss.windup.rules.apps.xml.model.XsltTransformationModel;
import org.jboss.windup.rules.apps.xml.service.XsltTransformationService;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.exception.WindupException;
import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.context.EvaluationContext;
/**
* Graph operation doing the xslt transformation using the .xslt source on the target xml object
*
* @author <a href="mailto:mbriskar@gmail.com">Matej Briskar</a>
*/
public class XSLTTransformation extends AbstractIterationOperation<XmlFileModel> implements XSLTTransformationDescription,
XSLTTransformationExtension, XSLTTransformationLocation, XSLTTransformationFileSystem, XSLTTransformationOf
{
private static final Logger LOG = Logging.get(XSLTTransformation.class);
private ClassLoader contextClassLoader;
private String description = "";
private String template;
private String extension;
private int effort = 0;
private Transformer xsltTransformer;
private Map<String, String> xsltParameters = new HashMap<>();
private XSLTTransformation(String variable)
{
super(variable);
}
private XSLTTransformation()
{
super();
}
/**
* Set the payload to the fileModel of the given instance even though the variable is not directly of it's type. This is mainly to simplify the
* creation of the rule, when the FileModel itself is not being iterated but just a model referencing it.
*
*/
@Override
public void perform(GraphRewrite event, EvaluationContext context)
{
checkVariableName(event, context);
WindupVertexFrame payload = resolveVariable(event, getVariableName());
if (payload instanceof FileReferenceModel)
{
FileModel file = ((FileReferenceModel) payload).getFile();
perform(event, context, (XmlFileModel) file);
}
else
{
super.perform(event, context);
}
}
public void addXsltParameter(String key, String value)
{
xsltParameters.put(key, value);
}
/**
* Create a new transformation for the given ref.
*/
public static XSLTTransformationOf of(String variable)
{
return new XSLTTransformation(variable);
}
@Override
public XSLTTransformationDescription withDescription(String description)
{
this.description = description;
return this;
}
@Override
public XSLTTransformationLocation usingTemplate(String template)
{
this.template = template;
return this;
}
@Override
public XSLTTransformationLocation usingTemplate(String location, ClassLoader loader)
{
this.template = location;
this.contextClassLoader = loader;
return this;
}
@Override
public XSLTTransformationExtension withExtension(String extension)
{
this.extension = extension;
return this;
}
/**
* Create a new {@link XSLTTransformation} using the given location of the source XSLT file within the current
* {@link Thread#getContextClassLoader()}.
*/
public static XSLTTransformationLocation using(String location)
{
return using(location, Thread.currentThread().getContextClassLoader());
}
/**
* Create a new {@link XSLTTransformation} using the given location of the source XSLT file path on the file-system.
*/
public static XSLTTransformationFileSystem usingFilesystem(String location)
{
XSLTTransformation tansformation = new XSLTTransformation();
tansformation.template = location;
return tansformation;
}
/**
* Create a new {@link XSLTTransformation} using the given location of the source XSLT file within the given {@link ClassLoader}.
*/
public static XSLTTransformationLocation using(String location, ClassLoader classLoader)
{
XSLTTransformation tansformation = new XSLTTransformation();
/*
* ClassLoader instance needed to see the file passed in the location
*/
tansformation.contextClassLoader = classLoader;
tansformation.template = location;
return tansformation;
}
private InputStream openInputStream(String pathString) throws IOException
{
if (this.contextClassLoader != null)
{
LOG.fine("Loading Resource " + pathString + " with classloader: " + pathString);
return contextClassLoader.getResourceAsStream(pathString);
}
else
{
Path path = Paths.get(pathString);
if (!Files.isRegularFile(path) && !pathString.equals(this.template))
{
// probably a relative file... try to infer it from the original template path
path = Paths.get(this.template).getParent().resolve(path);
}
LOG.fine("Loading File " + path + " with from " + path.toAbsolutePath().normalize().toString());
return new FileInputStream(path.toFile());
}
}
private void setup()
{
try (InputStream resourceAsStream = openInputStream(this.template))
{
final Source xsltSource = new StreamSource(resourceAsStream);
final TransformerFactory tf = TransformerFactory.newInstance();
tf.setURIResolver(new URIResolver()
{
@Override
public Source resolve(String href, String base) throws TransformerException
{
/*
* Fetch local only, for speed reasons.
*/
if (StringUtils.contains(href, "http://"))
{
LOG.warning("Trying to fetch remote URL for XSLT. This is not possible; for speed reasons: "
+ href + ": " + base);
return null;
}
try
{
InputStream inputStream = openInputStream(href);
return new StreamSource(inputStream);
} catch (IOException e)
{
throw new WindupException("Failed to load template: " + href + " due to: " + e.getMessage(), e);
}
}
});
ClassLoaders.executeIn(TransformerFactory.class.getClassLoader(), new Callable<Object>()
{
@Override
public Object call() throws Exception
{
xsltTransformer = tf.newTransformer(xsltSource);
return null;
}
});
if (xsltParameters != null)
{
for (String key : xsltParameters.keySet())
{
LOG.fine("Setting property: " + key + " -> " + xsltParameters.get(key));
xsltTransformer.setParameter(key, xsltParameters.get(key));
}
}
LOG.fine("Created XSLT successfully: " + template);
}
catch (TransformerConfigurationException e)
{
throw new IllegalStateException("Problem working with xsl file located at " + template
+ ". Please check if the file really exists.", e);
}
catch (Exception e)
{
throw new IllegalStateException("Not able to initialize the XSLT transformer.", e);
}
}
@Override
public void perform(GraphRewrite event, EvaluationContext context, XmlFileModel payload)
{
setup();
GraphContext graphContext = event.getGraphContext();
GraphService<XsltTransformationModel> transformationService = new GraphService<>(
graphContext, XsltTransformationModel.class);
String fileName = payload.getFileName();
fileName = StringUtils.replace(fileName, ".", "-");
fileName = fileName + extension;
XsltTransformationService xsltTransformationService = new XsltTransformationService(graphContext);
Path outputPath = xsltTransformationService.getTransformedXSLTPath();
Path resultPath = outputPath.resolve(fileName);
Source xmlSource = new DOMSource(payload.asDocument());
Result xmlResult = new StreamResult(resultPath.toFile());
try
{
xsltTransformer.transform(xmlSource, xmlResult);
XsltTransformationModel transformation = transformationService.create();
transformation.setDescription(description);
transformation.setEffort(effort);
transformation.setExtension(extension);
transformation.setSourceLocation(template);
transformation.setSourceFile(payload);
transformation.setResult(fileName);
ClassificationService classificationService = new ClassificationService(graphContext);
ClassificationModel classificationModel = classificationService.create();
classificationModel.setClassification("Transformed to: " + description);
classificationModel.setEffort(effort);
classificationModel.addFileModel(payload);
classificationModel.setRuleID(((Rule) context.get(Rule.class)).getId());
GraphService<LinkModel> linkService = new GraphService<>(graphContext, LinkModel.class);
LinkModel linkModel = linkService.create();
linkModel.setDescription(description);
linkModel.setLink(XsltTransformationService.TRANSFORMEDXML_DIR_NAME + "/" + fileName);
payload.addLinkToTransformedFile(linkModel);
// classificationModel.addLink(linkModel);
}
catch (TransformerException e)
{
LOG.log(Level.SEVERE, "Exception transforming XML.", e);
}
}
/**
* Set the parameters associated with this {@link XSLTTransformation};
*/
public XSLTTransformationEffort withParameters(Map<String, String> parameters)
{
this.xsltParameters.putAll(parameters);
return this;
}
/**
* Set the estimated effort associated with this {@link XSLTTransformation};
*/
public XSLTTransformation withEffort(int effort)
{
this.effort = effort;
return this;
}
public void setContextClassLoader(ClassLoader classLoader)
{
this.contextClassLoader = classLoader;
}
public String getDescription()
{
return description;
}
public String getTemplate()
{
return template;
}
public String getExtension()
{
return extension;
}
public Map<String, String> getXsltParameters()
{
return xsltParameters;
}
}