Spring Integration - XML Schema validator - Dynamic schema file selection - spring-integration

I am trying to implement XML validation using Spring Integration <int-xml:validating-filter />. I followed the discussion in usage of spring integration's schema validator?. The problem statement is the same but with an additional parameter. Instead to hard coding the value in schema-location="xyz.xsd", rather I want to dynamically select the appropriate xsd file for respective incoming xml or DOMSource inputs.
I also followed http://forum.spring.io/forum/spring-projects/integration/121115-dynamic-schema-location-for-xml-validating-filter-component where Gary Russell mentioned:
There's no direct support for dynamic schemas, but you can provide a
custom XmlValidator using the xml-validator attribute (mutually
exclusive with schema location)
Once you've introspected your document to find the schema you wish to
validate against, simply delegate to a validator that has been
configured to validate against that schema.
You can use a XmlValidatorFactory to create each validator; see the
XmlValidatingMessageSelector for how to create a validator, once you
know the schema location
Since the comments dates back to the year 2012, is there an approach now in spring integration to validate input xml by dynamically selecting appropriate schema? If not can anyone provide an example on how to implement?
Following is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway"
default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel"
error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<!-- How to do -->
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" />
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator" />
Here is my Validator class defined:
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
#Override
public SAXParseException[] validate(Source source) throws IOException {
/* How to implement this method?
Using XPath I can identify the root node from 'source' and then load
the appropriate XSD file. But don't know how to proceed
or what should be 'return'(ed) from here.
Any example is much appreciated.
*/
return null;
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// TODO Auto-generated method stub
return null;
}
}
Which is the best way of implementing the XML validator using Spring Integration?

There have been no changes since that comment.
As I said there, your validator needs to use XmlValidatorFactory to create a validator for each schema; then call a specific validator for each message; something like:
String schema = determineSchema(source);
XmlValidator val = lookupValidatorForSchema(schema);
if (val == null) {
// create a new one and add it to the map.
}
return val.validate(source);

If it helps other folks who are trying to do the same
Based on Gary's suggestion, I have come out with an implementation of XmlValidator by dynamically identifying input XML and then selecting appropriate Schema file to apply the validation.
Below is my spring integration configuration:
<int:gateway id="applicationServiceGateway" service-interface="abc.IGateway" default-request-channel="applicationRequestChannel" default-reply-channel="applicationResponseChannel" error-channel="errorProcessingChannel" />
<int:chain id="serviceRequestValidation" input-channel="applicationRequestChannel" output-channel="responseChannel">
<int-xml:validating-filter xml-validator="xmlValidator"
schema-type="xml-schema"
throw-exception-on-rejection="true" /> <!-- a MessageRejectedException is thrown in case validation fails -->
<int:service-activator id="schematronValidationActivator" ref="schematronValidator" method="validate" />
</int:chain>
<bean id="xmlValidator" class="abc.validator.DomSourceValidator">
<constructor-arg>
<map key-type="java.lang.String" value-type="java.lang.String">
<entry key="OTA_AirAvailRQ" value="common/schemas/FS_OTA_AirAvailRQ.xsd" />
<entry key="OTA_AirBookModifyRQ" value="common/schemas/FS_OTA_AirBookModifyRQ.xsd" />
<entry key="OTA_AirBookRQ" value="common/schemas/FS_OTA_AirBookRQ.xsd" />
</map>
</constructor-arg>
</bean>
To demonstrate I have used the OTA schema files to construct a map as constructor-arg. The map key is the root node from the XML file from the gateway and value is the location of the xsd file; and form the key-value pair map.
Refer to the below implementation class how this map is being used to identify the input XML and apply the validation.
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
import org.springframework.xml.validation.ValidationErrorHandler;
import org.springframework.xml.validation.XmlValidator;
import org.springframework.xml.validation.XmlValidatorFactory;
import org.xml.sax.SAXParseException;
public class DomSourceValidator implements XmlValidator {
private static final Log LOGGER = LogFactory.getLog(DomSourceValidator.class);
private Map<String, String> schemaMap;
private static Map<String, XmlValidator> validatorMap = new HashMap<>();
public DomSourceValidator(Map<String, String> schemaMap) {
this.schemaMap = schemaMap;
}
#PostConstruct
private void init() throws IOException {
LOGGER.info("Constructing Validators from schema resource list ...");
Assert.notEmpty(schemaMap, "No schema resource map found");
if (validatorMap.isEmpty()) {
XmlValidator validator = null;
for (Map.Entry<String, String> entry : schemaMap.entrySet()) {
validator = createValidatorFromResourceUri(entry.getValue());
validatorMap.put(entry.getKey(), validator);
}
}
}
#Override
public SAXParseException[] validate(Source source) throws IOException {
Assert.notNull(schemaMap, "No validator(s) defined");
XmlValidator validator = lookupValidator(source);
return validator.validate(source);
}
#Override
public SAXParseException[] validate(Source source, ValidationErrorHandler errorHandler) throws IOException {
// Skip implementation
return null;
}
private XmlValidator lookupValidator(Source source) {
String reqType = determineRequestType(source);
LOGGER.info("Loading validator for type: " + reqType);
XmlValidator xmlValidator = validatorMap.get(reqType);
Assert.notNull(xmlValidator, "No validator found for type: " + reqType);
return xmlValidator;
}
private String determineRequestType(Source source) {
if (source instanceof DOMSource) {
return ((DOMSource) source).getNode().getFirstChild().getNodeName();
}
return null;
}
private XmlValidator createValidatorFromResourceUri(String schemaResource) throws IOException {
Assert.notNull(schemaResource);
return XmlValidatorFactory.createValidator(new ClassPathResource(schemaResource), XmlValidatorFactory.SCHEMA_W3C_XML);
}
}
As soon as the spring bean id="xmlValidator" is initialized, the #PostConstruct kicks in to create Validator instances using XmlValidatorFactory from the resource URIs to have a pre-initialized validators.
If there is a validation error, a org.springframework.integration.MessageRejectedException: Message was rejected due to XML Validation errors is thrown (ref. throw-exception-on-rejection="true" in the <int-xml:validating-filter />).
The above implementation works perfectly fine for me. One can customize it further, or post another version to achieve the same.
Note
Instead of using a <int-xml:validating-filter />, one can also use a <int:service-activator /> in the <int-chain />, as logically <int-xml:validating-filter /> does not actually do any filter logic. But it serves the purpose.

