Implement CXF client to read LIST from a REST server - web-services

I am trying to implement a rest web service using Apache CXF and I want to return list of object as a response from server. So i have used generic entity to wrap my list on a server and everything is fine when I enter path from browser. It prints XML representation of object because I have used Jackson JAX-B but when i try to use JAX-RS client. I am getting an exception.
Exception in thread "main" javax.ws.rs.client.ResponseProcessingException: Problem with reading the data, class XYZ, ContentType: /.
at org.apache.cxf.jaxrs.impl.ResponseImpl.reportMessageHandlerProblem(ResponseImpl.java:433)
at org.apache.cxf.jaxrs.impl.ResponseImpl.doReadEntity(ResponseImpl.java:378)
at org.apache.cxf.jaxrs.impl.ResponseImpl.readEntity(ResponseImpl.java:325)
at org.apache.cxf.jaxrs.impl.ResponseImpl.readEntity(ResponseImpl.java:313)
at XYZ.ABC()
at XYZ.ABC()
Caused by: javax.ws.rs.core.NoContentException: Message body is empty
at org.apache.cxf.jaxrs.provider.AbstractJAXBProvider.reportEmptyContentLength(AbstractJAXBProvider.java:276)
at org.apache.cxf.jaxrs.provider.JAXBElementProvider.readFrom(JAXBElementProvider.java:166)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1325)
at org.apache.cxf.jaxrs.impl.ResponseImpl.doReadEntity(ResponseImpl.java:369)
... 4 more
I have written a following client code to get data from server
final Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target(URI.create(PATH));
Response response = webTarget.request(MediaType.APPLICATION_XML).get();
List<ABC> obj = response.readEntity(new GenericType<List<ABC>> (ABC.class){});
But Apart from it I have tried many code to implement CXF client and get data from server but I am getting a same exception almost all the time. I have tried JAXRSCLIENTFactory also to implement client but the same exception.

I ran into the same problem in unmarshalling using the CXF client. Here is how I did it:
Read the response into a String.
Used Gson to convert from string to list of objects.
Note: You will need a wrapper class for your list of objects.
Example:
If the server returns a list of products, Here is how to unmarshall the list:
Create a wrapper class
public class ProductList {
private List<Product> products;
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
}
Code to unmarshall
String responseBody = response.readEntity(String.class);
ProductList productList = new Gson().fromJson(responseBody, ProductList.class);
if(productList.getProducts() != null)
return productList.getProducts();

Related

Camel Restlet sends response to client asynchronously

