how to implement integration router in java - spring-integration

i am trying to convert below XML snippet into Java version, appreciate any help here
<int:router input-channel="channel_in" default-output-channel="channel_default"
expression="payload.name" ignore-channel-name-resolution-failures="true">
<int:mapping value="foo" channel="channel_one" />
<int:mapping value="bar" channel="channel_two" />
</int:router>
Here is what i did for my concrete example
#Router(inputChannel = "routerChannel")
public String route(Account message) {
if (message.getType().equals("check")) {
return "checkChannel";
} else if (message.getType().equals("credit")) {
return "creditChannel";
}
return "errorChannel";
}
#Bean
public DirectChannel checkChannel() {
return new DirectChannel();
}
when i do above i am seeing below error
org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application:8090.checkChannel'.;

All the Spring Integration custom tag has a description like this:
<xsd:element name="router" type="routerType">
<xsd:annotation>
<xsd:documentation>
Defines a Consumer Endpoint for the
'org.springframework.integration.router.AbstractMessageProcessingRouter' implementation
that serves as an adapter for invoking a method on any
Spring-managed object as specified by the "ref" and "method" attributes.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
So, it becomes pretty clear that we need some AbstractMessageProcessingRouter implementation to be present in the Java Configuration.
Also we have a paragraph in the Reference Manual like this:
With XML configuration and Spring Integration Namespace support, the XML Parsers hide how target beans are declared and wired together. For Java & Annotation Configuration, it is important to understand the Framework API for target end-user applications.
According your expression="payload.name" we need to look for the ExpressionEvaluatingRouter and then also read a chapter about #Bean configuration:
#Bean
#Router(inputChannel = "channel_in")
public ExpressionEvaluatingRouter expressionRouter() {
ExpressionEvaluatingRouter router = new ExpressionEvaluatingRouter("payload.name");
router.setDefaultOutputChannelName("channel_default");
router.setChannelMapping("foo", "channel_one");
router.setChannelMapping("bar", "channel_two");
return router;
}

Related

Translate JMS Queue XML config to Java config

In order to learn spring integration I've been attempting to create a simple, resilient log processor. I'm also wanting to stick with a java configuration approach.
I've been having a difficult time translating existing XML configuration, mostly due to being so new to spring in general.
In a question on the spring forums Gary Russell presented a similar solution to this using a publish-subscribe + JMS model with a simple XML config.
I've been attempting to translate his suggestion into a Java config, but am stuck. Namely I'm not sure of the proper entities to use for the outbound-channel-adapter, service-activators or how to set the order of messages properly.
Here is Gary's XML config:
<int-file:inbound-channel-adapter id="dispatcher"
directory="spool"
channel="fileChannel">
<int:poller fixed-delay="2000">
<int:transactional/>
</int:poller>
</int-file:inbound-channel-adapter>
<int:channel id="fileChannel" />
<int-file:file-to-string-transformer input-channel="fileChannel" output-channel="dispatchChannel" />
<int:publish-subscribe-channel id="dispatchChannel" />
<int-jms:outbound-channel-adapter id="dispatcherJms" channel="dispatchChannel" order="1"
connection-factory="connectionFactory"
destination="dispatcher.queue" />
<!-- If JMS Send was successful, remove the file (within the transaction)-->
<int:service-activator input-channel="dispatchChannel" order="2"
output-channel="nullChannel"
expression="headers.file_originalFile.delete()">
<bean id="transactionManager" class="org.springframework.jms.connection.JmsTransactionManager">
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
UPDATE
Based on the comments below I've updated the java config.
However I'm still receiving errors and most likely am not understanding the flow and connections between the entities, but the original question has been answered.
#Bean
#Transactional
#InboundChannelAdapter(channel = "dispatchChannel", poller = #Poller(fixedDelay = "2000"))
public MessageSource<?> dispatcher() {
CompositeFileListFilter<File> filters = new CompositeFileListFilter<>();
filters.addFilter(new SimplePatternFileListFilter(sourceFilenamePattern));
//filters.addFilter(persistentFilter());
FileReadingMessageSource source = new FileReadingMessageSource();
source.setAutoCreateDirectory(true);
source.setDirectory(new File(sourceDirectory));
source.setFilter(filters);
return source;
}
#Bean
public MessageChannel fileChannel() {
return new DirectChannel();
}
#Bean
public PublishSubscribeChannel dispatchChannel() {
return new PublishSubscribeChannel();
}
#Autowired
JmsTemplate jmsTemplate;
#Autowired
ConnectionFactory connectionFactory;
#Bean
#Order(1)
#ServiceActivator(inputChannel = "dispatchChannel")
public MessageHandler dispatcherJmsOutboundChannelAdapter(Message<File> message) {
JmsSendingMessageHandler handler = new JmsSendingMessageHandler(jmsTemplate);
handler.setDestinationName("dispatcher.queue");
return handler;
}
#Bean
#Order(2)
#ServiceActivator(inputChannel = "dispatchChannel")
public void removeFile(Message<?> message) {
//message.getHeaders().get(FileHeaders.ORIGINAL_FILE, File.class).delete();
log.info("delete");
}
#Bean
public JmsTransactionManager transactionManager(ConnectionFactory connectionFactory) {
return new JmsTransactionManager(connectionFactory);
}
I'm using spring boot and several starter components, such as activemq. I've added the #Bean for JmsListenerContainerFactory and a #JmsListener, though I'm not sure those are truly necessary.
I couldn't get anything to run until adding #EnableJms to my configuration file as well as #Autowiring the jmstemplate and connectionfactory.
When running, the error I'm receiving now is:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.springframework.messaging.Message] found for dependency
[org.springframework.messaging.Message<?>]:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {}
This one
<int:service-activator input-channel="dispatchChannel" order="2"
output-channel="nullChannel"
expression="headers.file_originalFile.delete()">
is pretty simple in Java:
#ServiceActivator(inputChannel = "dispatchChannel")
public void removeFile(Message<?> message) {
message.getHeaders().get(FileHeaders.ORIGINAL_FILE, File.class).delete();
}
and
<int-jms:outbound-channel-adapter>
is translated to this:
#Bean
#ServiceActivator(inputChannel = "dispatchChannel")
public MessageHandler dispatcherJmsOutboundChannelAdapter() {
JmsSendingMessageHandler handler =
new JmsSendingMessageHandler(new JmsTemplate(this.connectionFactory));
handler.setDestinationName("dispatcher.queue");
return handler;
}
Pay attention to this paragraph in the Reference Manual.
The last piece of jigsaw puzzle is FileWritingMessageHandler
#Bean
public FileWritingMessageHandler fileWritingMessageHandler() {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("headers.file_originalFile.delete()");
FileWritingMessageHandler fileWritingMessageHandler = new FileWritingMessageHandler(expression);
fileWritingMessageHandler.setOutputChannel(new NullChannel());
fileWritingMessageHandler.setDeleteSourceFiles(true);
return fileWritingMessageHandler;
}

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());
}
})