Related

How to have Spring ContextConfiguration load both from XML and from JavaConfig

From what I've read of the Spring #ContextConfiguration annotation, it's possible to load multiple XML context files, or multiple JavaConfig classes. What I need is to load from one XML context file and one class. All the examples I've seen either load all XML, or all classes, but not both.
I'm trying to do this because I want my test class, which is just there to verify expected Spring wiring, to load my default applicationContext.xml file (presently just a copy stored in "src/test/resources, and trying to figure out how to directly specify the default one) along with a JavaConfig class that specifies some JNDI resources that need to be available. For the purposes of my test, I only need to set those JNDI resources to dummy strings, but I'd really like to specify them in an inline static class instead of an external XML file, because my tests are going to have to verify that some settings are equal to those dummy strings, and it's more maintainable if both the values and the checks are in the same file.
What I have so far, and what I've tried, can be illustrated with this:
#RunWith(SpringRunner.class)
#ContextConfiguration(value = {"/testApplicationContext.xml", "/testResources.xml"})
//#ContextHierarchy({
// #ContextConfiguration("/testApplicationContext.xml"),
// #ContextConfiguration(classes = SpringWiringTest.Config.class)
//})
#TestPropertySource(properties = { "env = tomcat", "doNotifications = false" })
public class SpringWiringTest {
And this at the end of the class:
#Configuration
public static class Config {
#Bean public String uslDatasourcesList() { return "abc"; }
#Bean public String atgDatasourcesList() { return "abc"; }
#Bean public String uslTableNamePrefixsList() { return "abc"; }
#Bean public String atgTableNamePrefixsList() { return "abc"; }
#Bean public String doNotifications() { return "false"; }
#Bean public DataSource abc() { return new DriverManagerDataSource(); }
}
If I comment out the first #ContextConfiguration and comment back in the #ContextHierarchy block, I get an error like this:
Error creating bean with name 'uslDatasourcesList': Invocation of init
method failed; nested exception is
javax.naming.NoInitialContextException: Need to specify class name in
environment or system property, or as an applet parameter, or in an
application resource file: java.naming.factory.initial
Update:
Using the guideline of picking either JavaConfig or XML as the "entry point" to configuration, here are some modified excerpts that show what I'm trying:
#RunWith(SpringRunner.class)
#ContextConfiguration
//#ContextConfiguration(value = {"file:src/main/webapp/WEB-INF/applicationContext.xml", "/testResources.xml"})
#TestPropertySource(properties = { "env = tomcat", "doNotifications = false" })
public class SpringWiringTest {
...
#BeforeClass
public static void setup() throws Exception {
SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
DataSource ds = new DriverManagerDataSource();
builder.bind("java:comp/env/abc", ds);
}
...
#Configuration
#ImportResource("file:src/main/webapp/WEB-INF/applicationContext.xml")
public static class Config {
#Bean public String uslDatasourcesList() { return "abc"; }
#Bean public String atgDatasourcesList() { return "abc"; }
#Bean public String uslTableNamePrefixsList() { return "abc"; }
#Bean public String atgTableNamePrefixsList() { return "abc"; }
#Bean public String doNotifications() { return "false"; }
#Bean public DataSource abc() { return new DriverManagerDataSource(); }
}
}
When I run my test, the bottom "Caused by" in the exception says this:
Caused by: javax.naming.NameNotFoundException: Name
[uslDatasourcesList] not bound; 1 bindings: [java:comp/env/abc]
In the alternate version, using the commented-out "#ContextConfiguration" (and commenting out the Config class and its annotations), this error does not occur.
Note that this the meat of my "testResources.xml" file:
<bean id="uslDatasourcesList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="atgDatasourcesList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="uslTableNamePrefixList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="atgTableNamePrefixList" class="java.lang.String"> <constructor-arg value="abc"/> </bean>
<bean id="doNotifications" class="java.lang.String"> <constructor-arg value="false"/> </bean>
<bean id="abc" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
</bean>
Note that the bean mentioned in the error message, "uslDatasourcesList" is defined in both versions, but it's not working in the version with JavaConfig and XML mixed.
It almost appears that the beans in the "#ImportResource" annotation are evaluated on their own, before the beans declared in the JavaConfig class are merged into it.
This is clearly documented in the Spring Reference Manual in the section named Mixing XML, Groovy scripts, and annotated classes.
In summary, ...
... you will have to pick one as the entry point, and that one will have to include or import the other.
Thus, the following should hopefully solve your problem.
#RunWith(SpringRunner.class)
#ContextConfiguration
#TestPropertySource(properties = { "env = tomcat", "doNotifications = false" })
public class SpringWiringTest {
// ...
#Configuration
#ImportResource({"/testApplicationContext.xml", "/testResources.xml"})
static class Config {
// ...
}
}

Message Handler Chain in Spring Boot

Since Spring Boot recommends Java based configuration, I'm having trouble to convert the following xml based message handler chain config to Java based. Any help is appreciated.
<chain input-channel="incomingChannel" output-channel="completeChannel">
<splitter ref="itemSplitter" />
<transformer ref="transformer1" />
<transformer ref="transformer2" />
<aggregator ref="requestProcessor" />
<transformer ref="transformer3" />
<transformer ref="transformer4" />
I have tried to use IntegrationFlows to achieve the same as above.
#Bean
public IntegrationFlow incomingFlow(){
return IntegrationFlows.from(incomingChannel())
.split("itemSplitter","split")
.transform("transformer1")
.transform("transformer2")
.aggregate()//? need to figure out how to initialize this?
.transform("transformer3")
.transform("transformer4")
.channel(completeChannel())
.get();
}
But I got the following error
Failed to transform Message; nested exception is org.springframework.messaging.MessageHandlingException: Expression evaluation failed: locateItemEnrichmentTransformer; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 0): Property or field 'transformer1' cannot be found on object of type 'org.springframework.messaging.support.GenericMessage' - maybe not public?
Hence I'm not sure if this is the equivalent way in Java code to translate the original chain xml config.
RequestProcessor (aggregator) implementation:
#Component
public class RequestProcessor {
/** The log. */
private static Log log = LogFactory.getLog(RequestProcessor.class);
#Aggregator
public Message<Requests> aggregate(#Headers Map<String, ?> headers, List<Request> requests) {
try {
return MessageBuilder.withPayload(new Requests(service.submit(requests, false, true)))
.copyHeaders(headers)
.build();
} catch (ClientException e) {
log.error(e.toString());
return null;
}
}
}
There is no obligation to convert the flow from XML to Java - you can use #ImportResource to pull in the XML.
It is certainly possible to wire up a MessageHandlerChain in java but as you have found, it's easier to use the Java DSL to replace a chain.
The
.transform("transformer1")
form of .transform() (1 String parameter) expects an expression, not a bean name.
You can use
.transform(transformer1())
Where transformer1() is your transformer #Bean.
EDIT
For the aggregator, if you are using Java 8...
.aggregate(a -> a.processor(requestProcessor()))
...for Java 7 or 6...
.aggregate(new Consumer<AggregatorSpec>() {
public void accept(AggregatorSpec a) {
a.processor(requestProcessor());
}
})