I have a scenario that I'm using camel-restlet component to receive post requests, I'm forwarding these requests to an external web service, after receiving the response code from the external service, I need to add this response code to my own response to the client asynchronously.
Im trying to save the response object to a hashMap where key is an unique serial number generated based on the request content, once upon receiving the response from external web service, I can retrieve the response object from the hashMap using this unique key. Seems like restlet saves the response to exchange.getOut() message and sends back to the client synchronously which is not something I want. Not setting an out message would give me a nullPointerException.
route Class:
public class ReceiveRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("restlet:http://localhost:8083/api/atmp?restletMethod=post")
.to("activemq:queue:requestReceiveQueue");
from("activemq:queue:requestReceiveQueue")
.process(new RequestProcessor())
.to("activemq:queue:requestSendQueue");
from("activemq:queue:requestSendQueue")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.bean("responseProcessor");
}
}
requestProcessor class:
public class RequestProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
byte[] bytes = (byte[])message.getBody();
String body = new String(bytes);
String atmpId = GUIDGenerator.generateAtmpSerialNumber();
String terIndentifier = GUIDGenerator.generateTerminalIdentifier(body);
MapLookupHelper.insertResponse(atmpId, terIndentifier, exchange);
Map<String, Object> messageMap = new HashMap<String, Object>();
messageMap = FormatUtil.parseJson(body);
messageMap.put("ATMPId", atmpId);
exchange.getIn().setBody(messageMap.toString());
}
}
responseProcessor class
#Component
public class ResponseProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String responseCode = in.getHeader(Exchange.HTTP_RESPONSE_CODE).toString();
String body = in.getBody().toString();
Map<String, Object> resMap = new HashMap<String, Object>();
resMap = FormatUtil.parseJson(body);
String atmpId = resMap.get("ATMPId").toString();
Exchange ex = MapLookupHelper.getOutMessage(atmpId);
ex.getOut().setHeader("HostResponseCode", responseCode);
ex.getOut().setBody(resMap.toString());
}
}
I'm new to Apache Camel and would like to know if restlet is the right way to go, if not, any suggestion on how I can handle async responses to client in Camel? Is AsyncProcessor only solution to such scenario?
I think it's not issue of restlet. Your exchange pattern is InOut, that's why all jms-endpoint's waiting synchronously result of your .bean("responseProcessor").
Even if you change pattern to InOnly your client will not receive response asynchronously. I think you should make another route's architecture, like below:
from("restlet:http://localhost:8083/api/atmp_asyncRequest?restletMethod=post")
.process(exchange -> {
exchange.setProperty("uniqueRequestId", GUIDGenerator.generateAtmpSerialNumber());
})
.inOnly("seda:requestReceiveQueue")// here starts async processing of your request
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// make here response for client with generated request id
});
from("seda:requestReceiveQueue")
.process(exchange -> {
// prepare\process request if need
})
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// save somewhere prepared response for client bound to generated request id
});
from("restlet:http://localhost:8083/api/atmp_getResponse?restletMethod=post")
.process(exchange -> {
String requestId = ;//extract request id from client's request
Object body = ;//find response that you saved asynchronously by extracted request id
// if response not found, then async processing request not ended, so you should send message to client to continue polling
exchange.getIn().setBody(body);
});
That will work if you haven't callback server for async responses on client's side.
Also you can use Seda component instead of jms, for queueing tasks between routes.

RESTFUL Web Service - List

I have a client application requesting a list of channels from a webservice. Is it possible to take the "response" from the web service and store it in an ArrayList?
Meaning if I wanted to store a list of channels for example, it would normally come from the web service as a response, typically from ResponseBuilder.
And I want to store it in an ArrayList from the client, like List.
How would I go about doing that?
You can use TypeReference to instantiate your Channel object list, here is an example:
import com.fasterxml.jackson.core.type.TypeReference;
public class ChannelClient {
public void getChannels() {
Response serviceResponse = client.target("http://your_service_url/channels/").
request(MediaType.APPLICATION_JSON).get(Response.class);
String responseString = serviceResponse.readEntity(String.class);
List<Channel> list = new ObjectMapper().readerFor(new TypeReference<List<Channel>>() {
}).readValue(responseString);
}
}
Make sure to have Jersey JSON Jackson jar in your dependencies, you can get it from here
https://mvnrepository.com/artifact/org.glassfish.jersey.media/jersey-media-json-jackson/2.26-b07
EDIT: In case you want to consume MediaType.TEXT_PLAIN response, you will just change the request method argument to your specified type like this:
Response serviceResponse = client.target("http://your_service_url/channels/").
request(MediaType.TEXT_PLAIN).get(Response.class);

Forwarding requests to external endpoint from an CFX service

