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!
Related
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;
}
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());
}
})
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.
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
1) Is there a way to unmarshall MTOM message using Camel Spring-WS component?
2) I tried with Camel JAXB dataformat. It didn't work. Datahandler doesn't have any content. Does JAXB dataformat support MTOM?
<dataFormats>
<jaxb id="jaxb" contextPath="com.mycompany.hr.jaxb"/>
</dataFormats>
<route>
<from uri="spring-ws:rootqname:{http://mycompany.com/hr/schemas}HolidayRequest?endpointMapping=#endpointMapping" />
<unmarshal ref="jaxb"/>
<process ref="testProcessor" />
</route>
3) I thought MTOM is not enabled in JAXB dataformat. So I created a custom dataformat using MTOM enabled JAXB2Marshaller. But still facing the same issue.
import java.io.InputStream;
import java.io.OutputStream;
import javax.xml.transform.Source;
import org.apache.camel.Exchange;
import org.apache.camel.spi.DataFormat;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
public class MtomDataFormat implements DataFormat {
public void marshal(Exchange arg0, Object arg1, OutputStream arg2)
throws Exception {
// TODO Auto-generated method stub
}
public Object unmarshal(Exchange exchange, InputStream is) throws Exception {
Source source = exchange.getContext().getTypeConverter().mandatoryConvertTo(Source.class, is);
Jaxb2Marshaller mar = new Jaxb2Marshaller();
mar.setContextPath("com.mycompany.hr.jaxb");
mar.setMtomEnabled(true);
return mar.unmarshal(source);
}
}
Spring configuration
<bean id="endpointMapping"
class="org.apache.camel.component.spring.ws.bean.CamelEndpointMapping">
</bean>
<bean id="testProcessor" class="TestProcessor" />
<bean id="mtomDataFormat" class="MtomDataFormat" />
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="spring-ws:rootqname:{http://mycompany.com/hr/schemas}HolidayRequest?endpointMapping=#endpointMapping" />
<unmarshal ref="mtomDataFormat"/>
<process ref="testProcessor" />
</route>
</camelContext>