JAX WS, JAXB and Null elements with attributes

I am trying to consume a web service using JAXWS and wsimport. The WSIMPORT tool generated all the required classes and I can invoke the service without any issues.
However, I noticed in cases where response contains a nil element with valid attribute values, JAXWS fails to unmarshall it and throws a NullPointerException. I used SOAP UI to help debug and here's what I found. The response returns the following XML (Excerpt):
<externalIdentifiers>
<identifierType code="2" name="Passport" xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
<identifierValue/>
<issuingCountry xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</externalIdentifiers>
In my Java code, when trying to read the "name" property of identifier type as above, it throws a NPE:
if(id.getIdentifierType() == null)
{
System.out.println("NULL");
}
System.out.println("Identifier Type: " + id.getIdentifierType().getName());
Output:
NULL
Exception in thread "main" java.lang.NullPointerException
To me that does looks a reasonable response as in the response, identifierType is set as xsi:nil="true". That is also perfectly valid XML as per W3C. Question is, how do I read the attribute values such as code and name in such a case?
Below is how you can support this use case:
Java Model
ExternalIdentifiers
You can change the identifierType property to be of type JAXBElement<IdentifierType> instead of IdentifierType. To do this you will need to annotate the property with #XmlElementRef.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class ExternalIdentifiers {
#XmlElementRef(name="identifierType")
private JAXBElement<IdentifierType> identifierType;
public JAXBElement<IdentifierType> getIdentifierType() {
return identifierType;
}
}
ObjectFactory
You will need a corresponding #XmlElementDecl annotation on a create method in a class annotated with #XmlRegistry.
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
#XmlRegistry
public class ObjectFactory {
#XmlElementDecl(name="identifierType")
public JAXBElement<IdentifierType> createIdentifierType(IdentifierType identifierType) {
return new JAXBElement(new QName("identifierType"), IdentifierType.class, identifierType);
}
}
Demo Code
input.xml
<?xml version="1.0" encoding="UTF-8"?>
<externalIdentifiers>
<identifierType code="2" name="Passport" xsi:nil="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</externalIdentifiers>
Demo
import java.io.File;
import javax.xml.bind.*;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(ExternalIdentifiers.class, ObjectFactory.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
File xml = new File("src/forum18834036/input.xml");
ExternalIdentifiers externalIdentifiers = (ExternalIdentifiers) unmarshaller.unmarshal(xml);
System.out.println(externalIdentifiers.getIdentifierType().getValue().getName());
}
}
Output
Passport
Note
Currently there is a bug in EclipseLink JAXB (MOXy) regarding this use case:
http://bugs.eclipse.org/404944

