Consuming JSON with Jersey 2.3 throws XML exception - json

I'm trying to make Jersey 2.3 + Moxy work with my custom objects. Everything is ok when I'm producing JSON from those objects, but refuses to work when I want to consume them by POST. Code first:
Custom object:
#XmlRootElement
public class ContentAction extends Action {
private String contentType;
private Integer contentLength;
public ContentAction() {
setType(ActionType.CONTENT);
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public Integer getContentLength() {
return contentLength;
}
public void setContentLength(Integer contentLength) {
this.contentLength = contentLength;
}
}
Resource fragment (path is declared on the class):
#POST
#Produces(JsonHelper.JSON_UTF8)
public Action saveAction(#QueryParam("action") ContentAction action) throws IOException {
ActionEntity entity = actionConverter.toEntity(action);
entity.setBeacon(beaconService.findById(action.getBeacon().getId()));
return actionConverter.convert(actionService.save(entity));
}
API caller (class responsible for sending requests):
URI uri = buildUri(path, params);
HttpPost httpPost = new HttpPost(uri);
httpClient.execute(httpPost);
buildUri simply creates URI from address & parameters
The action is converted to JSON as follows: mapper.writeValueAsString(action)
And the exception:
org.glassfish.jersey.server.internal.inject.ExtractorException: Error unmarshalling JAXB object of type "class com.kontakt.platform.apicommon.model.ContentAction".
at org.glassfish.jersey.server.internal.inject.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:195)
at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.convert(AbstractParamValueExtractor.java:138)
at org.glassfish.jersey.server.internal.inject.AbstractParamValueExtractor.fromString(AbstractParamValueExtractor.java:129)
at org.glassfish.jersey.server.internal.inject.SingleValueExtractor.extract(SingleValueExtractor.java:83)
at org.glassfish.jersey.server.internal.inject.QueryParamValueFactoryProvider$QueryParamValueFactory.provide(QueryParamValueFactoryProvider.java:88)
at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:81)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:121)
at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:195)
at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:104)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:353)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:343)
at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:318)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:235)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:983)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:359)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:372)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:335)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:218)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1008)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:1852)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
Caused by: javax.xml.bind.UnmarshalException
- with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.]
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.createUnmarshalException(AbstractUnmarshallerImpl.java:335)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.createUnmarshalException(UnmarshallerImpl.java:512)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:209)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:175)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:140)
at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:123)
at org.glassfish.jersey.server.internal.inject.JaxbStringReaderProvider$RootElementProvider$1.fromString(JaxbStringReaderProvider.java:190)
... 40 more
Caused by: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:198)
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.fatalError(ErrorHandlerWrapper.java:177)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:441)
at com.sun.org.apache.xerces.internal.impl.XMLErrorReporter.reportError(XMLErrorReporter.java:368)
at com.sun.org.apache.xerces.internal.impl.XMLScanner.reportFatalError(XMLScanner.java:1388)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(XMLDocumentScannerImpl.java:998)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:607)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:116)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:489)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:835)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:764)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:123)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1210)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:568)
at com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:203)
... 44 more
When I remove #XmlRootElement, Jersey tries to create object from constructor with string parameter (if added) = no automatic bean creation.
I've been stuck with this for two days and I'd be very grateful for any help.

