/** * 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); }
/** * Checks whether the given node contains a validation matcher * * @param node * @return true if node value contains validation matcher, false if not */ private boolean isValidationMatcherExpression(Node node) { switch (node.getNodeType()) { case Node.ELEMENT_NODE: return node.getFirstChild() != null && StringUtils.hasText(node.getFirstChild().getNodeValue()) && ValidationMatcherUtils.isValidationMatcherExpression( node.getFirstChild().getNodeValue().trim()); case Node.ATTRIBUTE_NODE: return StringUtils.hasText(node.getNodeValue()) && ValidationMatcherUtils.isValidationMatcherExpression(node.getNodeValue().trim()); default: return false; // validation matchers makes no sense } }
/** * Handle document type definition with validation of publicId and systemId. * * @param received * @param source * @param validationContext * @param namespaceContext */ private void doDocumentTypeDefinition( Node received, Node source, XmlMessageValidationContext validationContext, NamespaceContext namespaceContext, TestContext context) { Assert.isTrue( source instanceof DocumentType, "Missing document type definition in expected xml fragment"); DocumentType receivedDTD = (DocumentType) received; DocumentType sourceDTD = (DocumentType) source; if (log.isDebugEnabled()) { log.debug( "Validating document type definition: " + receivedDTD.getPublicId() + " (" + receivedDTD.getSystemId() + ")"); } if (!StringUtils.hasText(sourceDTD.getPublicId())) { Assert.isNull( receivedDTD.getPublicId(), ValidationUtils.buildValueMismatchErrorMessage( "Document type public id not equal", sourceDTD.getPublicId(), receivedDTD.getPublicId())); } else if (sourceDTD.getPublicId().trim().equals(CitrusConstants.IGNORE_PLACEHOLDER)) { if (log.isDebugEnabled()) { log.debug( "Document type public id: '" + receivedDTD.getPublicId() + "' is ignored by placeholder '" + CitrusConstants.IGNORE_PLACEHOLDER + "'"); } } else { Assert.isTrue( StringUtils.hasText(receivedDTD.getPublicId()) && receivedDTD.getPublicId().equals(sourceDTD.getPublicId()), ValidationUtils.buildValueMismatchErrorMessage( "Document type public id not equal", sourceDTD.getPublicId(), receivedDTD.getPublicId())); } if (!StringUtils.hasText(sourceDTD.getSystemId())) { Assert.isNull( receivedDTD.getSystemId(), ValidationUtils.buildValueMismatchErrorMessage( "Document type system id not equal", sourceDTD.getSystemId(), receivedDTD.getSystemId())); } else if (sourceDTD.getSystemId().trim().equals(CitrusConstants.IGNORE_PLACEHOLDER)) { if (log.isDebugEnabled()) { log.debug( "Document type system id: '" + receivedDTD.getSystemId() + "' is ignored by placeholder '" + CitrusConstants.IGNORE_PLACEHOLDER + "'"); } } else { Assert.isTrue( StringUtils.hasText(receivedDTD.getSystemId()) && receivedDTD.getSystemId().equals(sourceDTD.getSystemId()), ValidationUtils.buildValueMismatchErrorMessage( "Document type system id not equal", sourceDTD.getSystemId(), receivedDTD.getSystemId())); } validateXmlTree( received.getNextSibling(), source.getNextSibling(), validationContext, namespaceContext, context); }
/** * Validate namespaces in message. The method compares namespace declarations in the root element * of the received message to expected namespaces. Prefixes are important too, so differing * namespace prefixes will fail the validation. * * @param expectedNamespaces * @param receivedMessage */ protected void validateNamespaces( Map<String, String> expectedNamespaces, Message receivedMessage) { if (CollectionUtils.isEmpty(expectedNamespaces)) { return; } if (receivedMessage.getPayload() == null || !StringUtils.hasText(receivedMessage.getPayload(String.class))) { throw new ValidationException( "Unable to validate message namespaces - receive message payload was empty"); } log.info("Start XML namespace validation"); Document received = XMLUtils.parseMessagePayload(receivedMessage.getPayload(String.class)); Map<String, String> foundNamespaces = XMLUtils.lookupNamespaces(receivedMessage.getPayload(String.class)); if (foundNamespaces.size() != expectedNamespaces.size()) { throw new ValidationException( "Number of namespace declarations not equal for node " + XMLUtils.getNodesPathName(received.getFirstChild()) + " found " + foundNamespaces.size() + " expected " + expectedNamespaces.size()); } for (Entry<String, String> entry : expectedNamespaces.entrySet()) { String namespace = entry.getKey(); String url = entry.getValue(); if (foundNamespaces.containsKey(namespace)) { if (!foundNamespaces.get(namespace).equals(url)) { throw new ValidationException( "Namespace '" + namespace + "' values not equal: found '" + foundNamespaces.get(namespace) + "' expected '" + url + "' in reference node " + XMLUtils.getNodesPathName(received.getFirstChild())); } else { log.info( "Validating namespace " + namespace + " value as expected " + url + " - value OK"); } } else { throw new ValidationException( "Missing namespace " + namespace + "(" + url + ") in node " + XMLUtils.getNodesPathName(received.getFirstChild())); } } log.info("XML namespace validation finished successfully: All values OK"); }
/** * 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); } }