Unmarshalling the XML using JAXB - spring-integration

Actually I am very new at spring and currently due to some requirement, I am working with spring-integration, I have made few JAXB classes to convert the data into XML and have to send it through webservices but in response I am getting the XML back with some new element, I want to know how to unmarshall the new XML with same JAXB classes that I have made?

I use the following component to do that (java configuration):
#Bean
public Jaxb2Marshaller jaxb2Marshaller() throws Exception {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
/* Packages with root elements (#XmlRootElement). Your JAXB classes */
marshaller.setContextPaths("...");
return marshaller;
}
#Bean
#ServiceActivator(inputChannel = "toWebServiceChannel")
public MessageHandler wsGateway() throws Exception {
ConfigWebServiceURLProvider provider = new ConfigWebServiceURLProvider(isHttps, host, port, endpoint);
/* marshaller and unmarshaller could be the same */
MarshallingWebServiceOutboundGateway gw = new MarshallingWebServiceOutboundGateway(url, jaxb2Marshaller(), jaxb2Marshaller());
gw.setOutputChannelName( "fromWebServiceChannel" );
return gw;
}

Related

How to Read Files and trigger http rest multipart endpoint using Spring Integration

I am following this spring integration example - https://github.com/iainporter/spring-file-poller
#Bean
public IntegrationFlow writeToFile(#Qualifier("fileWritingMessageHandler") MessageHandler fileWritingMessageHandler) {
return IntegrationFlows.from(ApplicationConfiguration.INBOUND_CHANNEL)
.transform(m -> new StringBuilder((String)m).reverse().toString())
.handle(fileWritingMessageHandler)
.log(LoggingHandler.Level.INFO)
.get();
}
#Bean (name = FILE_WRITING_MESSAGE_HANDLER)
public MessageHandler fileWritingMessageHandler(#Qualifier(OUTBOUND_FILENAME_GENERATOR) FileNameGenerator fileNameGenerator) {
FileWritingMessageHandler handler = new FileWritingMessageHandler(inboundOutDirectory);
handler.setAutoCreateDirectory(true);
handler.setFileNameGenerator(fileNameGenerator);
return handler;
}
Controller example
#PostMapping(value ="/data/{id}")
public String load( #RequestParam("jsonFile") MultipartFile jsonFile,
#PathVariable("id") Long id) throws JsonMappingException, JsonProcessingException{
//some business logic
return "Controller is called";
}
Instead of simple handling, I want to call a Rest endpoint that expects a file.
i.e. calling a rest api in handler similar to fileWritingMessageHandler
https://github.com/spring-projects/spring-integration-samples/blob/261648bed136a076f76ed15b1017f5e5b6d8b9ae/intermediate/multipart-http/src/main/resources/META-INF/spring/integration/http-outbound-config.xml
How can I create Map
Map<String, Object> multipartMap = new HashMap<String, Object>();
multipartMap.put("jsonFile", ????);
and call a getway method like
HttpStatus postMultipartRequest(Map<String, Object> multipartRequest);
To send a multi-part request you need to have a payload as a Map<String, Object>. You can read files from a directory using FileReadingMessageSource and respective poller configuration: https://docs.spring.io/spring-integration/docs/current/reference/html/file.html#file-reading. This one emits messages with java.io.File as a payload. To create a Map for it you just need a simple transformer in Java DSL:
.<File, Map<String, File>>transform(file -> Collections.singletonMap("jsonFile", file))
and then you use standard .handle(Http.outboundChannelAdapter("/data/{id}").uriVariable("id", "headers.someId")): https://docs.spring.io/spring-integration/docs/current/reference/html/http.html#http-java-config

spring-integration-kafka: Annotation-driven handling of KafkaProducerMessageHandler result?

