Getting the actual class of a message body - java

I'm passing a List of different objects to a camel route. I would like the route to split the body into one object per message and put the class of the body in a header (using a processor).
from("direct:in")
.split(body())
.process(new JmsTypeHeaderProcessor(body().getClass().getName()))
.to("mock:out");
I'm trying it like this...
#Produce(uri = "direct:in") private ProducerTemplate template;
#EndpointInject(uri = "mock:out") private MockEndpoint endpoint;
#Test
public void testRoute() throws Exception {
List<Object> list = new ArrayList<>();
list.add("String");
list.add(Integer.valueOf(1));
list.add(Boolean.FALSE);
template.sendBody(list);
for (Exchange ex : endpoint.getExchanges()) {
System.out.println("JMSType=" + ex.getIn().getHeader("JMSType"));
}
}
When I run that I find I actually have the headers
JMSType=org.apache.camel.builder.ValueBuilder
JMSType=org.apache.camel.builder.ValueBuilder
JMSType=org.apache.camel.builder.ValueBuilder
whereas I expected, and would like
JMSType=java.lang.String
JMSType=java.lang.Integer
JMSType=java.lang.Boolean
What is needed to get the class of the actual body?
BTW. I can see that log("body.class") returns what I want but I have not been able to follow how it works or adapt it for my needs.

The Camel routes are designed in the route builder and the code is run once, to setup the routes.
So this code
.process(new JmsTypeHeaderProcessor(body().getClass().getName()))
Is invoked once, and body().getClass() returns the ValueBuilder as that is what is used at design time in the DSL to specify body etc.
If you want to access the runtime message body, then get that from the Exchange from the process method of your processor. That is the runtime message and then you can get the body.

Related

Java Spring Boot Webflux - Mono response when there is no http body, but just http status

Small question regarding Spring Boot Webflux 2.5.0 and how to deal with a http response without body.
By "without body" I mean:
For instance, a web application I consume the rest API and have no control returns:
HTTP status code 200
HTTP body {"foo": "bar"}
With Spring Webflux, we can easily write something like:
public Mono<FooBar> sendRequest(SomeRequest someRequest) {
return webClient.mutate()
.baseUrl("https://third-party-rest-api.com:443")
.build()
.post()
.uri(/someroute)
.body(BodyInserters.fromValue(someRequest))
.retrieve().bodyToMono(FooBar.class);
}
public class FooBar {
private String foo;
//getter setters
}
In order to get the POJO corresponding to the http body.
Now, another third party API I am consuming only return HTTP 200 as status response.
I would like to emphasize, there is no HTTP body. It is not the empty JSON {}.
Hence, I am a bit lost, and do not know what to put here. Especially with the goal of avoiding the mono empty.
public Mono<WhatToPutHerePlease> sendRequest(SomeRequest someRequest) {
return webClient.mutate()
.baseUrl("https://third-party-rest-api.com:443")
.build()
.post()
.uri(/someroute-with-no-http-body-response)
.body(BodyInserters.fromValue(someRequest))
.retrieve()
.bodyToMono(WhatToPutHerePlease.class);
}
Any help please?
Thank you
Hence, I am a bit lost, and do not know what to put here.
The response is empty, so there's nothing for your webclient to parse and return a value. The resulting Mono is thus always going to be empty, whatever generic type you use.
We have a special type that essentially says "this will always be empty" - Void (note the capital V.) So if you want to return an empty Mono, keeping the rest of the code the same, that's the type you should use.
Alternatively, if you don't want to return an empty publisher, then you might consider using .retrieve().toBodiLessEntity() instead of .retrieve().bodyToMono() - this will return a Mono<ResponseEntity<Void>>. The resulting body will obviously still be empty, but the response entity returned will enable you to extract information such as the response code & header information, should that be useful.
toBodylessEntity() seems to suit your needs:
It returns a Mono<ResponseBody<Void>>.
With a (void rest) controller like:
#RestController
#SpringBootApplication
public class Demo {
public static void main(String[] args) {
SpringApplication.run(Demo.class, args);
// ...
}
#GetMapping("/")
public void empty() {
}
}
and a:
public class ReactiveClient {
Mono<ResponseEntity<Void>> mono = WebClient.create("http://localhost:8080")
.get()
.retrieve()
.toBodilessEntity();
// blocking/synchronous
public ResponseEntity<Void> get() {
return mono.block();
}
}
We can:
ReactiveClient reactiveClient = new ReactiveClient();
System.out.println(reactiveClient.get()); // or something else