I think the problem is that you need to set the content type. Since you are not specifying the content type, jersey is assuming it is reciving xml but it is in fact json. The error message is from jaxb trying to read the xml header and finding invalid characters. Try using the jersey client instead of an http post. Also I think you might what to change your #Produces(MediaType.APPLICATION_JSON) for consistency.
#Produces(MediaType.APPLICATION_JSON)
Example POST CODE
The most important part is that I am setting the type (type(MediaType.Application_JSON) that I am going to post.
WebResource webResource = createRestClient(true).resource(
REST_BASE_PATH + "/service");
ClientResponse response = webResource.type(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, contentActionObject);
Assert.assertTrue(response.getStatus() == 200);
If you do need to use the HttpPost object then there should be a configuration option to set http headers. I have done this using the the HttpUrlConnection. You would have to set your content type to be application/json instead of xml.
HttpURLConnection connection = null;
connection = (HttpURLConnection) new URL(requestUrl).openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/xml");
connection.setRequestProperty("Content-Type", "application/xml");

EclipseLink MOXy will be picked up as the JSON-binding provder by Jersey for media types that follow the following pattern.
*/json (i.e. application/json and text/json)
/+json
Based on your exception it appears as though the media type have have represented with JsonHelper.JSON_UTF8 does not match this pattern.

I know this is a little old, but I have been encountering the essentially the identical issue using Jersey 2.3 + Moxy. Same scenario and the identical exception is occurring. The only difference is that my POST method is annotated with:
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
I did find that when if I passed the same JSON representation in the request body instead of as a query parameter that it was unmarshalled successfully. This would require changing your POST method declaration to:
public Action saveAction(ContentAction action)
Not sure if this would be a work around for you. If you found a resolution I would be interested in hearing what it was.

Related

Reactor Feign cannot deserialize Flux response

I have the following controller
#RestController
public void MyController {
#GetMapping("/foo")
public Flux<Foo> getFoos() { /* return a flux of Foos*/ }
}
And a Feign client
public interface MyFeignClient {
#RequestLine("GET /foo")
Mono<Foo> getFoos();
}
public class MyClients {
public static MyFeignClient myFeignClient() {
return ReactorFeign.builder().target(MyFeignClient.class, "http://localhost:8080");
}
}
But when I call
StepVerifier.create(myFeignClient.foo())
.consumeNextWith(foo -> println(foo))
.verifyCompleted();
I got this error
java.lang.AssertionError: expectation "consumeNextWith" failed
(expected: onNext(); actual: onError(feign.FeignException: Cannot
deserialize instance of
com.example.Foo out of
START_ARRAY token at [Source: (BufferedReader); line: 1, column: 1]
reading GET
http://localhost:8080/foo))
at
reactor.test.ErrorFormatter.assertionError(ErrorFormatter.java:105)
at reactor.test.ErrorFormatter.failPrefix(ErrorFormatter.java:94) at
reactor.test.ErrorFormatter.fail(ErrorFormatter.java:64) at
reactor.test.ErrorFormatter.failOptional(ErrorFormatter.java:79) at
reactor.test.DefaultStepVerifierBuilder.lambda$consumeNextWith$1(DefaultStepVerifierBuilder.java:256)
at
reactor.test.DefaultStepVerifierBuilder$SignalEvent.test(DefaultStepVerifierBuilder.java:2112)
at
reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onSignal(DefaultStepVerifierBuilder.java:1408)
at
reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onExpectation(DefaultStepVerifierBuilder.java:1356)
at
reactor.test.DefaultStepVerifierBuilder$DefaultVerifySubscriber.onError(DefaultStepVerifierBuilder.java:1030)
at
reactor.core.publisher.FluxTake$TakeSubscriber.onError(FluxTake.java:138)
at
reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.onError(FluxSubscribeOn.java:157)
at
feign.reactive.ReactiveInvocationHandler$1.request(ReactiveInvocationHandler.java:115)
at
reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.requestUpstream(FluxSubscribeOn.java:131)
at
reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.onSubscribe(FluxSubscribeOn.java:124)
at
feign.reactive.ReactiveInvocationHandler.lambda$invokeMethod$0(ReactiveInvocationHandler.java:99)
at reactor.core.publisher.FluxSource.subscribe(FluxSource.java:52)
at reactor.core.publisher.Flux.subscribe(Flux.java:7777) at
reactor.core.publisher.FluxSubscribeOn$SubscribeOnSubscriber.run(FluxSubscribeOn.java:194)
at reactor.core.scheduler.WorkerTask.call(WorkerTask.java:84) at
reactor.core.scheduler.WorkerTask.call(WorkerTask.java:37) at
java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) at
java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at
java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at
java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834) Suppressed:
feign.FeignException: Cannot deserialize instance of
com.example.Foo out of
START_ARRAY token at [Source: (BufferedReader); line: 1, column: 1]
reading GET
http://localhost:8080/foo at
feign.FeignException.errorReading(FeignException.java:130) at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:162)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:80)
at
feign.reactive.ReactiveInvocationHandler$1.request(ReactiveInvocationHandler.java:109)
... 13 more Caused by:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
deserialize instance of
com.example.Foo out of
START_ARRAY token at [Source: (BufferedReader); line: 1, column: 1]
at
com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at
com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
at
com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139)
at
com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromArray(BeanDeserializerBase.java:1461)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:185)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:161)
at
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at
com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3063)
at feign.jackson.JacksonDecoder.decode(JacksonDecoder.java:61) at
feign.SynchronousMethodHandler.decode(SynchronousMethodHandler.java:183)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:147)
... 15 more
What have I done wrong here? How can I fix it?
A simple google search shows that the feign client does not support reactive. They have an incubating project trying to create support
Add Webflux support issue
Feign core
So to answer your questions
"What have i done wrong?"
You have chosen to use an HttpClient that does not support producers/consumers.
"How can i fix it?"
By not using the feign client and instead use an HttpClient that supports non blocking operations, like for instance, the spring WebClient.

