@Override public void validateMessagePayload( Message receivedMessage, Message controlMessage, XmlMessageValidationContext validationContext, TestContext context) throws ValidationException { log.info("Start XML message validation"); try { if (validationContext.isSchemaValidationEnabled()) { validateXMLSchema(receivedMessage, validationContext); validateDTD(validationContext.getDTDResource(), receivedMessage); } validateNamespaces(validationContext.getControlNamespaces(), receivedMessage); validateMessageContent(receivedMessage, controlMessage, validationContext, context); if (controlMessage != null) { Assert.isTrue( controlMessage.getHeaderData().size() <= receivedMessage.getHeaderData().size(), "Failed to validate header data XML fragments - found " + receivedMessage.getHeaderData().size() + " header fragments, expected " + controlMessage.getHeaderData().size()); for (int i = 0; i < controlMessage.getHeaderData().size(); i++) { validateXmlHeaderFragment( receivedMessage.getHeaderData().get(i), controlMessage.getHeaderData().get(i), validationContext, context); } } log.info("XML message validation successful: All values OK"); } catch (ClassCastException e) { throw new CitrusRuntimeException(e); } catch (DOMException e) { throw new CitrusRuntimeException(e); } catch (LSException e) { throw new CitrusRuntimeException(e); } catch (IllegalArgumentException e) { log.error( "Failed to validate:\n" + XMLUtils.prettyPrint(receivedMessage.getPayload(String.class))); throw new ValidationException("Validation failed:", e); } catch (ValidationException ex) { log.error( "Failed to validate:\n" + XMLUtils.prettyPrint(receivedMessage.getPayload(String.class))); throw ex; } }
/** * Validates XML header fragment data. * * @param receivedHeaderData * @param controlHeaderData * @param validationContext * @param context */ private void validateXmlHeaderFragment( String receivedHeaderData, String controlHeaderData, XmlMessageValidationContext validationContext, TestContext context) { log.info("Start XML header data validation ..."); Document received = XMLUtils.parseMessagePayload(receivedHeaderData); Document source = XMLUtils.parseMessagePayload(controlHeaderData); XMLUtils.stripWhitespaceNodes(received); XMLUtils.stripWhitespaceNodes(source); if (log.isDebugEnabled()) { log.debug("Received header data:\n" + XMLUtils.serialize(received)); log.debug("Control header data:\n" + XMLUtils.serialize(source)); } validateXmlTree( received, source, validationContext, namespaceContextBuilder.buildContext( new DefaultMessage(receivedHeaderData), validationContext.getNamespaces()), context); }
/** * Validate message payloads by comparing to a control message. * * @param receivedMessage * @param validationContext * @param context */ protected void validateMessageContent( Message receivedMessage, Message controlMessage, XmlMessageValidationContext validationContext, TestContext context) { if (controlMessage == null || controlMessage.getPayload() == null) { log.info("Skip message payload validation as no control message was defined"); return; } if (!(controlMessage.getPayload() instanceof String)) { throw new IllegalArgumentException( "DomXmlMessageValidator does only support message payload of type String, " + "but was " + controlMessage.getPayload().getClass()); } String controlMessagePayload = controlMessage.getPayload(String.class); if (receivedMessage.getPayload() == null || !StringUtils.hasText(receivedMessage.getPayload(String.class))) { Assert.isTrue( !StringUtils.hasText(controlMessagePayload), "Unable to validate message payload - received message payload was empty, control message payload is not"); return; } else if (!StringUtils.hasText(controlMessagePayload)) { return; } log.info("Start XML tree validation ..."); Document received = XMLUtils.parseMessagePayload(receivedMessage.getPayload(String.class)); Document source = XMLUtils.parseMessagePayload(controlMessagePayload); XMLUtils.stripWhitespaceNodes(received); XMLUtils.stripWhitespaceNodes(source); if (log.isDebugEnabled()) { log.debug("Received message:\n" + XMLUtils.serialize(received)); log.debug("Control message:\n" + XMLUtils.serialize(source)); } validateXmlTree( received, source, validationContext, namespaceContextBuilder.buildContext(receivedMessage, validationContext.getNamespaces()), context); }
/** * Handle attribute node during validation. * * @param receivedElement * @param receivedAttribute * @param sourceElement * @param validationContext */ private void doAttribute( Node receivedElement, Node receivedAttribute, Node sourceElement, XmlMessageValidationContext validationContext, NamespaceContext namespaceContext, TestContext context) { if (receivedAttribute.getNodeName().startsWith(XMLConstants.XMLNS_ATTRIBUTE)) { return; } String receivedAttributeName = receivedAttribute.getLocalName(); if (log.isDebugEnabled()) { log.debug( "Validating attribute: " + receivedAttributeName + " (" + receivedAttribute.getNamespaceURI() + ")"); } NamedNodeMap sourceAttributes = sourceElement.getAttributes(); Node sourceAttribute = sourceAttributes.getNamedItemNS(receivedAttribute.getNamespaceURI(), receivedAttributeName); Assert.isTrue( sourceAttribute != null, "Attribute validation failed for element '" + receivedElement.getLocalName() + "', unknown attribute " + receivedAttributeName + " (" + receivedAttribute.getNamespaceURI() + ")"); if (XmlValidationUtils.isAttributeIgnored( receivedElement, receivedAttribute, sourceAttribute, validationContext.getIgnoreExpressions(), namespaceContext)) { return; } String receivedValue = receivedAttribute.getNodeValue(); String sourceValue = sourceAttribute.getNodeValue(); if (isValidationMatcherExpression(sourceAttribute)) { ValidationMatcherUtils.resolveValidationMatcher( sourceAttribute.getNodeName(), receivedAttribute.getNodeValue().trim(), sourceAttribute.getNodeValue().trim(), context); } else if (receivedValue.contains(":") && sourceValue.contains(":")) { doNamespaceQualifiedAttributeValidation( receivedElement, receivedAttribute, sourceElement, sourceAttribute); } else { Assert.isTrue( receivedValue.equals(sourceValue), ValidationUtils.buildValueMismatchErrorMessage( "Values not equal for attribute '" + receivedAttributeName + "'", sourceValue, receivedValue)); } if (log.isDebugEnabled()) { log.debug("Attribute '" + receivedAttributeName + "'='" + receivedValue + "': OK"); } }
/** * Handle element node. * * @param received * @param source * @param validationContext */ private void doElement( Node received, Node source, XmlMessageValidationContext validationContext, NamespaceContext namespaceContext, TestContext context) { doElementNameValidation(received, source); doElementNamespaceValidation(received, source); // check if element is ignored either by xpath or by ignore placeholder in source message if (XmlValidationUtils.isElementIgnored( source, received, validationContext.getIgnoreExpressions(), namespaceContext)) { return; } // work on attributes if (log.isDebugEnabled()) { log.debug("Validating attributes for element: " + received.getLocalName()); } NamedNodeMap receivedAttr = received.getAttributes(); NamedNodeMap sourceAttr = source.getAttributes(); Assert.isTrue( countAttributes(receivedAttr) == countAttributes(sourceAttr), ValidationUtils.buildValueMismatchErrorMessage( "Number of attributes not equal for element '" + received.getLocalName() + "'", countAttributes(sourceAttr), countAttributes(receivedAttr))); for (int i = 0; i < receivedAttr.getLength(); i++) { doAttribute( received, receivedAttr.item(i), source, validationContext, namespaceContext, context); } // check if validation matcher on element is specified if (isValidationMatcherExpression(source)) { ValidationMatcherUtils.resolveValidationMatcher( source.getNodeName(), received.getFirstChild().getNodeValue().trim(), source.getFirstChild().getNodeValue().trim(), context); return; } // work on child nodes NodeList receivedChilds = received.getChildNodes(); NodeList sourceChilds = source.getChildNodes(); Assert.isTrue( receivedChilds.getLength() == sourceChilds.getLength(), ValidationUtils.buildValueMismatchErrorMessage( "Number of child elements not equal for element '" + received.getLocalName() + "'", sourceChilds.getLength(), receivedChilds.getLength())); for (int i = 0; i < receivedChilds.getLength(); i++) { this.validateXmlTree( receivedChilds.item(i), sourceChilds.item(i), validationContext, namespaceContext, context); } if (log.isDebugEnabled()) { log.debug( "Validation successful for element: " + received.getLocalName() + " (" + received.getNamespaceURI() + ")"); } }
/** * Validate message with a XML schema. * * @param receivedMessage * @param validationContext */ protected void validateXMLSchema( Message receivedMessage, XmlMessageValidationContext validationContext) { if (receivedMessage.getPayload() == null || !StringUtils.hasText(receivedMessage.getPayload(String.class))) { return; } try { Document doc = XMLUtils.parseMessagePayload(receivedMessage.getPayload(String.class)); if (!StringUtils.hasText(doc.getFirstChild().getNamespaceURI())) { return; } log.info("Starting XML schema validation ..."); XmlValidator validator = null; XsdSchemaRepository schemaRepository = null; if (validationContext.getSchema() != null) { validator = applicationContext .getBean(validationContext.getSchema(), XsdSchema.class) .createValidator(); } else if (validationContext.getSchemaRepository() != null) { schemaRepository = applicationContext.getBean( validationContext.getSchemaRepository(), XsdSchemaRepository.class); } else if (schemaRepositories.size() == 1) { schemaRepository = schemaRepositories.get(0); } else if (schemaRepositories.size() > 0) { for (XsdSchemaRepository repository : schemaRepositories) { if (repository.canValidate(doc)) { schemaRepository = repository; } } if (schemaRepository == null) { throw new CitrusRuntimeException( String.format( "Failed to find proper schema repository in Spring bean context for validating element '%s(%s)'", doc.getFirstChild().getLocalName(), doc.getFirstChild().getNamespaceURI())); } } else { log.warn( "Neither schema instance nor schema repository defined - skipping XML schema validation"); return; } if (schemaRepository != null) { if (!schemaRepository.canValidate(doc)) { throw new CitrusRuntimeException( String.format( "Unable to find proper XML schema definition for element '%s(%s)' in schema repository '%s'", doc.getFirstChild().getLocalName(), doc.getFirstChild().getNamespaceURI(), schemaRepository.getName())); } List<Resource> schemas = new ArrayList<>(); for (XsdSchema xsdSchema : schemaRepository.getSchemas()) { if (xsdSchema instanceof XsdSchemaCollection) { for (Resource resource : ((XsdSchemaCollection) xsdSchema).getSchemaResources()) { schemas.add(resource); } } else if (xsdSchema instanceof WsdlXsdSchema) { for (Resource resource : ((WsdlXsdSchema) xsdSchema).getSchemaResources()) { schemas.add(resource); } } else { synchronized (transformerFactory) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { transformerFactory .newTransformer() .transform(xsdSchema.getSource(), new StreamResult(bos)); } catch (TransformerException e) { throw new CitrusRuntimeException( "Failed to read schema " + xsdSchema.getTargetNamespace(), e); } schemas.add(new ByteArrayResource(bos.toByteArray())); } } } validator = XmlValidatorFactory.createValidator( schemas.toArray(new Resource[schemas.size()]), WsdlXsdSchema.W3C_XML_SCHEMA_NS_URI); } SAXParseException[] results = validator.validate(new DOMSource(doc)); if (results.length == 0) { log.info("Schema of received XML validated OK"); } else { log.error( "Schema validation failed for message:\n" + XMLUtils.prettyPrint(receivedMessage.getPayload(String.class))); // Report all parsing errors log.debug("Found " + results.length + " schema validation errors"); StringBuilder errors = new StringBuilder(); for (SAXParseException e : results) { errors.append(e.toString()); errors.append("\n"); } log.debug(errors.toString()); throw new ValidationException("Schema validation failed:", results[0]); } } catch (IOException e) { throw new CitrusRuntimeException(e); } catch (SAXException e) { throw new CitrusRuntimeException(e); } }