Translating from WSO2 to camel with Java DSL: How to forward with URI pattern

I'm removing WSO2 from our stack and I have to write in Camel Java DSL the endpoints that were implemented in WSO2.
In WSO2 we had an endpoint as below:
<resource methods="OPTIONS GET" uri-template="/request/{data}" inSequence="requestreset"/>
<http method="GET" uri-template="http://127.0.0.1/index.php?_q=requestreset&data={uri.var.data}"/>
My code in Java Camel's Router is:
public class DefaultRouteBuilder extends RouteBuilder {
private HashMap<String, String> routeCorresponding = new HashMap();
#Override
public void configure() throws Exception {
routeCorresponding.put("reset/request/{data}", "http://127.0.0.1/index.php?_q=requestreset&data={data}");
for (Map.Entry<String, String> pair : routeCorresponding.entrySet()) {
String url = pair.getKey();
String target = pair.getValue();
String resultTarget = target.contains("?") ? target + "&bridgeEndpoint=true" : target + "?bridgeEndpoint=true";
fromF("servlet:"+ url +"?matchOnUriPrefix=true")
.log("Request: ${in.header."+ Exchange.HTTP_METHOD +"} to ${in.header."+ Exchange.HTTP_URI +"}")
.toF(resultTarget);
}
}
}
But it doesn't work as I would want it because when I make a request to tomcat.myserver.com:8080/camel-example-servlet/reset/request/blablablablabla I get a response this:
org.apache.camel.http.common.HttpOperationFailedException: HTTP operation failed invoking http://127.0.0.1/index.php/reset/request/blablablablabla?_q=requestreset&data=%7Bdata%7D with statusCode: 404
Instead of http://127.0.0.1/index.php/reset/request/blablablablabla?_q=requestreset&data=%7Bdata%7D, I would like the following request to be on http://127.0.0.1/index.php?_q=requestreset&data=blablablablabla
Is it possible to achieve in Camel/Java DSL that? Basically what WSO2 was implementing with the URI template and the curly brackets around fields?
You can absolutely achieve that - but your {data} block is stored as a header, so you need to refer to it as ${header.data} in your target URI.
Here's an example using the REST DSL:
restConfiguration().component("servlet");
rest("/reset/request/{data}")
.get()
.route()
.log("Received request...")
.setHeader(Exchange.HTTP_PATH, simple("/index.php"))
.setHeader(Exchange.HTTP_QUERY, simple("_q=requestreset&data=${header.data}"))
.to("http://localhost:8080?bridgeEndpoint=true");
Edit based on your question below. Alternatively, if you need to proxy hundreds of URLs, instead of creating hundreds of routes, you could just create one single route which proxies them all and implement your routing logic in a Processor, e.g.:
from("servlet:?matchOnUriPrefix=true")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
// set your target URI here, look it up from the HashMap, etc.
}
})
.to("http://localhost:8080?bridgeEndpoint=true");

Conditional handler in integration flow

