package org.jboss.windup.rules.apps.xml.condition.validators;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.rules.apps.xml.condition.XmlFile;
import org.jboss.windup.rules.apps.xml.condition.XmlFileEvaluateXPathFunction;
import org.jboss.windup.rules.apps.xml.condition.XmlFileFunctionResolver;
import org.jboss.windup.rules.apps.xml.condition.XmlFileMatchesXPathFunction;
import org.jboss.windup.rules.apps.xml.condition.XmlFileParameterMatchCache;
import org.jboss.windup.rules.apps.xml.condition.XmlFileStartFrameXPathFunction;
import org.jboss.windup.rules.apps.xml.condition.XmlFileXPathTransformer;
import org.jboss.windup.rules.apps.xml.model.NamespaceMetaModel;
import org.jboss.windup.rules.apps.xml.model.XmlFileModel;
import org.jboss.windup.rules.apps.xml.model.XmlTypeReferenceModel;
import org.jboss.windup.rules.apps.xml.service.XmlFileService;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.xml.LocationAwareContentHandler;
import org.jboss.windup.util.xml.NamespaceMapContext;
import org.jboss.windup.util.xml.XmlUtil;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.ocpsoft.rewrite.param.DefaultParameterStore;
import org.ocpsoft.rewrite.param.Parameter;
import org.ocpsoft.rewrite.param.ParameterStore;
import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathFunction;
import javax.xml.xpath.XPathFunctionException;
import javax.xml.xpath.XPathFunctionResolver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* This is a part of XmlFile execution. Validator is checking that xpath/xpathResult attributes matches the queried one.
*/
public class XmlFileXpathValidator implements XmlFileValidator
{
protected static final String WINDUP_NS_PREFIX = "windup";
protected static final String WINDUP_NS_URI = "http://windup.jboss.org/windupv2functions";
private static XPathFactory factory = XPathFactory.newInstance();
private XPathExpression compiledXPath;
private final XPath xpathEngine;
private final XmlFileFunctionResolver xmlFileFunctionResolver;
private XmlFile.XmlFileEvaluationStrategy evaluationStrategy;
private String xpathResultMatch;
private List<WindupVertexFrame> results = new ArrayList<>();
private Map<String, String> namespaces = new HashMap<>();
private String xpathString;
private static final Logger LOG = Logging.get(XmlFileXpathValidator.class);
private RegexParameterizedPatternParser xpathPattern;
public XmlFileXpathValidator()
{
this.namespaces.put(WINDUP_NS_PREFIX, WINDUP_NS_URI);
this.xpathEngine = factory.newXPath();
final XPathFunctionResolver originalResolver = this.xpathEngine.getXPathFunctionResolver();
xmlFileFunctionResolver = new XmlFileFunctionResolver(originalResolver);
this.xpathEngine.setXPathFunctionResolver(xmlFileFunctionResolver);
}
public void setXpathString(String xpath)
{
this.xpathString = xpath;
this.compiledXPath = null;
if (xpath != null)
{
this.xpathPattern = new RegexParameterizedPatternParser(this.xpathString);
}
}
@Override
public boolean isValid(GraphRewrite event, EvaluationContext context, XmlFileModel model)
{
if(xpathString == null) {
return true;
}
String xpathStringWithParameterFunctions = XmlFileXPathTransformer.transformXPath(this.xpathString);
LOG.fine("XmlFile compiled: " + this.xpathString + " to " + xpathStringWithParameterFunctions);
XmlFileService xmlFileService = new XmlFileService(event.getGraphContext());
Document document = xmlFileService.loadDocumentQuiet(event, context, model);
if (document != null)
{
final ParameterStore store = DefaultParameterStore.getInstance(context);
final XmlFileParameterMatchCache paramMatchCache = new XmlFileParameterMatchCache();
this.xmlFileFunctionResolver.registerFunction(WINDUP_NS_URI, "startFrame",
new XmlFileStartFrameXPathFunction(paramMatchCache));
this.xmlFileFunctionResolver
.registerFunction(WINDUP_NS_URI, "evaluate", new XmlFileEvaluateXPathFunction(evaluationStrategy));
this.xmlFileFunctionResolver.registerFunction(WINDUP_NS_URI, "matches", new XmlFileMatchesXPathFunction(context, store,
paramMatchCache, event));
this.xmlFileFunctionResolver.registerFunction(WINDUP_NS_URI, "persist", new XmlFilePersistXPathFunction(event, context, model,
evaluationStrategy, store, paramMatchCache, results));
if (compiledXPath == null)
{
NamespaceMapContext nsContext = new NamespaceMapContext(namespaces);
this.xpathEngine.setNamespaceContext(nsContext);
try
{
this.compiledXPath = xpathEngine.compile(xpathStringWithParameterFunctions);
}
catch (Exception e)
{
String message = e.getMessage();
// brutal hack to try to get a reasonable error message (ugly, but it seems to work)
if (message == null && e.getCause() != null && e.getCause().getMessage() != null)
{
message = e.getCause().getMessage();
}
LOG.severe("Condition: " + this + " failed to run, as the following xpath was uncompilable: " + xpathString
+ " (compiled contents: " + xpathStringWithParameterFunctions + ") due to: "
+ message);
return false;
}
}
/**
* This actually does the work.
*/
XmlUtil.xpathNodeList(document, compiledXPath);
evaluationStrategy.modelSubmissionRejected();
}
return !results.isEmpty();
}
public List<WindupVertexFrame> getAndClearResultLocations()
{
List<WindupVertexFrame> output = results;
results = new ArrayList<WindupVertexFrame>();
return output;
}
public void setXpathResult(String xpathResult)
{
this.xpathResultMatch = xpathResult;
}
public String getXpathString()
{
return xpathString;
}
public XPathExpression getXpathExpression()
{
return compiledXPath;
}
public void setParameterStore(ParameterStore store)
{
if (this.xpathPattern != null)
{
this.xpathPattern.setParameterStore(store);
}
}
public Collection<? extends String> getRequiredParamaterNames()
{
if (xpathPattern != null)
{
return xpathPattern.getRequiredParameterNames();
}
else
{
return Collections.emptyList();
}
}
public void addNamespace(String prefix, String url)
{
this.namespaces.put(prefix, url);
}
public void setEvaluationStrategy(XmlFile.XmlFileEvaluationStrategy evaluationStrategy)
{
this.evaluationStrategy = evaluationStrategy;
}
final class XmlFilePersistXPathFunction implements XPathFunction
{
private final GraphRewrite event;
private final EvaluationContext context;
private final XmlFileModel xml;
private final XmlFile.XmlFileEvaluationStrategy evaluationStrategy;
private final ParameterStore store;
private final XmlFileParameterMatchCache paramMatchCache;
private final List<WindupVertexFrame> resultLocations;
XmlFilePersistXPathFunction(GraphRewrite event, EvaluationContext context, XmlFileModel xml,
XmlFile.XmlFileEvaluationStrategy evaluationStrategy,
ParameterStore store,
XmlFileParameterMatchCache paramMatchCache, List<WindupVertexFrame> resultLocations)
{
this.event = event;
this.context = context;
this.xml = xml;
this.evaluationStrategy = evaluationStrategy;
this.store = store;
this.paramMatchCache = paramMatchCache;
this.resultLocations = resultLocations;
}
@Override
public Object evaluate(@SuppressWarnings("rawtypes") List args) throws XPathFunctionException
{
int frameIdx = ((Double) args.get(0)).intValue();
NodeList arg1 = (NodeList) args.get(1);
String nodeText = XmlUtil.nodeListToString(arg1);
LOG.fine("persist(" + frameIdx + ", " + nodeText + ")");
for (int i = 0; i < arg1.getLength(); i++)
{
Node node = arg1.item(i);
if (xpathResultMatch != null)
{
if (!node.toString().matches(xpathResultMatch))
{
continue;
}
}
// Everything passed for this Node. Start creating XmlTypeReferenceModel for it.
int lineNumber = (int) node.getUserData(
LocationAwareContentHandler.LINE_NUMBER_KEY_NAME);
int columnNumber = (int) node.getUserData(
LocationAwareContentHandler.COLUMN_NUMBER_KEY_NAME);
GraphService<XmlTypeReferenceModel> fileLocationService = new GraphService<>(
event.getGraphContext(),
XmlTypeReferenceModel.class);
XmlTypeReferenceModel fileLocation = fileLocationService.create();
String sourceSnippit = XmlUtil.nodeToString(node);
fileLocation.setSourceSnippit(sourceSnippit);
fileLocation.setLineNumber(lineNumber);
fileLocation.setColumnNumber(columnNumber);
fileLocation.setLength(node.toString().length());
fileLocation.setFile(xml);
fileLocation.setXpath(xpathString);
GraphService<NamespaceMetaModel> metaModelService = new GraphService<>(
event.getGraphContext(),
NamespaceMetaModel.class);
for (Map.Entry<String, String> namespace : namespaces.entrySet())
{
NamespaceMetaModel metaModel = metaModelService.create();
metaModel.setSchemaLocation(namespace.getKey());
metaModel.setSchemaLocation(namespace.getValue());
metaModel.addXmlResource(xml);
fileLocation.addNamespace(metaModel);
}
resultLocations.add(fileLocation);
evaluationStrategy.modelSubmissionRejected();
evaluationStrategy.modelMatched();
for (Map.Entry<String, String> entry : paramMatchCache.getVariables().entrySet())
{
Parameter<?> param = store.get(entry.getKey());
String value = entry.getValue();
if (!evaluationStrategy.submitValue(param, value))
{
evaluationStrategy.modelSubmissionRejected();
return false;
}
}
evaluationStrategy.modelSubmitted(fileLocation);
evaluationStrategy.modelMatched();
}
return true;
}
}
}