package org.jboss.windup.rules.apps.xml.xml; import java.io.IOException; import java.util.Collections; import javax.xml.XMLConstants; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.lang3.StringUtils; import org.jboss.windup.config.AbstractRuleProvider; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.loader.RuleLoaderContext; import org.jboss.windup.config.metadata.MetadataBuilder; import org.jboss.windup.config.operation.iteration.AbstractIterationOperation; import org.jboss.windup.config.phase.MigrationRulesPhase; import org.jboss.windup.config.query.Query; import org.jboss.windup.graph.service.WindupConfigurationService; import org.jboss.windup.reporting.model.ClassificationModel; import org.jboss.windup.reporting.model.InlineHintModel; import org.jboss.windup.reporting.service.ClassificationService; import org.jboss.windup.reporting.service.InlineHintService; import org.jboss.windup.reporting.service.TagSetService; import org.jboss.windup.reporting.category.IssueCategoryRegistry; import org.jboss.windup.rules.apps.xml.model.XmlFileModel; import org.jboss.windup.rules.files.condition.ProcessingIsOnlineGraphCondition; import org.ocpsoft.rewrite.config.Configuration; import org.ocpsoft.rewrite.config.ConfigurationBuilder; import org.ocpsoft.rewrite.config.Rule; import org.ocpsoft.rewrite.context.EvaluationContext; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * A rule provider validating all of the xml files and registering the classification in case the xml file is not valid. * * @author <a href="mailto:mbriskar@gmail.com">Matej Briskar</a> * @author <a href="mailto:hotmana76@gmail.com">Marek Novotny</a> */ public class ValidateXmlFilesRuleProvider extends AbstractRuleProvider { public static final String NOT_VALID_XML_TAG = "Not valid XML"; private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; public ValidateXmlFilesRuleProvider() { super(MetadataBuilder .forProvider(ValidateXmlFilesRuleProvider.class) .setPhase(MigrationRulesPhase.class) .setHaltOnException(true)); } @Override public Configuration getConfiguration(RuleLoaderContext ruleLoaderContext) { return ConfigurationBuilder.begin() .addRule() .when(new ProcessingIsOnlineGraphCondition().and(Query.fromType(XmlFileModel.class))) .perform(new ValidateAndRegisterClassification()); } private class ValidateAndRegisterClassification extends AbstractIterationOperation<XmlFileModel> { @Override public void perform(GraphRewrite event, EvaluationContext context, XmlFileModel sourceFile) { boolean onlineMode = WindupConfigurationService.getConfigurationModel(event.getGraphContext()).isOnlineMode(); boolean validationFailed = false; try { // ignore if we have already encountered a parse error if (StringUtils.isNotBlank(sourceFile.getParseError())) return; SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); factory.setValidating(true); factory.setXIncludeAware(false); final SAXParser parser = factory.newSAXParser(); parser.setProperty(JAXP_SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI); ValidateXmlHandler handler = new ValidateXmlHandler(onlineMode); parser.parse(sourceFile.asFile(), handler); if (!handler.getXsdURLs().isEmpty()) { for (SAXParseException exception : handler.getParseExceptions()) { if (isExceptionRelatedToSource(sourceFile, exception)) { validationFailed = true; createSAXParseHint(event, context, sourceFile, exception); } } } } catch (SAXParseException e) { if (isExceptionRelatedToSource(sourceFile, e)) { validationFailed = true; createSAXParseHint(event, context, sourceFile, e); } } catch (ParserConfigurationException | IOException | SAXException e) { validationFailed = true; } finally { if (validationFailed) createParseFailureClassification(event, context, sourceFile); } } private boolean isExceptionRelatedToSource(XmlFileModel sourceFile, SAXParseException e) { if (e.getSystemId() == null) return true; // Just assume that it is related, in the absence of other information if (e.getSystemId().startsWith("http://") || e.getSystemId().startsWith("https://") || e.getSystemId().startsWith("ftp://")) return false; return e.getSystemId().endsWith(sourceFile.getFileName()); } private void createSAXParseHint(GraphRewrite event, EvaluationContext context, XmlFileModel sourceFile, SAXParseException e) { int lineNumber = e.getLineNumber(); int column = e.getColumnNumber(); InlineHintService service = new InlineHintService(event.getGraphContext()); InlineHintModel hintModel = service.create(); hintModel.setRuleID(((Rule) context.get(Rule.class)).getId()); hintModel.setLineNumber(lineNumber); hintModel.setColumnNumber(column); // FIXME - Fake value as we don't get an actual length of the error from the parser hintModel.setLength(1); hintModel.setFile(sourceFile); hintModel.setEffort(1); IssueCategoryRegistry issueCategoryRegistry = IssueCategoryRegistry.instance(event.getRewriteContext()); hintModel.setIssueCategory(issueCategoryRegistry.loadFromGraph(event.getGraphContext(), IssueCategoryRegistry.POTENTIAL)); if (e.getCause() instanceof InvalidXSDURLException) { String xsdUrl = ((InvalidXSDURLException) e.getCause()).getUrl(); hintModel.setTitle(XmlFileModel.XSD_URL_NOT_VALID); hintModel.setHint(xsdUrl + " is not a valid url."); } else { hintModel.setTitle(XmlFileModel.NOT_VALID_XML); String message = "XSD Validation failed due to:\n\n"; message += "\t" + e.getMessage(); message += "\n\n"; hintModel.setHint(message); } sourceFile.setGenerateSourceReport(true); } } private void createParseFailureClassification(GraphRewrite event, EvaluationContext context, XmlFileModel sourceFile) { sourceFile.setGenerateSourceReport(true); ClassificationService classificationService = new ClassificationService(event.getGraphContext()); ClassificationModel model = classificationService.attachClassification(event, context, sourceFile, XmlFileModel.NOT_VALID_XML, null); model.setEffort(0); // do not rely on default 0 value and set it that transparently IssueCategoryRegistry issueCategoryRegistry = IssueCategoryRegistry.instance(event.getRewriteContext()); model.setIssueCategory(issueCategoryRegistry.loadFromGraph(event.getGraphContext(), IssueCategoryRegistry.POTENTIAL)); TagSetService tagSetService = new TagSetService(event.getGraphContext()); model.setTagModel(tagSetService.getOrCreate(event, Collections.singleton(NOT_VALID_XML_TAG))); } }