Spring Integration - XML Schema validator - Dynamic schema file selection

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.

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!

JAXB and validation against parts of a schema with schemagen?

A very generalized and simplified scenario description:
Application builds JAXB instances through UI,
The JAXB instances are built in different steps are composed in the end,
As this is done, I must perform validation of the JAXB instances created in each step against the XSD to verify that the UI tier does not build nonsense.
Now the schema defines complex types A, B and C. The schema specifies that XML document must have top level element A and this can contain multiple B and this can optionally include C. I create JAXB instance for B and absolutely want to validate this against the complex type definition in the XSD before nesting it under A. However validation of this will fail if I validate against the entire XSD.
Questions: How to validate JAXB instance against only a part of the XSD from which its class has been generated from?
How about using schemagen to generate schema from the JAXB class which instance I want to validate and then validate against that? Do you think that can work? Any other ideas?
I have no previous experience with schemagen and will start prototyping of this solution soon.
Note: in reality, the schemas are not as simple as in the above example and the solution of creating some always-valid mock of A is not feasible option. Not to mention that this kind of validation will be on hundred places to say the least.
create separate schema for element you want to validate, where this element is on root level.
to workaround missing #xmlRoottag see 101 ways to marshal objects with JAXB
Well it turns out that using xsi:type let's one accomplish this.
http://www.w3.org/TR/xmlschema-1/#xsi_type
While working with xjc and schemagen tools please consider same concept which we use in java. In java, every class is a skeleton and object is an instance. Same as we need to consider XSD as skeleton and XML as instance.
xjc tool:- Xsd to Java Class Or Unmarshaling
Consider the below XSD with namespaces for example. The xjc tool will generate the java class along with package-info and object-factory.
<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ir="http://irctc.org/service" targetNamespace="http://irctc.org/service">
<xsd:element name="Customer" type="ir:CustomerType"/>
<xsd:complexType name="CustomerType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="src" type="xsd:string"/>
<xsd:element name="dest" type="xsd:string"/>
<xsd:element name="price" type="xsd:float" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
Use below command
D:\>xjc Customer.xsd
parsing a schema...
compiling a schema...
org\irctc\service\CustomerType.java
org\irctc\service\ObjectFactory.java
org\irctc\service\package-info.java
Note :- If you have multiple java class then you can use jaxb.index file instead of using ObjectFactory.java.
schemagen tool:- Java Class to Xsd Or Marshaling
Let us suppose we want to generate the Xsd file using java class then first we need to create the ObjectFactory.java Or jaxb.index file and package-info.java in the same package where we have the CustomerType.java.
package org.irctc.service;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
#XmlRootElement("Customer")
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "CustomerType", propOrder = {
"name",
"src",
"dest",
"price"
})
public class CustomerType {
#XmlElement(required = true)
protected String name;
#XmlElement(required = true)
protected String src;
#XmlElement(required = true)
protected String dest;
protected Float price;
public String getName() {
return name;
}
public void setName(String value) {
this.name = value;
}
public String getSrc() {
return src;
}
public void setSrc(String value) {
this.src = value;
}
public String getDest() {
return dest;
}
public void setDest(String value) {
this.dest = value;
}
public Float getPrice() {
return price;
}
public void setPrice(Float value) {
this.price = value;
}
}
Use below command
D:\>schemagen org.irctc.service.CustomerType
Note: Writing D:\schema1.xsd
The above command will generate the xsd file. The package of java class will considered as xsd namespaces.
The above details are only for understanding marshaling and unmarshaling process using tools in Jax-B API.
For more details , check below examples
Jax-B Hello World Example
Java-XML mapping with Jax-B 2.0

Resources