Skip to content

Commit

Permalink
feat: xml validation add fail if no schema found attribut
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Jegge authored and bbortt committed Jan 15, 2024
1 parent cad790d commit 52eeec5
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.citrusframework.validation.xml.schema;

public enum ValidationStrategy {

IGNORE,
FAIL
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
package org.citrusframework.validation.xml.schema;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.citrusframework.XmlValidationHelper;
import org.citrusframework.context.TestContext;
import org.citrusframework.exceptions.CitrusRuntimeException;
Expand All @@ -33,14 +24,49 @@
import org.w3c.dom.Document;
import org.xml.sax.SAXParseException;

import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static java.lang.String.format;
import static java.util.Optional.ofNullable;
import static org.citrusframework.validation.xml.schema.ValidationStrategy.FAIL;

public class XmlSchemaValidation implements SchemaValidator<XmlMessageValidationContext> {

public static final String NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME = "citrus.xml.no.schema.found.strategy";
public static final String NO_SCHEMA_FOUND_STRATEGY_ENV_VAR_NAME = NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME.replace(".", "_").toUpperCase();

/** Logger */
private static final Logger logger = LoggerFactory.getLogger(XmlSchemaValidation.class);

/** Transformer factory */
private final TransformerFactory transformerFactory = TransformerFactory.newInstance();

/** fail if no schema found property */
private final ValidationStrategy noSchemaFoundStrategy;

public XmlSchemaValidation() {
this(new SystemProvider());
}

/**
* {@code protected} constructor meant for injecting mocks during tests.
*/
protected XmlSchemaValidation(SystemProvider systemProvider) {
this(getSchemaValidationStrategy(systemProvider));
}

public XmlSchemaValidation(ValidationStrategy noSchemaFoundStrategy) {
this.noSchemaFoundStrategy = noSchemaFoundStrategy;
}

/**
* Validate message with an XML schema.
*
Expand Down Expand Up @@ -76,24 +102,31 @@ private void validateSchema(Message message, TestContext context, XmlMessageVali
schemaRepository = context.getReferenceResolver().resolve(validationContext.getSchemaRepository(), XsdSchemaRepository.class);
} else if (schemaRepositories.size() == 1) {
schemaRepository = schemaRepositories.get(0);
} else if (schemaRepositories.size() > 0) {
schemaRepository = schemaRepositories.stream().filter(repository -> repository.canValidate(doc)).findFirst().orElseThrow(() -> new CitrusRuntimeException(String.format("Failed to find proper schema " + "repository for validating element '%s(%s)'", doc.getFirstChild().getLocalName(), doc.getFirstChild().getNamespaceURI())));
} else if (!schemaRepositories.isEmpty()) {
schemaRepository = schemaRepositories.stream().filter(repository -> repository.canValidate(doc)).findFirst().orElseThrow(() -> new CitrusRuntimeException(format("Failed to find proper schema " + "repository for validating element '%s(%s)'", doc.getFirstChild().getLocalName(), doc.getFirstChild().getNamespaceURI())));
} else {
logger.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()));
if (FAIL.equals(noSchemaFoundStrategy)) {
throw new CitrusRuntimeException(format("Unable to find proper XML schema definition for element '%s(%s)' in schema repository '%s'", doc.getFirstChild().getLocalName(), doc.getFirstChild().getNamespaceURI(), schemaRepository.getName()));
} else {
if (logger.isTraceEnabled()) {
logger.trace(createSchemaNotFoundMessage(doc, schemaRepository));
}
return;
}
}

List<Resource> schemas = new ArrayList<>();
for (XsdSchema xsdSchema : schemaRepository.getSchemas()) {
if (xsdSchema instanceof XsdSchemaCollection) {
schemas.addAll(((XsdSchemaCollection) xsdSchema).getSchemaResources());
} else if (xsdSchema instanceof WsdlXsdSchema) {
schemas.addAll(((WsdlXsdSchema) xsdSchema).getSchemaResources());
if (xsdSchema instanceof XsdSchemaCollection xsdSchemaCollection) {
schemas.addAll(xsdSchemaCollection.getSchemaResources());
} else if (xsdSchema instanceof WsdlXsdSchema wsdlXsdSchema) {
schemas.addAll(wsdlXsdSchema.getSchemaResources());
} else {
synchronized (transformerFactory) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Expand Down Expand Up @@ -146,4 +179,42 @@ private void validateSchema(Message message, TestContext context, XmlMessageVali
public boolean supportsMessageType(String messageType, Message message) {
return "XML".equals(messageType) || (message != null && IsXmlPredicate.getInstance().test(message.getPayload(String.class)));
}

private String createSchemaNotFoundMessage(Document doc, XsdSchemaRepository schemaRepository) {
return format(
"Unable to find proper XML schema definition for element '%s(%s)' in schema repository '%s'",
doc.getFirstChild().getLocalName(),
doc.getFirstChild().getNamespaceURI(),
schemaRepository.getName()
);
}

private static ValidationStrategy getSchemaValidationStrategy(SystemProvider systemProvider) {
return extractEnvOrProperty(systemProvider, NO_SCHEMA_FOUND_STRATEGY_ENV_VAR_NAME, NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME)
.map(String::toUpperCase)
.map(value -> {
try {
return ValidationStrategy.valueOf(value);
} catch (IllegalArgumentException e) {
throw new CitrusRuntimeException(format("Invalid property value '%s' for no schema found strategy", value));
}
})
.orElse(FAIL);
}

private static Optional<String> extractEnvOrProperty(SystemProvider systemProvider, String envVarName, String fallbackPropertyName) {
return systemProvider.getEnv(envVarName)
.or(() -> systemProvider.getProperty(fallbackPropertyName));
}

static final class SystemProvider {

Optional<String> getEnv(String envVarName) {
return ofNullable(System.getenv(envVarName));
}

Optional<String> getProperty(String propertyName) {
return ofNullable(System.getProperty(propertyName));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.citrusframework.validation.xml.schema;

import org.citrusframework.context.TestContext;
import org.citrusframework.context.TestContextFactory;
import org.citrusframework.exceptions.CitrusRuntimeException;
import org.citrusframework.message.DefaultMessage;
import org.citrusframework.message.Message;
import org.citrusframework.validation.xml.XmlMessageValidationContext;
import org.citrusframework.xml.XsdSchemaRepository;
import org.mockito.Mock;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.Optional;

import static java.lang.String.format;
import static org.citrusframework.validation.xml.schema.ValidationStrategy.FAIL;
import static org.citrusframework.validation.xml.schema.ValidationStrategy.IGNORE;
import static org.citrusframework.validation.xml.schema.XmlSchemaValidation.NO_SCHEMA_FOUND_STRATEGY_ENV_VAR_NAME;
import static org.citrusframework.validation.xml.schema.XmlSchemaValidation.NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME;
import static org.mockito.Mockito.doReturn;
import static org.mockito.MockitoAnnotations.openMocks;
import static org.springframework.test.util.ReflectionTestUtils.getField;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertThrows;

public class XmlSchemaValidationTest {

@Mock
private XmlSchemaValidation.SystemProvider systemProviderMock;

@BeforeMethod
public void beforeMethodSetup() {
openMocks(this);
}

@DataProvider(name = "validParameters")
public Object[][] dataProvider() {
return new Object[][] {
{"Ignore", IGNORE},
{"Fail", FAIL},
{"IGNORE", IGNORE},
{"FAIL", FAIL},
{"ignore", IGNORE},
{"fail", FAIL},
{null, FAIL},
};
}

@Test
public void configurationFromEnvTakesPrecedenceOverProperties() {
doReturn(Optional.of("IGNORE")).when(systemProviderMock).getEnv(NO_SCHEMA_FOUND_STRATEGY_ENV_VAR_NAME);
doReturn(Optional.of("FAIL")).when(systemProviderMock).getProperty(NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME);

XmlSchemaValidation fixture = new XmlSchemaValidation(systemProviderMock);

assertEquals(getField(fixture, "noSchemaFoundStrategy"), IGNORE);
}

@Test
public void configurationFromEnv() {
doReturn(Optional.of("IGNORE")).when(systemProviderMock).getEnv(NO_SCHEMA_FOUND_STRATEGY_ENV_VAR_NAME);

XmlSchemaValidation fixture = new XmlSchemaValidation(systemProviderMock);

assertEquals(getField(fixture, "noSchemaFoundStrategy"), IGNORE);
}

@Test
public void configurationFromProp() {
doReturn(Optional.of("IGNORE")).when(systemProviderMock).getProperty(NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME);

XmlSchemaValidation fixture = new XmlSchemaValidation(systemProviderMock);

assertEquals(getField(fixture, "noSchemaFoundStrategy"), IGNORE);
}

@Test
public void configurationDefaultValues() {
XmlSchemaValidation fixture = new XmlSchemaValidation();
assertEquals(getField(fixture, "noSchemaFoundStrategy"), FAIL);
}

@Test(dataProvider = "validParameters")
public void testValidPropertyValues(String value, ValidationStrategy expectedValidationStrategy) throws ParserConfigurationException, IOException, SAXException {
doReturn(Optional.ofNullable(value)).when(systemProviderMock).getProperty(NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME);
XmlSchemaValidation fixture = new XmlSchemaValidation(systemProviderMock);

Message message = new DefaultMessage("<message xmlns='http://citrusframework.org/otherXsd'>"
+ "<attribut>Hello Citrus</attribut>"
+ "</message>");

XsdSchemaRepository schemaRepository = new XsdSchemaRepository();
Resource schemaResource = new ClassPathResource("org/citrusframework/validation/test.xsd");
SimpleXsdSchema schema = new SimpleXsdSchema(schemaResource);
schema.afterPropertiesSet();

schemaRepository.getSchemas().add(schema);

TestContextFactory testContextFactory = TestContextFactory.newInstance();
TestContext context = testContextFactory.getObject();

context.getReferenceResolver().bind("schemaRepository", schemaRepository);

switch (expectedValidationStrategy) {
case FAIL -> assertThrows("Unable to find proper XML schema definition for element 'message(http://citrusframework.org/otherXsd)' in schema repository 'schemaRepository'", CitrusRuntimeException.class, () -> fixture.validate(message, context, new XmlMessageValidationContext()));
case IGNORE -> fixture.validate(message, context, new XmlMessageValidationContext());
default -> throw new IllegalArgumentException("Unexpected ValidationStrategy: " + expectedValidationStrategy + "!");
}
}

@Test
public void testInvalidPropertyValue() {
String invalidValue = "DEACTIVATED";

doReturn(Optional.of(invalidValue)).when(systemProviderMock).getProperty(NO_SCHEMA_FOUND_STRATEGY_PROPERTY_NAME);

assertThrows(format("Invalid property value '%s' for no schema found strategy", invalidValue), CitrusRuntimeException.class,() -> new XmlSchemaValidation(systemProviderMock));
}
}

0 comments on commit 52eeec5

Please sign in to comment.