Spring default consumes and produces

I'm writing a servlet that will use a bunch of RestControllers to provide functionality.
All of that will use JSON almost exclusively, so I would like a compact way to say: Unless specified otherwise, consume and produce MediaType.APPLICATION_JSON_VALUE for everything.
I thought I found a nice solution on another SO question.
However, as already pointed out in a comment there, this solution causes trouble.
#RestController
#RequestMapping(value = "/relationship/type", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE, method = {
RequestMethod.GET
})
public class DRelationshipTypeResource {
// #GetMapping("/all")
#RequestMapping(value = "/all", method = RequestMethod.GET)
public List<DRelationshipTypeDTO> getAll() {
return DRelationshipTypeService.getAll();
}
This controller also will feature POST/PUT/DELETE plus some more GETs. I removed them for now to minimize possible causes of errors.
Calling this route produces a 415 error.
Even worse, I would really like to be able to use
#GetMapping("/all")
instead of the more verbose #RequestMapping Overload for the getAll()-Method, but that also produces the same 415 error.
Server debug console spits out this when the request arrives:
2019-01-29 10:20:54.627 WARN 10712 --- [io-9999-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type '' not supported]
2019-01-29 10:20:54.628 ERROR 10712 --- [io-9999-exec-10] o.a.c.c.C.[Tomcat].[localhost] : Exception Processing ErrorPage[errorCode=0, location=/error]
java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.getHttpServletMapping()Ljavax/servlet/http/HttpServletMapping;
at org.apache.catalina.core.ApplicationHttpRequest.setRequest(ApplicationHttpRequest.java:690) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.ApplicationHttpRequest.<init>(ApplicationHttpRequest.java:114) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.ApplicationDispatcher.wrapRequest(ApplicationDispatcher.java:917) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:358) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) ~[tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:394) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:253) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:175) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417) [tomcat-embed-core-9.0.14.jar:9.0.14]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.14.jar:9.0.14]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.14.jar:9.0.14]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]
and returns a HTTP Status 415 – Unsupported Media Type to the client making the request.
To clarify further, if I use a "dumb" class such as this, everything works fine, with the content correctly being returned as JSON.
#RestController
#RequestMapping("relationship/type")
public class DRelationshipTypeResource {
#GetMapping("/all")
public List<DRelationshipTypeDTO> getAll() {
return DRelationshipTypeService.getAll();
}
As the stack trace, clearly telling content-type is empty (' ').
I think Content-Type is not passed while making the GET call. If you pass Content-Type as 'application/json' it should work.
You have defined consumes and produces at the class level, which means by default all the REST services should pass headers, Content-Type and Accept in order to consume the service.
It's missing to add / at beginning on your path and add method type GET:
#RequestMapping(value = "/relationship/type",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE,
method = {RequestMethod.GET}))
To accept all request type just overwrite consumes value.
#RequestMapping(value = "/all", consumes="*/*", method = RequestMethod.GET)
public List<DRelationshipTypeDTO> getAll() {
return DRelationshipTypeService.getAll();
}
The issue was with my requests not explicitly having a Content-Type application/json header, as pointed out by https://stackoverflow.com/a/54418436/2436002 .
To clear up some of the apparent misinformation about all this, everything worked just as I expected now, with very readable, clean and spring-like code. Maybe it can help others looking for an example.
#RestController
#RequestMapping(value = "relationship/type", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public class DRelationshipTypeResource {
#GetMapping("/all")
public List<DRelationshipTypeDTO> getAll() {
return DRelationshipTypeService.getAll();
}
#GetMapping("/{query}")
public DRelationshipTypeDTO get(#PathVariable("query") String query) {
return DRelationshipTypeService.get(query);
}
#PostMapping
public ResponseEntity<Void> create(DRelationshipTypeDTO dto) {
String label = DRelationshipTypeService.create(dto);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{label}").buildAndExpand(label).toUri();
return ResponseEntity.created(uri).build();
}
#PutMapping("{label}")
public ResponseEntity<Void> update(#PathVariable("label") String label, DRelationshipTypeDTO dto) {
DRelationshipTypeService.update(label, dto);
return ResponseEntity.noContent().build();
}
#DeleteMapping("{label}")
public ResponseEntity<Void> delete(#PathVariable("label") String label) {
DRelationshipTypeService.delete(label);
return ResponseEntity.noContent().build();
}
Not 100% yet on the best method for URI-Building during the POST /Create, but that's a different issue, and it at least works fine (proper location header for HTTP201 response).

