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.
Related
My whole API generates JSON without any problem, but as soon as an exception is thrown, the controller decides to stringy the object to XML... why?
My controller looks like this:
#RestController
public class Controller{
#ResponseBody
#ExceptionHandler({ IllegalArgumentException.class, MissingServletRequestParameterException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, Object> invalid(Exception ex){
return new ObjectResponseBuilder().add("message", ex.getMessage()).get();
}
}
However this is the response I get:
<Map>
<message>Required request parameter 'min' for method parameter type Long is not present</message>
</Map>
I'm making the request directly from the browser, so no header is set... just like for all the others endpoints (that are returning JSON instead)
I have a spring boot application that uses Swagger 2.0 to generate API and model objects from Swagger API definitions.
I am running some of the unit tests using MockitoJUnitRunner.
One of the API returns an object containing a date field.
#JsonProperty("departureDate")
private LocalDate departureDate = null;
I am having some problems in trying to get expected date format in JSON response in the unit test.
My test looks something like this.
#RunWith(MockitoJUnitRunner.class)
public class TourApiControllerStandaloneTest {
//mock declarations
...
private JacksonTester<TourHeader> jsonTourHeader;
// controller under test
private TourApiController tourApiController;
private TourleaderMockData tourleaderMockData;
...
#Before
public void setup() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new LocalDateModule()); //LocalDateModule formats the date in yyyy-mm-dd format
tourApiController = new TourApiController(objectMapper,
httpServletRequestMock, tourServiceMock);
JacksonTester.initFields(this, objectMapper);
mockMvc = MockMvcBuilders.standaloneSetup(tourApiController)
.setControllerAdvice(new GlobalExceptionHandler())
.build();
tourleaderMockData = new TourleaderMockData();
}
#Test
public void getTourHeaderWhenExists() throws Exception {
...
// given
given(tourServiceMock.getTourHeader(tourNumber))
.willReturn(tourleaderMockData.getMockTourHeaderRecord(tourNumber));
// when
MockHttpServletResponse response = mockMvc.perform(
get("/tour/" + tourNumber + "/header").accept(MediaType.ALL))
.andReturn().getResponse();
// then
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
// *************** THIS ASSERTION IS FAILING ****************
assertThat(response.getContentAsString()).isEqualTo(
jsonTourHeader.write(tourleaderMockData.getMockTourHeaderRecord(tourNumber)).getJson()
);
}
}
Date format returned in JSON response from API call and serlized JSON from mock header object do not match. This is how the date appears in actual and expected response.
** Actual **
{ ...,"departureDate":[2018,12,1], ...}
** Expected **
{...,"departureDate": "2018-12-01", ...}
The same assertion works i.e. date format is coming as expected when I am using SpringRunner as below.
#RunWith(SpringRunner.class)
#WebMvcTest(TourApiController.class)
Can someone please advise what I can do to get expected date format when I am running the test with MockitoJUnitRunner. Thanks.
Ok, I got the answer from another thread on stackoverflow
https://stackoverflow.com/a/35323312/5519519
So basically I needed to create an instance of MappingJackson2HttpMessageConverter and set my object mapper into this instance.
And then pass this message converter at the time of setting up mockMvc.
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new
MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
mockMvc = MockMvcBuilders.standaloneSetup(tourApiController)
.setControllerAdvice(new GlobalExceptionHandler())
.setMessageConverters(mappingJackson2HttpMessageConverter)
.build();
This resolved the issue.
I'm trying to use Spring-Data-Couchbase.
I want to get List from findAll(Iterable) method.
I set view 'all' to my Production View.
But, I meet the exception.
How Can I use findAll(Iterable) method?
Below is a Sample Code and exception.
Document Class:
#Document
public class User {
#Id
private String id;
private String userSeq;
}
Repository Class :
public interface UserRepository extends CouchbaseRepository<User, String> {
}
Service Class:
//List<String> get the Same result & exception.
Set<String> friendSet = new HashSet<String>();
friendSet.add("User8");
friendSet.add("User6");
userRepository.findAll(friendSet)
Exception:
Caused by: java.util.concurrent.ExecutionException: OperationException: SERVER: bad_request Reason: invalid UTF-8 JSON: {{error,{2,"lexical error: invalid char in json text.\n"}},
"[User8, User6]"}
at com.couchbase.client.internal.HttpFuture.waitForAndCheckOperation(HttpFuture.java:98)
at com.couchbase.client.internal.HttpFuture.get(HttpFuture.java:82)
at com.couchbase.client.internal.HttpFuture.get(HttpFuture.java:72)
at com.couchbase.client.CouchbaseClient.query(CouchbaseClient.java:778)
... 66 more
Caused by: OperationException: SERVER: bad_request Reason: invalid UTF-8 JSON: {{error,{2,"lexical error: invalid char in json text.\n"}},
"[User8, User6]"}
at com.couchbase.client.protocol.views.NoDocsOperationImpl.parseError(NoDocsOperationImpl.java:110)
at com.couchbase.client.protocol.views.ViewOperationImpl.handleResponse(ViewOperationImpl.java:68)
at com.couchbase.client.http.HttpResponseCallback.completed(HttpResponseCallback.java:103)
at com.couchbase.client.http.HttpResponseCallback.completed(HttpResponseCallback.java:51)
at org.apache.http.concurrent.BasicFuture.completed(BasicFuture.java:115)
at org.apache.http.nio.protocol.HttpAsyncRequester$RequestExecutionCallback.completed(HttpAsyncRequester.java:376)
at org.apache.http.concurrent.BasicFuture.completed(BasicFuture.java:115)
at org.apache.http.nio.protocol.BasicAsyncClientExchangeHandler.responseCompleted(BasicAsyncClientExchangeHandler.java:179)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.processResponse(HttpAsyncRequestExecutor.java:349)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:236)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:267)
at org.apache.http.impl.nio.DefaultHttpClientIODispatch.onInputReady(DefaultHttpClientIODispatch.java:165)
at org.apache.http.impl.nio.DefaultHttpClientIODispatch.onInputReady(DefaultHttpClientIODispatch.java:51)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:113)
at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:159)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:338)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:316)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:277)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:105)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:584)
... 1 more
You have to quote the id:s before you call findAll(...).
Set<String> friendSet = new HashSet<String>();
friendSet.add("\"User8\"");
friendSet.add("\"User6\"");
If the id:s aren't quoted the keys parameter sent to the Couchbase server will look something like [User8, User6] which isn't valid JSON, hence the exception. Pretty surprising behaviour...
I extended the jersey-examples-moxy code to use an XML schema definition instead of the JAXB annotated beans. The xjc compiled XML schema produces XML and JSON encodings identical to the original example.
I followed the jersey instructions and used the ObjectFactory to generate the JAXBElement Customer object representation within CustomerResource.java. I also modified the client as described. I also incorporated the fix described in PUT issues with JSON processing using JAXB under Jersey 2.2 with MOXy
The MediaType.APPLICATION_XML functions perfectly, and MediaType.APPLICATION_JSON works for GETs, but the client is failing to marshall JSON on a PUT with "MessageBodyWriter not found". The following exception is thrown:
testJsonCustomer(org.glassfish.jersey.examples.jaxbmoxy.MoxyAppTest) Time elapsed: 0.113 sec <<< ERROR!
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=application/json, type=class javax.xml.bind.JAXBElement, genericType=class javax.xml.bind.JAXBElement.
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:191)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)
at org.glassfish.jersey.filter.LoggingFilter.aroundWriteTo(LoggingFilter.java:268)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1005)
at org.glassfish.jersey.client.ClientRequest.writeEntity(ClientRequest.java:430)
at org.glassfish.jersey.client.HttpUrlConnector._apply(HttpUrlConnector.java:290)
Here is how I modified CustomerResource.java:
private static ObjectFactory factory = new ObjectFactory();
#GET
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public JAXBElement<Customer> getCustomer() {
return factory.createCustomer(customer);
}
#PUT
#Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public JAXBElement<Customer> setCustomer(Customer c) {
customer = c;
return factory.createCustomer(customer);
}
Here is how I am making the PUT request (same as for the functioning XML):
#Override
protected void configureClient(ClientConfig clientConfig) {
clientConfig.register(new MoxyXmlFeature());
}
#Test
public void testJsonCustomer() throws Exception {
ObjectFactory factory = new ObjectFactory();
final WebTarget webTarget = target().path("customer");
// Target customer entity with GET and verify inital customer name.
Customer customer = webTarget.request(MediaType.APPLICATION_JSON).get(Customer.class);
assertEquals("Tom Dooley", customer.getPersonalInfo().getName());
// Update customer name with PUT and verify operation successful.
customer.getPersonalInfo().setName("Bobby Boogie");
Response response = webTarget.request(MediaType.APPLICATION_JSON).put(Entity.json(factory.createCustomer(customer)));
assertEquals(200, response.getStatus());
// Target customer entity with GET and verify name updated.
Customer updatedCustomer = webTarget.request(MediaType.APPLICATION_JSON).get(Customer.class);
assertEquals(customer.getPersonalInfo().getName(), updatedCustomer.getPersonalInfo().getName());
}
Thank you for your help!
The issue you're facing is on this line:
Response response = webTarget.request(MediaType.APPLICATION_JSON).put(Entity.json(factory.createCustomer(customer)));
Basically you're passing JAXBElement to Entity#json method but the runtime doesn't have information about the generic type, you need to provide it. That's what GenericEntity<T> class is for:
webTarget
.request(MediaType.APPLICATION_JSON)
.put(Entity.json(new GenericEntity<JAXBElement<Customer>>(factory.createCustomer(customer)) {}));
I'm mapping my request's JSON POST data into an object using Spring's #RequestBody annotation and MappingJacksonHttpMessageConverter. However after that I'd like to read the data in String form to do some additional authentication. But when the marshalling has happened, the InputStream in HttpServletRequest is empty. Once I remove the #RequestBody parameter from the method the reading of POST data into a String works as expected.
Do I have to compromise by giving up the #RequestBody and doing the binding somehow manually or is there a more elegant solution?
So, basically you need to compute a hash of the request body. The elegant way to do it is to apply a decorator to the InputStream.
For example, inside a handler method (in this case you can't use #RequestBody and need to create HttpMessageConverter manually):
#RequestMapping(...)
public void handle(HttpServletRequest request) throws IOException {
final HashingInputStreamDecorator d =
new HashingInputStreamDecorator(request.getInputStream(), secretKey);
HttpServletRequest wrapper = new HttpServletRequestWrapper(request) {
#Override
public ServletInputStream getInputStream() throws IOException {
return d;
}
};
HttpMessageConverter conv = ...;
Foo requestBody = (Foo) conv.read(Foo.class, new ServletServerHttpRequest(wrapper));
String hash = d.getHash();
...
}
where hash is computed incrementally in overriden read methods of HashingInputStreamDecorator.
You can also use #RequestBody if you create a Filter to apply the decorator. In this case decorator can pass the computed hash to the handler method as a request attribute. However, you need to map this filter carefully to apply it only to the requests to specific handler method.
In your urlMapping bean you can declare list of additional interceptors:
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<bean class="org.foo.MyAuthInterceptor"/>
</list>
</property>
</bean>
Those interceptors have access to HttpServletRequest, though if you read from the stream the chances are that parameter mapper won't be able to read it.
public class AuthInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav) {
...
}
}
If I understand this correctly, one common way used with JAX-RS (which is somewhat similar to Spring MVC with respect to binding requests) is to first "bind" into some intermediate raw type (usually byte[], but String also works), and manually bind from that to object, using underlying data binder (Jackson). I often do this to be able to fully customize error handling of data binding.