Spring 2.5 to 3.2 upgrade error: Invalid property 'commandClass' of bean class

I am new to Spring and am upgrading a Spring 2.5 web app to 3.2.3. I am getting an error navigating off the front page of the app.
The error is Invalid property 'commandClass' of bean class. This web app has been running for about 5 years, so the
issue has to be the Spring 2.5 to 3.2 changes. I must have something wired wrong, any ideas?
The full error is :
Error creating bean with name '/new_candidate.html' defined in ServletContext resource [/WEB-INF/webapp-servlet.xml]:
Error setting property values;
nested exception is org.springframework.beans.NotWritablePropertyException:
Invalid property 'commandClass' of bean class [org.myorg.app.web.ScoreChangeController]:
Bean property 'commandClass' is not writable or has an invalid setter method.
Does the parameter type of the setter match the return type of the getter?
Here is the bean def from the webapp-sevlet.xml:
<bean name="/new_candidate.html" class="org.myorg.app.web.ScoreChangeController" scope="session">
<property name="commandClass" value="org.myorg.app.model.Database"/>
<property name="formView" value="generic"/>
<property name="candidateManager" ref="candidateManager"/>
</bean>
The controller is:
package org.myorg.app.web;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.myorg.app.model.Database;
import org.myorg.app.service.CandidateManager;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.validation.BindException;
import org.springframework.web.util.WebUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
public class ScoreChangeController
{
private CandidateManager candidateManager;
protected Object formBackingObject(HttpServletRequest request) throws Exception {
Database defaultDatabase = new Database();
defaultDatabase.setApptNo("16 digits");
defaultDatabase.setAccessionNo("8 digits");
defaultDatabase.setTstPkgId("12345678912345678912345");
return defaultDatabase;
}
protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
Database database = (Database)binder.getTarget();
}
public CandidateManager getCandidateManager() {
return candidateManager;
}
public void setCandidateManager(CandidateManager candidateManager) {
this.candidateManager = candidateManager;
}
public ModelAndView onSubmit(HttpServletRequest req, HttpServletResponse res, Object command, BindException errors) throws Exception {
if(WebUtils.hasSubmitParameter(req, "retrieve"))
{
candidateManager.retrieveData((Database)command);
}
return new ModelAndView(new RedirectView("success.jsp"));
}
}
You are trying to inject property commandClass inside the ScoreChangeController bean, but i am not able to see any property with that name in that controller and moreover it is not a subclass of any specific controller also so no chance of inheritance also.
Solution : Either remove the injection of commandClassproperty from the ScoreChangeController bean .
<bean name="/new_candidate.html" class="org.myorg.app.web.ScoreChangeController" scope="session">
<property name="formView" value="generic"/>
<property name="candidateManager" ref="candidateManager"/>
OR create a new property in your ScoreChangeController having the name as command and create the setters and getters for that.