Currently I am fighting with the following problem:
I need to forward SOAP requests to an external service in special cases (decision based on tenantId provided in the SOAP message). I created an interceptor for this task to extract tenantId from the message request, get assignment (each tenantId is assigned to its own service instance running on a different server) and if no assignment is made, I need to process the request just as normal.
Currently I implemented on this way: I create HttpUrlConnection in the interceptor and forward the request to an external endpoint (in case there is an assignment) and take the outputStream of the response and send the response over HttpServletResponse.getOutputStream etc...
I also need to consider that the interceptor be used with various service (tenantId must be provided in the SOAP request).
I also read about Provider and Dispatch objects not sure how this should work.
Is there any way to get target service and port (QNames) from the incoming message?
I cannot use Camel at the moment (only CXF is allowed).
Maybe you can try something like this :
/** Your interceptor */
public void handleMessage(SoapMessage msg) throws Fault {
Exchange exchange = msg.getExchange();
Endpoint ep = exchange.get(Endpoint.class);
// Get the service name
ServiceInfo si = ep.getEndpointInfo().getService();
String serviceName = si.getName().getLocalPart();
XMLStreamReader xr = msg.getContent(XMLStreamReader.class);
if (xr != null) { // If we are not even able to parse the message in the SAAJInInterceptor (CXF internal interceptor) this can be null
// You have the QName
QName name = xr.getName();
SOAPMessage msgSOAP = msg.getContent(SOAPMessage.class);
// Read soap msg
if (msgSOAP != null) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
msgSOAP.writeTo(byteArrayOutputStream);
String encoding = (String) msg.get(Message.ENCODING);
String xmlRequest = new String(byteArrayOutputStream.toByteArray(), encoding);
}
// Forward to external service with JAX-RS implementation
Client client = ClientBuilder.newClient()
.target("http://your-target")
.path("/custom-path")
.request()
.post(Entity.entity(xmlRequest, MediaType.APPLICATION_XML));
}
}
Hope this help.

communication between 2 rest web services

I have many rest controllers in my project (developed with spring MVC) and i would like to make them communicate between each other.
what's the best way to make two spring REST controllers exchange messages ?
Supposed you have 2 controllers:
#RestController
#RequestMapping("/first")
public class FirstController(){
// your code here
}
and
#RestController
#RequestMapping("/second")
public class SecondController(){
// supposed this is your FirstController url.
String url = "http://localhost:8080/yourapp/first";
// create request.
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(url);
// execute your request.
HttpResponse response = client.execute(request);
// do whatever with the response.
}
For reference, have a look at this: http://www.mkyong.com/java/apache-httpclient-examples/
Used library: https://hc.apache.org/httpcomponents-client-ga/

Cannot call web api 2 post method with int parameter in URL in Unit Test using Http server

Please ignore the spelling mistake, I cannot copy code so I have typed the whole thing and changed name of controller and method.
WEB API 2
Controller:
// Controller name is Test
public HttpResponseMessage Method1(int param1) // Post method
{
// return string
}
If I create an object of controller in test case then it is working fine. But if I want to test in localhost using following code:
Unit Test:
public void Method1Test()
{
HttpResponseMessage response;
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
HttpServer server = new HttpServer(config);
using(var client = new HttpClient(server))
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5022/api/test?param1=1");
request.Content = new ObjectContent<int>(param1, new JsonMediaTypeFormatter());
response = client.SendAsync(request, CancellationToken.None).Result;
};
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
Now, my test case is failing. I used the same code in different project and it worked. May be it is the way I am trying to call Post method. Is this the right way to call post method with Int parameter in URL?
In help page, under API column it shows:
POST api/test/param1={param1}
Also I have put some stop point in actual service I am cursor is not stopping at that point. Why?
If I want to call the same service from browser, what URL should I pass? Is it -
http://localhost:5022/api/test?param1=1
Or something else?
I figured it out. Following is the correct unit test method but this has some extra information which I have not provided earlier i.e., passing object as an input for the service.
private void Method1Test(ObjectClass obj)
{
HttpResponseMessage response = null;
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
HttpServer server = new HttpServer(config);
using (var client = new HttpClient(server))
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://localhost:5022/api/test/1");
request.Content = new ObjectContent<ObjectClass>(obj, new JsonMediaTypeFormatter());
response = client.SendAsync(request, CancellationToken.None).Result;
};
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}
So the correct URL that I was looking for was
http://localhost:5022/api/test/1
Sorry, It took long to post this answer. This method is working like a charm for more then 2 years.