Is there a way to achieve the behavior of the code below using annotation driven code?
#Bean
#ServiceActivator(inputChannel = "toKafka")
public MessageHandler handler() throws Exception {
KafkaProducerMessageHandler<String, String> handler =
new KafkaProducerMessageHandler<>(kafkaTemplate());
handler.setTopicExpression(new LiteralExpression("someTopic"));
handler.setMessageKeyExpression(new LiteralExpression("someKey"));
handler.setSendSuccessChannel(success());
handler.setSendFailureChannel(failure());
return handler;
}
#Bean
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
#Bean
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.brokerAddress);
// set more properties
return new DefaultKafkaProducerFactory<>(props);
}
Can I specify the send success/failure channels using Spring Integration annotations?
I'd like as much as possible to keep a consistent pattern of doing things (e.g., specifying the flow of messages) throughout my app, and I like the Spring Integration diagrams (e.g., of how channels are connected) IntelliJ automatically generates when you configure your Spring Integration app with XML or Java annotations.
No; it is not possible, the success/failure channels have to be set explicitly when using Java configuration.
This configuration is specific to the Kafka handler and #ServiceActivator is a generic annotation for all types of message handler.

Spring Integration Web Service - Jaxb2Marshaller - How I can use SAX Parser?

I am using Jaxb2Marshaller with Spring Integration. I have an inbound gateway web service, when someone call it, it will auto parse into JAXB generated classes.
But when I debug into the source, I see Jaxb2Marshaller using DOM. I thought it would use SAX as for binding XML data into Java object, SAX is faster. Why Jaxb2Marshaller use DOM by default ? How can I configure it to use SAX ?
As I checked the document
The
unmarshaller requires an instance of Source. If the message payload is not an instance of Source,
conversion will be attempted. Currently String, File and org.w3c.dom.Document payloads are
supported. Custom conversion to a Source is also supported by injecting an implementation of a
SourceFactory.
Note
If a SourceFactory is not set explicitly, the property on the UnmarshallingTransformer will
by default be set to a DomSourceFactory
About SourceFactory
http://docs.spring.io/spring-integration/api/org/springframework/integration/xml/source/SourceFactory.html
We can see that currently, it only has DomSourceFactory and StringSourceFactory. There is no SaxSourceFactory.
So we can't use SAX with Jaxb2Marshaller, right ?
Will it have SaxSourceFactory in the future ? or never ?
The weird thing is when I check Jaxb2Marshaller , I see the code already handle SAX
XMLReader xmlReader = null;
InputSource inputSource = null;
if (source instanceof SAXSource) {
SAXSource saxSource = (SAXSource) source;
xmlReader = saxSource.getXMLReader();
inputSource = saxSource.getInputSource();
}
else if (source instanceof StreamSource) {
StreamSource streamSource = (StreamSource) source;
if (streamSource.getInputStream() != null) {
inputSource = new InputSource(streamSource.getInputStream());
}
else if (streamSource.getReader() != null) {
inputSource = new InputSource(streamSource.getReader());
}
else {
inputSource = new InputSource(streamSource.getSystemId());
}
}
So, the final question is CAN I configure use Spring Integration Web Service with JAXB with SAX ? Am I missed something?
Here is my configurations:
<ws:inbound-gateway id="inbound-gateway" request-channel="RequestChannel" reply-channel="ResponseChannel"
marshaller="marshaller" unmarshaller="marshaller" />
<int:channel id="RequestChannel" />
<int:channel id="ResponseChannel" />
<bean id="marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.example.webservice.api"/>
</bean>
Thank you and best regards,
Nha Nguyen
I'm using WebServiceGatewaySupport classes and tried adding a AxiomSoapMessageFactory as Bean to the application context, until I found out that WebServiceTemplate does not load the WebServiceMessageFactory from the application context. So I ended up with adding a constructor:
public class SomeServiceImpl extends WebServiceGatewaySupport implements SomeService {
public SomeServiceImpl(WebServiceMessageFactory messageFactory) {
super(messageFactory);
}
}
and building the service myself with a #Configuration class:
#Configuration
public class WebServicesConfiguration {
private WebServiceMessageFactory webServiceMessageFactory = new AxiomSoapMessageFactory();
#Bean
public SomeService someService() {
return new SomeServiceImpl(webServiceMessageFactory);
}
}
Try to configure AxiomSoapMessageFactory bean with the name MessageDispatcherServlet.DEFAULT_MESSAGE_FACTORY_BEAN_NAME.
By default it is SaajSoapMessageFactory which does exactly this before unmarshalling:
public Source getPayloadSource() {
SOAPElement bodyElement = SaajUtils.getFirstBodyElement(getSaajBody());
return bodyElement != null ? new DOMSource(bodyElement) : null;
}
where Axiom is based on STaX:
XMLStreamReader streamReader = getStreamReader(payloadElement);
return StaxUtils.createCustomStaxSource(streamReader);
And class StaxSource extends SAXSource {

Serializing with JAXB and the Any (without ElementNS)

I'm looking a best practices to marshal a XMLAnyElement that can handle String, Long etc... I found a Serializing with JAXB and the Any, but I need avoid ElementNS and resolve the case attached
Is DOMHandler the best way?
public static void main(String[] args) throws JAXBException {
JAXBContext jc = JAXBContext.newInstance(Payload.class, Foo.class, ObjectFactory.class);
Payload payload = new Payload();
payload.any = new ArrayList<>();
payload.any.add(new Bar());
payload.any.add(new Foo());
payload.any.add("pepe");
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(payload, System.out);
}
Resolved, see my PoC on github..
https://github.com/franciscophilip/jaxb-payload-poc/

org.apache.camel.InvalidPayloadException: No body available of type error thrown while unMarshalling Jaxb Object

I am sending JAXB Object to Rabbit MQ via Java.
JAXBContext jaxbContext = JAXBContext.newInstance(MyDTO.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
java.io.StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(deliveryrequest, sw);
ConnectionFactory factory = new ConnectionFactory() ;
//TODO change the hardcoding to the properties file
factory.setHost("rabbitmq.host.net");
factory.setUsername("user");
factory.setPassword("pass");
Channel channel ;
Connection connection;
Map<String, Object> args = new HashMap<String, Object>();
String haPolicyValue = "all";
args.put("x-ha-policy", haPolicyValue);
connection = factory.newConnection();
channel = connection.createChannel();
//TODO change the hardcoding to the properties file
channel.queueDeclare("upload.com.some.queue", true, false, false, args);
//TODO change the hardcoding to the properties file
channel.basicPublish("com.some.exchange", "someroutingKey",
new AMQP.BasicProperties.Builder().contentType("text/plain")
.deliveryMode(2).priority(1).userId("guest").build(),
sw.toString().getBytes());
I am using Camel in different application to read this.
<camel:route id="myRoute">
<camel:from uri="RabbitMQEndpoint" />
<camel:to uri="bean:MyHandler" />
</camel:route>
Handler is using the Jaxb object redone in camel side
#Handler
public void handleRequest(MyDTO dto) throws ParseException {
I am getting the error which I did not expect to get.
Caused by: org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type: byte[] to the required type: com.mycompany.MyDTO with value [B#1b8d3e42
at org.apache.camel.impl.converter.BaseTypeConverterRegistry.mandatoryConvertTo(BaseTypeConverterRegistry.java:181)
at org.apache.camel.impl.MessageSupport.getMandatoryBody(MessageSupport.java:99)
Solution is appreciated.
Your message body type from rabbit is a byte[], and you want to call a bean which as MyDTO type. You have a type mismatch. And Camel cannot find a type converter that can convert the message body from byte[] to your MyDTO type.
Is the byte[] data from Rabbit in XML format? And do you have JAXB annotations on MyDTO classes, so you could use JAXB to marshal that from xml to Java ?
It is Jaxb object. I was under impression that if i move the publisher too Camel. Things will be sorted out.
I changed the publisher to the following.
#EndpointInject(context = "myContext" , uri = "direct:myRoute")
private ProducerTemplate sendAMyContext;
And in method I called
#Override
public MyResponse putMyCall(
MyRequest myrequest) {
sendMyContext.sendBody(myrequest);
My camel route is simple one
<camel:camelContext id="MyContext" autoStartup="true" xmlns="http://camel.apache.org/schema/spring">
<camel:endpoint id="myQueue" uri="${my.queue.1}" />
<camel:route>
<camel:from uri="direct:myRoute"/>
<camel:to uri="bean:mySendHandler"/>
<camel:convertBodyTo type="String"></camel:convertBodyTo>
<camel:to uri="ref:myQueue" />
</camel:route>
</camel:camelContext>
I added the handler to convert the Jaxb object to string(because of error)
public class MySendHandler {
#Handler
public String myDelivery( MyRequest myRequest) throws ParseException, JAXBException {
JAXBContext jaxbContext = JAXBContext.newInstance(MyRequest.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
// output pretty printed
jaxbMarshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
java.io.StringWriter sw = new StringWriter();
jaxbMarshaller.marshal(attributedDeliveryRequest, sw);
return sw.toString();
}
Still the same error
[SpringAMQPConsumer.SpringAMQPExecutor-1] WARN org.springframework.amqp.support.converter.JsonMessageConverter (JsonMessageConverter.java:111) - Could not convert incoming message with content-type [text/plain]
SpringAMQPConsumer.SpringAMQPExecutor-1] ERROR org.apache.camel.processor.DefaultErrorHandler (MarkerIgnoringBase.java:161) - Failed delivery for (MessageId: ID-Con ExchangeId: ID-65455-1380873539452-3-2).
Exhausted after delivery attempt: 1 caught: org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[Message: <XML>]
org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[Message: <xml>
.....
Caused by: org.apache.camel.InvalidPayloadException: No body available of type: MyDTO but has value: [B#7da255c of type: byte[] on: Message: <xml>
. Caused by: [org.apache.camel.NoTypeConversionAvailableException - No type converter available to convert from type: byte[] to the required type: MyDeliveryDTO with value [B#7da255c]
at org.apache.camel.impl.MessageSupport.getMandatoryBody(MessageSupport.java:101)
at org.apache.camel.builder.ExpressionBuilder$38.evaluate(ExpressionBuilder.java:934)
... 74 more
Caused by: org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type: byte[] to the required type: MyDTO with value [B#7da255c
at org.apache.camel.impl.converter.BaseTypeConverterRegistry.mandatoryConvertTo(BaseTypeConverterRegistry.java:181)
at org.apache.camel.impl.MessageSupport.getMandatoryBody(MessageSupport.java:99)
... 75 more
My receiving camel route is as below
<camel:route id="processVDelivery">
<camel:from uri="aVEndpoint" />
<camel:to uri="bean:myDataHandler" />
</camel:route>
I added
<camel:convertBodyTo type="String"></camel:convertBodyTo>
to it and it gave me error that it cannot convert it from string to the object
adding this
<camel:unmarshal></camel:unmarshal>
-------------------------
Answer to the question will be all your mashaller have to be part of classpath
---------------------------------
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-jaxrs</artifactId>
<version>${jackson.version}</version>
</dependency>
------- Also you can the custom marshaller using dataformat
<camel:dataFormats>
<camel:jaxb id="name" contextPath="com.something"/>
</camel:dataFormats
make sure that you keep jaxb.index file in com.something package with the root level Jaxb object name
Using Camel Rest with Spring boot
rest("/authenticate")
.post()
.route()
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.process(exchange -> {
org.apache.camel.TypeConverter tc = exchange.getContext().getTypeConverter();
String str_value = tc.convertTo(String.class, exchange.getIn().getBody());
System.err.println(str_value);
exchange.getOut().setBody(str_value);
})
//.removeHeader(Exchange.HTTP_URI)
.to("http://localhost:8082/authenticate?bridgeEndpoint=true")
//.convertBodyTo(DTO.class)
.log("Boo!")
.end()
.endRest();

Resources