Camel POJO producing with JAXB data format exception

I am using Camel 2.10.3
Here is my camel context:
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<endpoint id="webserviceStart" uri="direct:webserviceStart"/>
<dataFormats>
<jaxb id="jaxb" prettyPrint="true"
contextPath="com.jaxbtest.package" />
</dataFormats>
<route id="myRoute">
<from ref="webserviceStart" />
<marshal ref="jaxb" />
<to uri="spring-ws:http://wshost:8010/service"/>
<unmarshal ref="jaxb" />
</route>
</camelContext>
This code works:
#Component
public class WebserviceClient
{
#EndpointInject( ref = "webserviceStart" )
ProducerTemplate _producer;
public Response invoke( Request input )
{
return ( Response ) _producer.sendBody( input ).getOut().getBody();
}
}
This code (following the "Hiding the Camel APIs from your code using #Produce" section of http://camel.apache.org/pojo-producing.html) does not:
#Component
public class WebserviceClient
{
public static interface MyWebservice
{
Response invoke( #Body Request body );
}
#EndpointInject( ref = "webserviceStart" )
MyWebservice _producer;
public Response invoke( Request input )
{
return ( Response ) _producer.invoke( input );
}
}
it throws an exception:
Caused by: java.io.IOException: javax.xml.bind.JAXBException: class org.apache.camel.component.bean.BeanInvocation nor any of its super class is known to this context.
at org.apache.camel.converter.jaxb.JaxbDataFormat.marshal(JaxbDataFormat.java:103)
at org.apache.camel.processor.MarshalProcessor.process(MarshalProcessor.java:59)
at org.apache.camel.util.AsyncProcessorConverterHelper$ProcessorToAsyncProcessorBridge.process(AsyncProcessorConverterHelper.java:61)
at org.apache.camel.util.AsyncProcessorHelper.process(AsyncProcessorHelper.java:73)
If this is a known bug in camel does anyone know the issue that is related to it? Should I create a new JIRA for this? This seems to be such a simple use case of POJO producing.
Generally, when you get this error, it means you haven't set the list of classes on the JAXB context.
You'd do in JAVA DSL -
JAXBContext context = JAXBContext.newInstance('your classes');
JaxbDataFormat jaxb = new JaxbDataFormat();
jaxb.setContext(context);
and then use your custom dataformat 'jaxb' as your marshal/unmarshal ref.
Thanks!

Resources