How to unit test an apache camel cxfrs route?

I have a working cxfrs route. I can hit the rest service using SoapUi with a json object and get a response.
I wanted to write unit tests, and I though to use CamelTestSupport. My routebuilder configure() method looks something like this:
from(cxfrsEndpoint)
.recipientList(simple("direct:${header.operationName}"))
.setHeader(Exchange.CONTENT_LENGTH, simple("-1"));
from("direct:submitRequest")
.bean("responseBean", "checkJson")
.bean("responseBean", "createSuccessResponse");
When I hit the url from SoapUi (http://localhost:8181/cxf/myContext/submitRequest) as a POST with an appropriate json string, I get a "success" json back. Cool.
In my unit test, I created an overriden createRouteBuilder method:
#Override
protected RouteBuilder createRouteBuilder() {
MyRouteBuilder myRouteBuilder = new MyRouteBuilder();
myRouteBuilder.setCxfrsEndpoint("direct:start");
return myRouteBuilder;
}
And then my unit test (I thought) would look something like this:
#Test
public void thisIsATest() throws Exception {
MyRequest myRequest = new MyRequest();
request.setSomeProperty("Some property value");
ObjectMapper objectMapper = new ObjectMapper();
String goodJsonRequest = objectMapper.writeValueAsString(request);
String response = (String) template.requestBodyAndHeader(START_POINT, goodJsonRequest, "operationName", "submitRequest");
assertNotNull(response);
//Omitted: further assertions for content of the response json
}
Well, when I execute the unit test, I get an unmarshalling exception with this as the cause:
Caused by: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '{' (code 123) in prolog; expected '<'
at [row,col {unknown-source}]: [1,1]
at com.ctc.wstx.sr.StreamScanner.throwUnexpectedChar(StreamScanner.java:647)
at com.ctc.wstx.sr.BasicStreamReader.nextFromProlog(BasicStreamReader.java:2054)
at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1131)
at com.sun.xml.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:164)
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:415)
... 74 more
It doesn't like my json string. What is the real service receiving?
On the real route, I enabled logging on the endpoint in the blueprint.xml like this:
<!-- RouteBuilder declarations -->
<bean id="myRouteBuilder" class="com.mycompany.MyRouteBuilder">
<property name="cxfrsEndpoint" value="cxfrs:bean:cxfrsEndpoint?bindingStyle=SimpleConsumer&loggingFeatureEnabled=true" />
</bean>
The json string I see in the log as the "Payload:" doesn't look any different than the json string I'm sending via template.requestBodyAndHeader(...).
What am I missing or what am I doing wrong here?
Thanks!
Please, try not rewrite your endpoint like this:
myRouteBuilder.setCxfrsEndpoint("direct:start");
Seems to me that overriding your endpoint changes the interface and the CXF now is expecting a XML and not JSON (based on your logs):
Caused by: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '{' (code 123) in prolog; expected '<'
at [row,col {unknown-source}]: [1,1]
at com.ctc.wstx.sr.StreamScanner.throwUnexpectedChar(StreamScanner.java:647)
at com.ctc.wstx.sr.BasicStreamReader.nextFromProlog(BasicStreamReader.java:2054)
at com.ctc.wstx.sr.BasicStreamReader.next(BasicStreamReader.java:1131)
at com.sun.xml.bind.v2.runtime.unmarshaller.StAXStreamConnector.bridge(StAXStreamConnector.java:164)
at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:415)
... 74 more
You already have the endpoint defined in here:
<bean id="myRouteBuilder" class="com.mycompany.MyRouteBuilder">
<property name="cxfrsEndpoint" value="cxfrs:bean:cxfrsEndpoint?bindingStyle=SimpleConsumer&loggingFeatureEnabled=true" />
</bean>
My suggestion is to use this code as example and call your endpoint directly via HTTP:
public void setUp() throws Exception {
super.setUp();
httpclient = HttpClientBuilder.create().build();
}
public void tearDown() throws Exception {
super.tearDown();
httpclient.close();
}
#Test
public void testGetCustomerOnlyHeaders() throws Exception {
HttpGet get = new HttpGet("http://serviceurl.com");
HttpResponse response = httpclient.execute(get);
assertEquals(200, response.getStatusLine().getStatusCode());
}
If you have a chance, take a look into the entire camel-cxf project tests to have an idea how to properly test.