I'm developing web hook notification service that allows clients to subscribe/unsubscribe to messages flowing through middle-ware and get notified about the messages (according to provided criteria) by posting the message payload to provided callback URL.
The message delivery looks like this :
flowBuilder
.enrichHeaders(e->e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,true))
.handle(Http.outboundChannelAdapter(message-> {
String subscriptionId = message.getHeaders().get(SUBSCRIPTION_ID_HEADER_NAME, String.class);
return subscriptionsStore.get(UUID.fromString(subscriptionId)).getCallbackUrl(); //potential NPE if subscription was removed
},restTemplateBuilder.build())
.get()
As you can see, the implementation of uriFunction fetches the callback URL from subscriptionsStore by subscription id(part of the message header).
My question is about the situation where the client has already unsubscribed with his subscription id and I'm after the conditional handler.
I know that I can filter messages with subscription id are still present in subscription store, but this is not the proper solution, as client might unsubscribe between filter and handle operations still causing NRE in uriFunction.
Another solution is to enreach the header with callback URL and filter then by header having non-empty value, but I don't want to compromise neither header nor payload of original message.
I can think about another approach: to calculate the URI of non-existing subscriptions as some static value and add interceptor to RestTempalte to simulate the HTTP OK replay for this specific URI value...
So my question is about the proper way to handle this case by using the standard EIP or another Spring integration feature I'm not aware about...
Thanks
UPDATE
I've added the DedicatedMessage class that holds the context :
public static class DedicatedMessage extends GenericMessage<Object> implements MessageDecorator{
#Getter
#Transient
private Subscription subscription;
public DedicatedMessage(Subscription subscription,Object payload,Map<String,Object> headers) {
super(payload,headers);
this.subscription = subscription;
}
#Override
public Message<?> decorateMessage(Message<?> message) {
return new DedicatedMessage (subscription,message.getPayload(),message.getHeaders());
}
}
and changed the flow as :
flowBuilder
.enrichHeaders(e->e.header(MessageHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE,true))
.handle((payload, headers) -> {
String subscriptionId = (String) headers.get(SUBSCRIPTION_ID_HEADER_NAME);
Subscription subscription = subscriptionsCache.get(UUID.fromString(subscriptionId));
return Optional.ofNullable(subscription)
.map(s-> new DedicatedMessage(s, payload,headers))
.orElse(null);
})
.handle(Http.outboundChannelAdapter(message->((DedicatedMessage)message).getSubscription().getCallbackUrl()
,restTemplateBuilder.build())
.get()
Any issues with this apporach ?
I am not sure what your NRE abbreviation means, but you could throw a NoSuchSubscriptionException from your subscriptionsStore.get() method and then ignore/report that exception in an ExpressionEvaluatingRequestHandlerAdvice applied to the outbound channel adapter in its endpoint.advice() chain.

Adding Header To A SOAP Call (Java equavalent in C#)

The SOAP API I am intending to use has given a working example in Java. In every request to the API one should add three values to the header (I just guess they are a domain, a password and api key). To this aim we override the org.apache.axis.client.Stub like this:
public class SeveraApiStubBase extends org.apache.axis.client.Stub {
#Override
public org.apache.axis.client.Call _createCall() throws ServiceException {
org.apache.axis.client.Call _call = super._createCall();
_call.addHeader(new org.apache.axis.message.SOAPHeaderElement(
"http://something.somethingelse.com/", "WebServicePassword", "API_KEY"));
return _call;
}
}
And then we run the method with the provided header.
I was wondering what the equivalent is in C#.
Update: The use of the IClientMessageInspector class
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
HttpRequestMessageProperty httpRequestMessage;
object httpRequestMessageObject;
if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name, out httpRequestMessageObject))
{
httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty;
if (string.IsNullOrEmpty(httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER]))
{
httpRequestMessage.Headers[USER_AGENT_HTTP_HEADER] = this.m_userAgent;
}
}
else
{
httpRequestMessage = new HttpRequestMessageProperty();
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, this.m_userAgent);
request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage);
}
return null;
}
Normally you would use WCF if using C# rather than Axis. It is a little different of an approach than Axis.
Assuming you're making a client consuming an existing WSDL, you would start by using svcutil to generate your service contract code. Here is a link that describes this part. The example they give is a service with primitive inputs to all operations, so it doesn't show generation of complex type classes.
You can then use message inspectors to intercept the outgoing request and add a header. The IClientMessageInspector interface has the method BeforeSendRequest that passes a Message class as an argument. The Message class has a Headers collection where you can add whatever headers you need.

How can I override the decisions made during JAX-RS Content Negotiation?

I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.

Categories

Resources