I'm aware that some developer say it's not a unit test to invoke a method of an EJB which requests a web resource. However, please do not argue about that in this thread! I think it is worthwhile to do it.
my Test: testng class --> EJB method --> rest resource
Setup:
Wildfly 8.1
TestNG 6.8.8
jboss-jaxrs-api_1.1_spec, 1.0.1.Final
This is my testing class.
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import com.doe.webapp.model.general.geoinfo.GeoInfo;
public class GeoIPBeanTest {
#DataProvider(name = "ipAdresses")
public static Object[][] primeNumbers() {
return new Object[][] {
{ "127.0.0.1", true }, // localhost
{ "80.218.114.61", true } }; // real IP
}
#Test(dataProvider = "ipAdresses")
public void getGeoInfoByIp(String ipAddress, boolean isExpectedTrue) {
GeoIPBean geoIpBean = new GeoIPBean();
GeoInfo geoInfo = null;
try {
geoInfo = geoIpBean.getGeoInfoByIp(ipAddress);
} catch (Exception ex) {
Assert.fail(ex.getLocalizedMessage());
}
}
}
This is my class under test.
import javax.ejb.Singleton;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import com.doe.webapp.model.general.geoinfo.GeoInfo;
#Singleton
public class GeoIPBean {
private static final String IPV4_PATTERN = "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
Map<String, GeoInfo> geoInfoCache = new HashMap<String, GeoInfo>();
// Service Description is here http://freegeoip.net/
static final String GEO_SERICE_URL = "http://freegeoip.net/";
static final String FORMAT = "json";
public GeoInfo getGeoInfoByIp(String ipAddress) {
if(!isValidIp(ipAddress)){
//TODO log invalid IP as warning
return null;
}
GeoInfo geoInfo = geoInfoCache.get(ipAddress);
if (geoInfo == null) {
Client client = ClientBuilder.newClient();
// Invoke the service.
WebTarget webTarget = client.target(GEO_SERICE_URL + FORMAT + "/"
+ ipAddress);
//geoInfo
Builder builder = webTarget.request(MediaType.APPLICATION_JSON);
geoInfo = builder.get(GeoInfo.class);
}
return geoInfo;
}
public static boolean isValidIp(String ipAddress) {
if(ipAddress == null)
return false;
Pattern pattern = Pattern.compile(IPV4_PATTERN);
Matcher matcher = pattern.matcher(ipAddress);
return matcher.matches();
}
}
This EJB works when I run it in the container. It does NOT work in a testNG case.
Client client = ClientBuilder.newClient();
This line in the EJB returnS the error.
java.lang.AssertionError: java.lang.ClassNotFoundException: org.glassfish.jersey.client.JerseyClientBuilder
at org.testng.Assert.fail(Assert.java:94)
at com.doe.webapp.service.general.geoinfo.GeoIPBeanTest.getGeoInfoByIp(GeoIPBeanTest.java:25)
I first thought it is because I have annotated the wildfly library with scope provided ...
<!-- JBOSS JAX REST 2.0 FRAMEWORK -->
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
<version>1.0.1.Final</version>
</dependency>
<!-- RS client library -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.0.1</version>
</dependency>
... but Wildfly is using RestEasy and not Jersey. Then I added ...
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.18.1</version>
</dependency>
... but did NOT help neither.
In Wildfly, they have been using RESTEasy as the default rest provider. It states,
RESTEasy is bundled with JBoss/Wildfly and completely integrated as per the requirements of Java EE 6.
Therefore you have to remove RESTEasy dependencies defore you use Jersey with Jboss/Wildfly. Eventhough I haven't done that, there can be many resources which guides to do that. (Check these links.)
Or else, as an alternative, you can use RESTEasy instead of Jersey.
Related
Use-case:
Developers/I, want to only implement a Protobuf implementation (binary protocol). However, I need a way to add config, so, the same implementation is exposed as rest/json api as well -- without code duplication.
I have proto endpoints exposed. I also want consumers to post json equivalent of those proto objects and return/receive json equivalent of the results with type info (Pojo?). The type info helps with OpenAPI / Swagger documentation too!
What are the most elegant/simple ways to achieve that without code duplication?
Any example github code that achieves that would be helpful.
Note: This is for webflux & netty - no tomcat.
ProtobufJsonFormatHttpMessageConverter - works for tomcat, does not work for netty. A working example code would be great.
I was messing around with this and ended up with this. Nothing else worked for me.
Using protov3 and setting a protobuf like this
syntax = "proto3";
option java_package = "com.company";
option java_multiple_files = true;
message CreateThingRequest {
...
message CreateThingResponse {
....
I can scan for the protobuf files by setting app.protoPath in my application.properties
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.google.common.reflect.ClassPath;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.reactive.config.WebFluxConfigurer;
#Configuration
public class WebConfig implements WebFluxConfigurer {
#Value("${app.protoPath:com.}")
private String protoPath;
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(
new Jackson2JsonEncoder(Jackson2ObjectMapperBuilder.json().serializerByType(
Message.class, new JsonSerializer<Message>() {
#Override
public void serialize(Message value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String str = JsonFormat.printer().omittingInsignificantWhitespace().print(value);
gen.writeRawValue(str);
}
}
).build())
);
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
Map<Class<?>, JsonDeserializer<?>> deserializers = new HashMap<>();
try {
for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
if (info.getName().startsWith(protoPath)) {
final Class<?> clazz = info.load();
if (!Message.class.isAssignableFrom(clazz)) {
continue;
}
#SuppressWarnings("unchecked") final Class<Message> proto = (Class<Message>) clazz;
final JsonDeserializer<Message> deserializer = new CustomJsonDeserializer() {
#Override
public Class<Message> getDeserializeClass() {
return proto;
}
};
deserializers.put(proto, deserializer);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(Jackson2ObjectMapperBuilder.json().deserializersByType(deserializers).build()));
}
private abstract static class CustomJsonDeserializer extends JsonDeserializer<Message> {
abstract Class<? extends Message> getDeserializeClass();
#Override
public Message deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Message.Builder builder = null;
try {
builder = (Message.Builder) getDeserializeClass()
.getDeclaredMethod("newBuilder")
.invoke(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
JsonFormat.parser().merge(jp.getCodec().readTree(jp).toString(), builder);
return builder.build();
}
}
}
Then I just use the object types in the returns;
#PostMapping(
path = "/things",
consumes = {MediaType.APPLICATION_JSON_VALUE, "application/x-protobuf"},
produces = {MediaType.APPLICATION_JSON_VALUE, "application/x-protobuf"})
Mono<CreateThingResponse> createThing(#RequestBody CreateThingRequest request);
With https://github.com/innogames/springfox-protobuf you can get the responses to show in swagger but the requests still aren't showing for me.
You'll have to excuse the messy Java I'm a little rusty.
I needed to support json and the following code helped
#Bean
public WebFluxConfigurer webFluxConfigurer() {
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.registerModule(new ProtobufModule());
configurer.customCodecs().register(new Jackson2JsonEncoder(mapper));
configurer.customCodecs().register(new Jackson2JsonDecoder(mapper));
}
};
}
Try adding ProtoEncoder in your WebFlux config:
#EnableWebFlux
public class MyConfig implements WebFluxConfigurer {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.customCodecs().register(new ProtobufEncoder());
}
}
Then in your request mapping return the proto object:
#GetMapping (produces = "application/x-protobuf")
public MyProtoObject lookup() {
return new MyProtoObject();
}
Furthermore, if you want to serialize the proto object into JSON and return String, then have a look at com.googlecode.protobuf-java-format:protobuf-java-format library and JsonFormat::printToString capability (https://code.google.com/archive/p/protobuf-java-format/):
#GetMapping
public String lookup() {
return new JsonFormat().printToString(new MyProtoObj());
}
Since version 4.1 spring provides org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter for reading and writing protos as Json.
However, If you are using Spring 5.x and Protobuf 3.x there is org.springframework.http.converter.protobuf.ProtobufJsonFormatHttpMessageConverter for more explicit conversion of Json.
This documentation should help you:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/protobuf/ProtobufHttpMessageConverter.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/protobuf/ProtobufJsonFormatHttpMessageConverter.html
I have the POST request api call to accept the json body request parameters and multipart file from client side(postman or java client).
I want to pass both the json data and multipart file in single request.
I have written the code like below.
#RequestMapping(value = "/sendData", method = RequestMethod.POST, consumes = "multipart/form-data")
public ResponseEntity<MailResponse> sendMail(#RequestPart MailRequestWrapper request) throws IOException
But, i could not accomplish it using postman rest client.
I'm using spring boot on server side.
Could anyone suggest me on this question.
Thanks in advance,
You cat use #RequestParam and Converter for JSON objects
simple example :
#SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
#Data
public static class User {
private String name;
private String lastName;
}
#Component
public static class StringToUserConverter implements Converter<String, User> {
#Autowired
private ObjectMapper objectMapper;
#Override
#SneakyThrows
public User convert(String source) {
return objectMapper.readValue(source, User.class);
}
}
#RestController
public static class MyController {
#PostMapping("/upload")
public String upload(#RequestParam("file") MultipartFile file,
#RequestParam("user") User user) {
return user + "\n" + file.getOriginalFilename() + "\n" + file.getSize();
}
}
}
and postman:
UPDATE
apache httpclient 4.5.6 example:
pom.xml dependency:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.5.6</version>
</dependency>
<!--dependency for IO utils-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
service will be run after application fully startup, change File path for your file
#Service
public class ApacheHttpClientExample implements ApplicationRunner {
private final ObjectMapper mapper;
public ApacheHttpClientExample(ObjectMapper mapper) {
this.mapper = mapper;
}
#Override
public void run(ApplicationArguments args) {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
File file = new File("yourFilePath/src/main/resources/foo.json");
HttpPost httpPost = new HttpPost("http://localhost:8080/upload");
ExampleApplication.User user = new ExampleApplication.User();
user.setName("foo");
user.setLastName("bar");
StringBody userBody = new StringBody(mapper.writeValueAsString(user), MULTIPART_FORM_DATA);
FileBody fileBody = new FileBody(file, DEFAULT_BINARY);
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
entityBuilder.addPart("user", userBody);
entityBuilder.addPart("file", fileBody);
HttpEntity entity = entityBuilder.build();
httpPost.setEntity(entity);
HttpResponse response = client.execute(httpPost);
HttpEntity responseEntity = response.getEntity();
// print response
System.out.println(IOUtils.toString(responseEntity.getContent(), UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
}
console output will look like below:
ExampleApplication.User(name=foo, lastName=bar)
foo.json
41
You can use both of them.
#RequestPart : This annotation associates a part of a multipart request with the method argument, which is useful for sending complex multi-attribute data as payload, e.g., JSON or XML.
In other words Request Part parse your json string object from request to your class object. On the other hand, Request Param just obtain the string value from your json string value.
For example, using Request Part:
#RestController
#CrossOrigin(origins = "*", methods= {RequestMethod.POST, RequestMethod.GET,
RequestMethod.PUT})
#RequestMapping("/api/api-example")
public class ExampleController{
#PostMapping("/endpoint-example")
public ResponseEntity<Object> methodExample(
#RequestPart("test_file") MultipartFile file,
#RequestPart("test_json") ClassExample class_example) {
/* do something */
}
}
and postman would be configured like:
#RequestParam : Another way of sending multipart data is to use #RequestParam. This is especially useful for simple data, which is sent as key/value pairs along with the file, as I said, just key/value. Also is used to get value from query params, I think that is its main goal.
I was stuck with this problem for past few hours
So I came across this question.
Summary:
Use #ModelAttribute instead of #RequestBody.
#ModelAttriute will work just like other normal(without multipart property in entity) Entity mapping.
You have two options -
Send a MultipartFile along with json data
public void uploadFile(#RequestParam("identifier") String identifier, #RequestParam("file") MultipartFile file){
}
OR
Send Json data inside a MultipartFile and then parse Multipart file as mentioned below and thats it.
public void uploadFile(#RequestParam("file") MultipartFile file){
POJO p = new ObjectMapper().readValue(file.getBytes(), POJO.class);
}
I explain all here in answer part:
enter link description here
I am writing a Jersey 2 Restful web service.
Here is the service class:
package com.Test.PS;
import java.io.IOException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import com.Test.Exchange.*; // Here class UserInfo is defined
#Path("/ps")
public class TestService {
private UserInfo ui;
public TestService () throws IOException {
ui = new UserInfo();
}
public TestService (String uid) throws IOException {
UserInfo ui = ObjectFileStore.serializeDataIn(uid);
}
public TestService (UserInfo ui) throws IOException {
this.ui = ui;
ObjectFileStore.serializeDataOut(ui);
}
#GET
#Produces(MediaType.TEXT_HTML)
public String sayHelloHTML(#QueryParam("uid") String uid) {
String resource="<h1> Hi '" + uid + "'. </h1>";
return resource;
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public UserInfo postNK(#QueryParam("asid") String asid, UserInfo u) {
return ui;
}
}
Here is the maven dependencies:
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>${version.jersey}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
<version>${version.jersey}</version>
</dependency>
<!-- JSON Support (MOXy) -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>${version.jersey}</version>
</dependency>
Finaly, this is my web.xml file:
<display-name>Test-PS</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Test-PS</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.Test.PS</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Test-PS</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
</web-app>
I am getting the following error when cosuming the post method using the following code:
UserInfo n2k = new UserInfo();
ClientConfig config = new ClientConfig(MOXyJsonProvider.class);
Client client = ClientBuilder.newClient(config);
String targetUrl = "http://localhost:8081/Test-PS";
WebTarget target = client.target(targetUrl);
Invocation.Builder invocationBuilder = target.path("rest").path("ps").queryParam("asid", ASID).request(MediaType.APPLICATION_JSON);
Response response = invocationBuilder.post(Entity.entity(n2k, MediaType.APPLICATION_JSON));
UserInfo ui = response.readEntity(UserInfo.class);
response.close();
client.close();
On the console screen, I see:
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=text/html;charset=utf-8, type=class com.Exchange.UserInfo, genericType=class com.Exchange.UserInfo.
org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:232)
org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:156)
org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1091)
org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:874)
org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:808)
org.glassfish.jersey.client.ClientResponse.readEntity(ClientResponse.java:321)
org.glassfish.jersey.client.InboundJaxrsResponse$1.call(InboundJaxrsResponse.java:115)
org.glassfish.jersey.internal.Errors.process(Errors.java:316)
org.glassfish.jersey.internal.Errors.process(Errors.java:298)
org.glassfish.jersey.internal.Errors.process(Errors.java:229)
org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:389)
org.glassfish.jersey.client.InboundJaxrsResponse.runInScopeIfPossible(InboundJaxrsResponse.java:264)
org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.java:112)
com.AS.AuthenticatorService.Authenticate(AuthenticatorService.java:79)
org.apache.jsp.JSP.Authenticate_jsp._jspService(Authenticate_jsp.java:128)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:458)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:386)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:330)
javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
The server logs says:
"POST /Test-PS/rest/ps?asid=AS1 HTTP/1.1" 500 1081
I have tried lots of solutions provided for this error but non of them works!!!
I want also to add that if the POST method returns a primitive type, e.g., String, instead of the user defined one, i.e., UserInfo, it works!
UserInfo is a serialized class with fields like: username, date of birth, etc..
You are combining to things. Your GET method can use query params but POST don't. Change your post method like this:
#POST("{asid}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public UserInfo postNK(#PathParam("asid") String asid, UserInfo u) {
return ui;
}
Then
Invocation.Builder invocationBuilder = target.path("rest").path("ps/" + ASID).request(MediaType.APPLICATION_JSON);
I used to build rest api with jax-rs so I guess there should be another way to add the ASID param.
Oh, finally I got the issue. It is realy a strange one. There is a method in the UserInfo class called setUserInfo that accepts a parameter of the same class type as shown in the code bellow:
public void setUserInfo(UserInfo ui) {
this.firstName = ui.getFirstName();
this.lastName = ui.getFirstName();
this.dateOfBirth = ui.getDateOfBirth();
this.homeAddress = ui.getHomeAddress();
}
If I delete it, everything works smoothly. Once I re-add it, I got the same exception. It seems that it is forbidden for a class to have a method which accepts a parameter of its type if this class will be sent as a Jersey JSON response.
I do not know why, but this was the issue as I tried it several times and evertime the method is added, the same exception is raised!!! It can be a bug!!!
You may try it as well
I'm using Jackson JSON provider in order to serialize/deserialize JAXRS requests.
In order to set it up I've a jboss-deployment-structure.xml file under WEB-INF folder:
<jboss-deployment-structure>
<deployment>
<exclusions>
<module name="org.jboss.resteasy.resteasy-jackson-provider"/>
<module name="org.jboss.resteasy.resteasy-jettison-provider"/>
</exclusions>
<dependencies>
<module name="org.jboss.resteasy.resteasy-jackson2-provider" services="import"/>
</dependencies>
</deployment>
</jboss-deployment-structure>
So, I've built a test in order to get it:
#RunWith(Arquillian.class)
public class FollowUpActivityDTOSerializationTest
{
#Inject private ObjectMapper mapper;
#Deployment
public static WebArchive createDeployment()
{
System.getProperties().remove("javax.xml.parsers.SAXParserFactory");
EmbeddedGradleImporter importer = ShrinkWrap.create(EmbeddedGradleImporter.class);
WebArchive war = importer.forThisProjectDirectory().importBuildOutput().as(WebArchive.class);
war.addClass(ArquillianAlternative.class);
war.addClass(MockFactory.class);
war.addAsWebInfResource(
new StringAsset("<alternatives><stereotype>com.living.mock.ArquillianAlternative</stereotype></alternatives>"),
"beans.xml"
);
JavaArchive[] libs = Maven.resolver().resolve("org.mockito:mockito-core:2.0.31-beta").withTransitivity().as(JavaArchive.class);
war.addAsLibraries(libs);
return war;
}
#Test
public void emptyJSON()
{
String emptyJSON = "{\"id\": \"id\"}";
try {
FollowUpActivityDTO dto = this.mapper.readValue(emptyJSON, FollowUpActivityDTO.class);
assertNotNull(dto);
assertEquals(dto.getId(), "id");
} catch (IOException e) {
fail(e.getMessage());
}
}
}
The problem is Weld tells me that:
Unsatisfied dependencies for type ObjectMapper with qualifiers #Default
The question, how can I get the jackson provider?
The most important thing here is to get in testing time, the same ObjectMapper that JAX-RS implementation would use.
It's important because I configure some settings related to this object in my provider:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonConfig implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(final Class<?> type) {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setSerializationInclusion(Include.NON_EMPTY);
return mapper;
}
}
So, it's important to use this provider.
So, in order to inject this object I've written this injection code:
#Inject private ContextResolver<ObjectMapper> mapperResolver;
By default, there is no producer for ObjectMapper unless you explicitly provided one. If you replace your injection point with an instantiation of the ObjectMapper, e.g. private ObjectMapper objectMapper = new ObjectMapper() you'll avoid the injection problem.
I am using Jersey Client 2.8 and trying to register my own Jackson configurator which will sets custom ObjectMapper properties.
public class ConnectionFactory {
private final Client client;
public ConnectionFactory() {
ClientConfig clientConfig = new ClientConfig();
clientConfig.property(ClientProperties.FOLLOW_REDIRECTS, true);
clientConfig.property(ClientProperties.CONNECT_TIMEOUT, connTimeoutSec * 1000);
clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeoutSec * 1000);
this.client = ClientBuilder.newBuilder().register(JacksonConfigurator.class).register(JacksonFeature.class).withConfig(clientConfig).build();
// Some more code here ...
}
}
According to Example 8.15 in Jackson Registration documentation, this should register the following JacksonConfigurator class to the client.
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Provides custom configuration for jackson.
*/
#Provider
public class JacksonConfigurator implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public JacksonConfigurator() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
If I deserialize the response from client, the client should ignore unrecognized fields in the response. But I am getting following error -
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "xyz" (class LookupData), not marked as ignorable (3 known properties: "abc", "pqr", "def"])
at [Source: org.glassfish.jersey.message.internal.EntityInputStream#55958273; line: 1, column: 11] (through reference chain: LookupData["xyz"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)
at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:731)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:915)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1292)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1270)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:247)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:118)
at com.fasterxml.jackson.databind.ObjectReader._bind(ObjectReader.java:1232)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:676)
at com.fasterxml.jackson.jaxrs.base.ProviderBase.readFrom(ProviderBase.java:800)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.invokeReadFrom(ReaderInterceptorExecutor.java:257)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:229)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:149)
at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1124)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:851)
... 39 more
Can someone please let me know if I am missing something while registering the JacksonConfigurator class?
Try initializing the Jersey client with a JacksonJsonProvider that's been configured appropriately:
final JacksonJsonProvider jacksonJsonProvider = new JacksonJaxbJsonProvider().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final Client client = ClientBuilder.newClient(new ClientConfig(jacksonJsonProvider));
This was tested with Jackson 2.5.1 and Jersey 2.17
I had same issue and looking for a solution... find your proposition and it works for me :)
My test is very basic / see here below :
Client client = ClientBuilder.newClient()
.register(JacksonFeature.class).register(JacksonConfigurator.class);
WebTarget wt = client.target(REST_SERVICE_URL).path(
"amount");
wt = wt.queryParam("from", "USD");
ConverterResponse cr=wt.request().get(ConverterResponse.class);
If I don't register your JacksonConfigurator, I get same exception as yours :
UnrecognizedPropertyException: Unrecognized field
Because converterResponse defines a subset of REST response object methods.
Thank you very much !
Jersey version 2.30:
Exception:
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "failedLoginCount"
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#77fbf8c9; line: 1, column: 100] (through reference chain: com.xx.XXX["failedLoginCount"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:62)
it works in below code:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
public class ServiceClient {
private Client client;
public ServiceClient() {
ClientConfig config = new ClientConfig();
JacksonJsonProvider
jacksonJsonProvider = new JacksonJaxbJsonProvider().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
config.register(jacksonJsonProvider);
client = ClientBuilder.newClient(config);
}
}