NO JSON Result from Spring3 MVC DispatcherServlet due to AOP Configuration

When we configure Spring AOP the JSON Results disappear for : AOPExression1
<aop:pointcut id="dmhMethodExecution"
expression="within(com.aditya.dmh..*)" />
So I added an exclusion for : AOPExpression1 as AOpExpression2
<aop:pointcut id="dmhMethodExecution"
expression="within(com.aditya.dmh..*)
and !within(com.aditya.dmh.controller..*)" />
in the ASPECTJ Expression
Still I donot see my JSON results from the controller which is a restful implementation.
package com.aditya.dmh.controller;
#Controller
public class EmployeeController {
private EmployeeServiceInterface employeeService;
#Autowired
public void setEmployeeService(EmployeeServiceInterface employeeService) {
this.employeeService = employeeService;
}
#RequestMapping("/employeeservices/1/allemployees.view")
public #ResponseBody Result<EmployeeModel> getEmployees(){
return employeeService.getEmployees(0, 10);
}
}
When I use log4j for the DEBUG messages I see the following:
15:37:04.214 [http-8090-1] DEBUG o.s.web.servlet.DispatcherServlet - Null ModelAndView returned to DispatcherServlet with name 'dmhServiceDispatcher': assuming HandlerAdapter completed request handling
15:37:04.214 [http-8090-1] DEBUG o.s.web.servlet.DispatcherServlet - Successfully completed request
When I remove the AOP the JSON results start to appear and I see that the additional Debug Message.
17:11:36.270 [http-8090-2] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Written [com.aditya.Result#8a85268] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#62ba2e48]
Looking at the Spring forums I understand that the Convertor is automatically configured when the
<mvc:annotation-driven/>
is used.
Is my problem of configuring AOP have anything to do with the RequestResponseBodymethodProcessor not being called.
Does this have anything to do with the proxies created around my controller when I use AOPExpression1. Why would an exclusion as in AOPExpression2 still have the problem.
Anyhelp would be appreciated
I belive that to intercept a request to a controller you should do it with MVC interceptors and not with aspects. What I did is to put into the applicationContext.xml this:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/employeeservices/1/allemployees.view"/>
<bean class="com.aditya.dmh.interceptor.ResultInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
Now, the class ResultInterceptor is where you put the code you want to be done, for instance:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("--- preHandle --- ");
return true;
}
At least this is the way I did it.
Hope it helps.
This is a bit of a speculation:
I think what is happening is a CGLIB based dynamic proxy is getting created for your controller (although you have excluded it explicitly in your new pointcut expression), if this happens then #RequestMapping annotations are not correctly detected(by `) and so the controller is not there to handle your REST request.
Can you try a few things:
Have an interface for the controller with the exact same methods that the controller handles, and put the #RequestMapping annotations there, this will handle cases where the dynamic proxy is created and should work as expected even if the dynamic proxy gets created..
Play around a little more with your pointcut expression to see why a proxy for you controller may be getting created.
THE SOLUTION FOR OUR PROBLEM IN THIS CONTEXT
We found out that the whole thing was with the Around Advice in AOP Configuration that we have had.
Before Fix
public void logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.debug(buildLogMessage(new StringBuilder().append(METHOD_AROUND_ID)
.append("[").append(totalTime).append("] ").toString(),
joinPoint));
return returnValue;
}
After Fix
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object returnValue = joinPoint.proceed();
long totalTime = System.currentTimeMillis() - startTime;
log.debug(buildLogMessage(new StringBuilder().append(METHOD_AROUND_ID)
.append("[").append(totalTime).append("] ").toString(),
joinPoint));
return returnValue;
}
the void effectively made sure that the Response Object sent by the logAround was not passed on back to the RequestResponseBodyMethodProcessor
Once we had it captured & returned the cglib proxies sent the response back to the processor & had the response sent back to the client.

