package org.jboss.windup.rules.apps.javaee.rules;
import static org.joox.JOOX.$;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.phase.InitialAnalysisPhase;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.config.ruleprovider.IteratingRuleProvider;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.reporting.model.TechnologyTagLevel;
import org.jboss.windup.reporting.model.TechnologyTagModel;
import org.jboss.windup.reporting.service.ClassificationService;
import org.jboss.windup.reporting.service.TechnologyTagService;
import org.jboss.windup.rules.apps.javaee.model.EnvironmentReferenceModel;
import org.jboss.windup.rules.apps.javaee.model.EnvironmentReferenceTagType;
import org.jboss.windup.rules.apps.javaee.model.WebXmlModel;
import org.jboss.windup.rules.apps.javaee.service.EnvironmentReferenceService;
import org.jboss.windup.rules.apps.javaee.service.WebXmlService;
import org.jboss.windup.rules.apps.xml.model.DoctypeMetaModel;
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.service.XmlFileService;
import org.jboss.windup.util.xml.DoctypeUtils;
import org.jboss.windup.util.xml.NamespaceUtils;
import org.ocpsoft.rewrite.config.ConditionBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Discovers web.xml files, parses them, and places relevant metadata into the graph.
*
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
*/
@RuleMetadata(phase = InitialAnalysisPhase.class, perform = "Discover web.xml files")
public class DiscoverWebXmlRuleProvider extends IteratingRuleProvider<XmlFileModel>
{
private static final Logger LOG = Logger.getLogger(DiscoverWebXmlRuleProvider.class.getSimpleName());
private static final String TECH_TAG = "Web XML";
private static final TechnologyTagLevel TECH_TAG_LEVEL = TechnologyTagLevel.INFORMATIONAL;
private static final String REGEX_DTD = "(?i).*web.application.*";
@Override
public ConditionBuilder when()
{
return Query.fromType(XmlFileModel.class).withProperty(XmlFileModel.ROOT_TAG_NAME, "web-app");
}
public void perform(GraphRewrite event, EvaluationContext context, XmlFileModel payload)
{
XmlFileService xmlFileService = new XmlFileService(event.getGraphContext());
Document doc = xmlFileService.loadDocumentQuiet(event, context, payload);
if (doc != null && isWebXml(payload, doc))
{
addWebXmlMetadata(event, context, event.getGraphContext(), payload, doc);
}
}
private boolean isWebXml(XmlFileModel xml, Document doc)
{
// check it's doctype against the known doctype.
return !(xml.getDoctype() != null && !processDoctypeMatches(xml.getDoctype()));
}
private String getVersion(XmlFileModel xml, Document doc)
{
String version = null;
// check it's doctype against the known doctype.
if (xml.getDoctype() != null)
{
// if it isn't matching doctype, then continue.
if (processDoctypeMatches(xml.getDoctype()))
{
version = processDoctypeVersion(xml.getDoctype());
}
}
else
{
// if there is no doctype, check the XSD..
version = $(doc).attr("version");
// if the version attribute isn't found, then grab it from the XSD name if we can.
if (StringUtils.isBlank(version))
{
// get the first tag's namespace...
String namespace = $(doc).find("web-app").namespaceURI();
if (StringUtils.isBlank(namespace))
{
namespace = doc.getFirstChild().getNamespaceURI();
}
// find that namespace, and try and pull the version from the XSD name...
for (NamespaceMetaModel ns : xml.getNamespaces())
{
if (StringUtils.equals(ns.getURI(), namespace))
{
version = NamespaceUtils.extractVersion(ns.getSchemaLocation());
break;
}
}
}
}
return version;
}
private void addWebXmlMetadata(GraphRewrite event, EvaluationContext evaluationContext, GraphContext context, XmlFileModel xml, Document doc)
{
ClassificationService classificationService = new ClassificationService(context);
TechnologyTagService technologyTagService = new TechnologyTagService(context);
classificationService.attachClassification(event, evaluationContext, xml, "Web XML", " Web Application Deployment Descriptors");
TechnologyTagModel technologyTag = technologyTagService.addTagToFileModel(xml, TECH_TAG, TECH_TAG_LEVEL);
WebXmlService webXmlService = new WebXmlService(context);
String webXmlVersion = getVersion(xml, doc);
// check the root XML node.
WebXmlModel webXml = webXmlService.addTypeToModel(xml);
// change "_" in the version to "."
if (StringUtils.isNotBlank(webXmlVersion))
{
webXmlVersion = StringUtils.replace(webXmlVersion, "_", ".");
webXml.setSpecificationVersion(webXmlVersion);
// set the tag version
technologyTag.setVersion(webXmlVersion);
}
String displayName = $(doc).child("display-name").text();
displayName = StringUtils.trimToNull(displayName);
if (StringUtils.isNotBlank(displayName))
{
webXml.setDisplayName(displayName);
}
// extract references.
List<EnvironmentReferenceModel> refs = processEnvironmentReference(context, doc.getDocumentElement());
for (EnvironmentReferenceModel ref : refs)
{
webXml.addEnvironmentReference(ref);
}
}
private boolean processDoctypeMatches(DoctypeMetaModel doctypeMetaModel)
{
if (StringUtils.isNotBlank(doctypeMetaModel.getPublicId()))
{
if (Pattern.matches(REGEX_DTD, doctypeMetaModel.getPublicId()))
{
return true;
}
}
if (StringUtils.isNotBlank(doctypeMetaModel.getSystemId()))
{
if (Pattern.matches(REGEX_DTD, doctypeMetaModel.getSystemId()))
{
return true;
}
}
return false;
}
private String processDoctypeVersion(DoctypeMetaModel entry)
{
String publicId = entry.getPublicId();
String systemId = entry.getSystemId();
// extract the version information from the public / system ID.
String versionInformation = DoctypeUtils.extractVersion(publicId, systemId);
return versionInformation;
}
private List<EnvironmentReferenceModel> processEnvironmentReference(GraphContext context, Element element)
{
EnvironmentReferenceService environmentReferenceService = new EnvironmentReferenceService(context);
List<EnvironmentReferenceModel> resources = new ArrayList<>();
// find JMS references...
for (Element resourceRef : $(element).find("resource-ref").get())
{
processElement(environmentReferenceService, resources, resourceRef, "res-type", "res-ref-name", EnvironmentReferenceTagType.RESOURCE_REF);
}
for (Element resourceRef : $(element).find("ejb-ref").get())
{
processElement(environmentReferenceService, resources, resourceRef, "ejb-ref-type", "ejb-ref-name", EnvironmentReferenceTagType.EJB_REF);
}
for (Element resourceRef : $(element).find("ejb-local-ref").get())
{
processElement(environmentReferenceService, resources, resourceRef, "ejb-ref-type", "ejb-ref-name", EnvironmentReferenceTagType.EJB_LOCAL_REF);
}
for (Element resourceRef : $(element).find("message-destination-ref").get())
{
processElement(environmentReferenceService, resources, resourceRef, "message-destination-type", "message-destination-ref-name", EnvironmentReferenceTagType.MSG_DESTINATION_REF);
}
return resources;
}
private void processElement(EnvironmentReferenceService environmentReferenceService, List<EnvironmentReferenceModel> resources, Element element, String typeLocation, String nameLocation, EnvironmentReferenceTagType refType)
{
String id = $(element).attr("id");
String type = $(element).child(typeLocation).text();
String name = $(element).child(nameLocation).text();
type = StringUtils.trim(type);
name = StringUtils.trim(name);
EnvironmentReferenceModel ref = environmentReferenceService.findEnvironmentReference(name, refType);
if (ref == null)
{
ref = environmentReferenceService.create();
ref.setName(name);
ref.setReferenceType(type);
ref.setReferenceTagType(refType);
LOG.info("Added: "+ref);
}
else {
if(ref.getReferenceTagType() != null && (ref.getReferenceTagType() != refType)) {
LOG.warning("Expected type: "+EnvironmentReferenceTagType.RESOURCE_REF +" but actually: "+ref.getReferenceType());
}
}
ref.setReferenceId(id);
resources.add(ref);
}
}