javax.xml.bind.UnmarshalException when request rest json service with jersey client

I have a rest webservice (with jersey) which returns json list, if i call it directly it returns exactly this :
[{"success":false,"uri":"foo:22","message":"Unknown host : foo"},{"success":true,"uri":"localhost:8082","message":null}]
generated by this snippet :
#GET
#Path("/opening/")
public List<OpeningResult> testOpenings(#QueryParam("uri") List<String> uris) {
LOG.debug("testOpenings request uris :[" + uris + "]");
List<OpeningResult> openingResults = infoService.testOpenings(uris);
return openingResults;
}
It's a Collection of Pojo which look like this :
#XmlRootElement(name = "OpeningResult")
public class OpeningResult {
attributes
...
getter/setter
}
this Pojo is shared through a common jar between the server and the client.
i call the web service with this snippet :
Client client = Client.create();
WebResource resource = client.resource("http://localhost:8080/scheduler/rest/opening");
MultivaluedMap<String, String> params = new MultivaluedMapImpl();
for (String uri : uris) {
params.add("uri", uri);
}
List<OpeningResult> results = newArrayList(resource.queryParams(params).get(OpeningResult[].class));
I add some trace on the server side, i see that my rest service is called with the good parameters, buth on client side, i have this error :
Caused by: javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"success"). Expected elements are <{}OpeningResult>
I don't find where it comes from ?
Modify your code to set up your client like this:
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
Client client = Client.create(clientConfig);
I had the exact same problem until this question and its answers pointed me in the right direction.
The situation is caused by the default jersey-json module used for serialization to and from JSON, which does not handle certain JSON constructs properly.
You can set the FEATURE_POJO_MAPPING flag to use the Jackson library's JacksonJsonProvider for JSON serialization instead.
Check out the Jersey Client side doc on using JSON. It looks like you're at least missing the annotation:
#Produces("application/json")
But you could also be missing the POJO Mapping feature filters for both client and server side. These all seem to be minor configuration changes.