Jersey issue with unmarshalling responce entity with HashSet field - java

I have a problem with Jersey (2.13). I suppose that the problem related to un-marshalling mechanism. My User entity has a field - collection of Role entities. When I try to get it, I get an error.
Here is my classes
#XmlRootElement(name = "user")
public class User {
private Set<Role> roleSet;
// other fields omitted
#XmlElementWrapper(name = "roles")
#XmlElement(name = "role")
public Set<Role> getRoleSet() { /* compiled code */ }
// all other getters with simple JAXB annotations #XmlElement omitted
}
Role
#XmlRootElement(name = "role")
public class ClientRole {
private String name;
private boolean active;
private String id;
#XmlElement(name = "name")
public String getName() { /* compiled code */ }
// all getters with JAXB annotations omitted
}
And my Jersey client code
HttpAuthenticationFeature authenticationFeature = HttpAuthenticationFeature.basic("freddy", "qwerty");
Client client = ClientBuilder
.newClient()
.register(authenticationFeature)
.register(JacksonFeature.class);
WebTarget target = client.target("http://localhost:4444/server/");
Response resp = target
.path("/api/users/freddy")
.request()
.accept(MediaType.APPLICATION_JSON)
.get();
User entity = resp.readEntity(User.class);
System.out.println(entity);
How can I fix that error? Or maybe I just forgot to register some new feature (for the new Jersey release 2.13)?
Exception in thread "main" javax.ws.rs.ProcessingException: Error reading entity from input stream.
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:866)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:783)
at org.glassfish.jersey.client.ClientResponse.readEntity(ClientResponse.java:326)
at org.glassfish.jersey.client.InboundJaxrsResponse$1.call(InboundJaxrsResponse.java:111)
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:228)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:399)
at org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.java:108)
at com.test.CreateUserExample.main(CreateUserExample.java:36)
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "roles" (class com.server.dto.auth.User), not marked as ignorable (6 known properties: "enabled", "statusId", "role", "username", "emailAddress", "password"])
at [Source: org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream#34cdeda2; line: 1, column: 141] (through reference chain: com.server.dto.auth.User["roles"])
at com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:51)
at com.fasterxml.jackson.databind.DeserializationContext.reportUnknownProperty(DeserializationContext.java:671)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:771)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1297)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1275)
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:1233)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:677)
at com.fasterxml.jackson.jaxrs.base.ProviderBase.readFrom(ProviderBase.java:777)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.invokeReadFrom(ReaderInterceptorExecutor.java:264)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:234)
at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:154)
at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1124)
at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:851)
UPDATE
Here is my dependencies
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>2.13</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.13</version>
</dependency>
UPDATE 2
String entity = resp.readEntity(String.class);
{"name":"Bob","enabled":true,"roles":[{"name":"ADMIN","active":false,"id":12313214},{"name":"USER","active":true,"id":45353}]}

The problem is that while serializing, you're probably using MOXy which adds the additional wrapping element (roles in this case) which is not fully supported by Jackson.
You can either use MOXy for de-serialization at the client side
Client client = ClientBuilder
.newClient()
.register(authenticationFeature)
.register(MoxyJsonFeature.class);
or to slightly change your model class so that JAXB serializer (MOXy) produces something that Jackson is able to de-serialize
#XmlElement(name = "roles")
public Set<Role> getRoleSet() {
return roleSet;
}
Finally, you can let Jersey use Jackson on the server side by registering JacksonFeature:
#ApplicationPath("/")
public class TestApplication extends ResourceConfig {
public TestApplication() {
register(TestResource.class);
register(JacksonFeature.class);
}
}

Related

Include/exclude Attributes in json response from application.yml

I am using JHipster(spring boot) to generate my project. I would like to hide/show fields in JSON from application.yml. for exemple:
I have the following class
#Entity
#Table(name = "port")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Port implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "city")
private String city;
#Column(name = "description")
private String description;
//getters & setters
}
My GET method return a response like:
{
"id": 1,
"city": "boston",
"description": "test test"
}
I would like to be able to include/exclude some fields from application.yml (since i don't have application.properties) otherwise to have something like:
//application.yml
include: ['city']
exclude: ['description']
in this exemple my json should look like:
{
"id": 1,
"city": "boston",
}
for exemple if I have 40 fields and I need to hide 10 and show 30 I just want to put the 10 I want to hide in exclude in application.yml without go everytime to change the code. I guess #jsonignore hide fields but I don't know how to do it from application.yml
Sorry for not explaining well. I hope it's clear.
Thank you in advance for any suggestion or solution to do something similar
Spring boot by default uses Jackson JSON library to serialize your classes to Json. In that library there is an annotation #JsonIgnore which is used precisely for the purpose to tell Json engine to egnore a particular property from serialization/de-serialization. So, lets say in your entity Port you would want to exclude property city from showing. All you have to do is to annotate that property (or its getter method) with #JsonIgnore annotation:
#Column(name = "city")
#JsonIgnore
private String city;
You can try to create a hashmap in your controller to manage your HTTP response.
Map<String, Object> map = new HashMap<>();
map.put("id", Port.getId());
map.put("city", Port.getCity());
return map;
Basically you don't expose your Port entity in your REST controller, you expose a DTO (Data Transfer Object) that you value from your entity in service layer using a simple class (e.g PortMapper). PortDTO could also be a Map as suggested in other answer.
Your service layer can then use a configuration object (e.g. PortMapperConfiguration) that is valued from application.yml and used by PortMapper to conditionally call PortDTO setters from Port getters.
#ConfigurationProperties(prefix = "mapper", ignoreUnknownFields = false)
public class PortMapperConfiguration {
private List<String> include;
private List<String> exclude;
// getters/setters
}
#Service
public class PortMapper {
private PortMapperConfiguration configuration;
public PortMapper(PortMapperConfiguration configuration) {
this.configuration = configuration;
}
public PortDTO toDto(Port port) {
PortDTO dto = new PortDTO();
// Fill DTO based on configuration
return dto;
}
}

Deserialize JSON to polymorphic types Spring boot

I am working on an e-policy project where i need to save different types of policies. For simplicity i am considering only two types "LifeInsurance" and "AutoInsurance". What i want to achieve is if the JSON request to create policy contains "type":"AUTO_INSURANCE" then the request should be mapped to AutoInsurance.class likewise for LifeInsurance but currently in spring boot app the request is getting mapped to parent class Policy eliminating the specific request fields for auto/Life insurance. The domain model i have created is as below.
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#Getter
#Setter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({ #Type(value = AutoInsurance.class, name = "AUTO_INSURANCE"),
#Type(value = LifeInsurance.class) })
public class Policy {
#Id
#GeneratedValue
private Long id;
private String policyNumber;
#Enumerated(EnumType.STRING)
private PolicyType policyType;
private String name;
}
My AutoInsurance class is below.
#Entity
#NoArgsConstructor
#Getter
#Setter
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonTypeName(value = "AUTO_INSURANCE")
public class AutoInsurance extends Policy {
#Id
#GeneratedValue
private Long id;
private String vehicleNumber;
private String model;
private String vehicleType;
private String vehicleName;
}
Below is LifeInsurance type child class
#Entity
#NoArgsConstructor
#Getter
#Setter
#JsonTypeName(value = "LIFE_INSURANCE")
public class LifeInsurance extends Policy {
#OneToMany(mappedBy = "policy")
private List<Dependents> dependents;
private String medicalIssues;
private String medication;
private String treatments;
}
To save the policy details, I am sending JSON request from UI with a "type" property indicating the type of insurance in the request.
When i run the below test method, JSON request gets mapped to the correct child class as required.
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper map = new ObjectMapper();
String s = "{\"id\": 1,\"policyNumber\": \"Aut-123\",\"type\": \"AUTO_INSURANCE\",\"policyType\": \"AUTO_INSURANCE\",\"name\": null,\"address\": null,\"contact\": null,\"agentNumber\": null,\"agentName\": null,\"issuedOn\": null,\"startDate\": null,\"endDate\": null,\"vehicleNumber\": \"HR\",\"model\": null,\"vehicleType\": \"SUV\",\"vehicleName\": null}";
Policy p = map.readValue(s, Policy.class);
System.out.println(p.getClass());
//SpringApplication.run(EPolicyApplication.class, args);
}
But when i run the same in Spring boot in a RESTController postmapping, I am getting a PArent class object instead of the child class object.
#RestController
#RequestMapping("/policy")
public class PolicyController {
#PostMapping
public void savePolicy(Policy policy) {
System.out.println(policy.getClass());
}
}
I can get the JSON as string, autowire objectmapper and parse manually but i want to understand if its a known issue and if anyone else has faced the same with Spring boot. I have searched for solutions on this but i got was solution to deserializing to polymorphic classes but nothing related to issue with Spring boot.
In your method you haven't annotated the Policy method argument with #RequestBody. Which leads to Spring creating just an instance of Policy instead of using Jackson to convert the request body.
#PostMapping
public void savePolicy(#RequestBody Policy policy) {
System.out.println(policy.getClass());
}
Adding the #RequestBody will make that Spring uses Jackson to deserialize the request body and with that your annotations/configuration will be effective.

406 error while retrieving the details in REST and hibernate

I am trying to fetch the details of countries in XML using REST and hibernate. But when hitting the URL below is the error I get. I have set the header accepts in the request correctly to xml.
The resource identified by this request is only capable of generating
responses with characteristics not acceptable according to the request
"accept" headers.
CONTROLLER
#RequestMapping(value = "/getAllCountries", method =
RequestMethod.GET,produces="application/xml",
headers = "Accept=application/xml")
public List<Country> getCountries() throws CustomerNotFoundException{
List<Country> listOfCountries = countryService.getAllCountries();
return listOfCountries;
}
MODEL
#XmlRootElement (name = "COUNTRY")
#XmlAccessorType(XmlAccessType.FIELD)
#Entity
#Table(name="COUNTRY")
public class Country{
#XmlAttribute
#Id
#Column(name="id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
int id;
#XmlElement
#Column(name="countryName")
String countryName;
#XmlElement
#Column(name="population")
long population;
public Country() {
super();
}
SERVICE
#Transactional
public List<Country> getAllCountries() {
return countryDao.getAllCountries();
}
DAO
public List<Country> getAllCountries() {
Session session = this.sessionFactory.getCurrentSession();
List<Country> countryList = session.createQuery("from Country").list();
return countryList;
}
Can someone please help..
Use spring recommended jackson-dataformat-xml library in pom.xml. It will do automatic XML transformation for you as soon as JAXB library (which is inbuilt in JDK >= 1.6) is present, even without XML annotations. However, you can use #JacksonXml.. annotations to given desired structure to your xml.
To achieve the desired result here, I will create a wrapper class and update my controller as below:
//pom.xml
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
//wrapper class
#JacksonXmlRootElement(localName = "countries")
#Data //lombok
#AllArgsConstructor //lombok
public class Countries {
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "country")
private List<Country> countries;
}
//controller
#RequestMapping(value = "/getAllCountries", method = RequestMethod.GET)
public Object getCountries() throws CustomerNotFoundException{
return new Countries(countryService.getAllCountries());
}
NOTE: XML Wrapper class is not required here. Spring will just do fine with the default array transformation using <List><Items>, but its recommended to shape your XML as per desired structure.

Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Status"

I am receiving following error message, I have Status class but it is not being recognized. I've got no idea how to proceed and could not find an answer online.
Error
org.springframework.http.converter.HttpMessageNotReadableException: Could
not read JSON: Unrecognized field "Status" (class
com.myproject.ticket.EventsResponse), not marked as ignorable (3 known
properties: "events", "status", "page"])
....
Caused by:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "Status" (class com.myproject.ticket.EventsResponse),
not marked as ignorable (3 known properties: "events", "status", "page"])
EventsResponse
#XmlRootElement(name = "EventsResponse")
#XmlAccessorType(XmlAccessType.FIELD)
public class EventsResponse {
#XmlElement(name = "Status")
private Status status;
#XmlElement(name = "Paging")
private Page page;
#XmlElementWrapper(name="Events")
#XmlElement(name = "Event")
private List<Event> events;
.....
Status
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Status {
#XmlElement(name = "Version")
private double version;
#XmlElement(name = "TimeStampUtc")
private Date timeStampUtc;
#XmlElement(name = "Code")
private int code;
#XmlElement(name = "Message")
private String message;
#XmlElement(name = "Details")
private String details;
Response
<EventsResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Status>
<Version>2.0</Version>
<TimeStampUtc>2016-06-11T09:32:21</TimeStampUtc>
<Code>0</Code>
<Message>Success</Message>
<Details />
</Status>
<Paging>
<PageNumber>1</PageNumber>
<PageSize>50</PageSize>
<PageResultCount>15</PageResultCount>
<TotalResultCount>15</TotalResultCount>
<TotalPageCount>1</TotalPageCount>
</Paging>
<Events>
<Event>
I added following to Status but I am still receiving the same error.
#XmlElement(name = "Status")
#JacksonXmlProperty(localName = "Status")
private Status status;
I failed to reconstruct your issue.
I created a test project github here that has Jackson configuration and JAXB annotations that meet your needs.
I added dependencies to jackson-dataformat-xml and woodstox-core-asl as your Stax implementations (in my test project I am using Jackson 2.6.6. ,Spring 4.2.6)
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
Configure the Jackson2ObjectMapperBuilder to use both Jackson and JAXB annotations. This is a Spring-boot example to convert to Simple Spring-MVC look here
#SpringBootApplication
public class EventAppConfiguration {
public static void main(String[] args) {
SpringApplication.run(EventAppConfiguration.class, args);
}
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
b.indentOutput(true)
//Enable Introspects for both Jackson and JAXB annotation
.annotationIntrospector(introspector())
//Use CamelCase naming
.propertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss"));
return b;
}
#Bean
public AnnotationIntrospector introspector(){
AnnotationIntrospector primary = new JacksonAnnotationIntrospector();
AnnotationIntrospector secondary = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
AnnotationIntrospector pair = AnnotationIntrospector.pair(primary, secondary);
return pair;
}
}
Note the use of
PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE
it will save you the need to specify alternative naming for first letter capitalization and will require the use for JAXB annotation only for warping and renaming for example my EventsResponse will look like:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class EventsResponse {
private Status status;
#XmlElement(name = "Paging")
private Page page;
#XmlElementWrapper(name = "Events")
#XmlElement(name = "Event")
private List<Event> events;
...
}
You have two options, assuming you are using Jackson to deserialize your XML objects. The simplest is to use Jackson's own XML annotations instead of or as well as the JAXB #XmlElement annotations. For example:
#XmlElement(name = "Status")
#JacksonXmlProperty(localName = "Status")
private Status status;
(The #XmlElement annotation is in the jackson-dataformat-xml package in Maven - the version should match your other Jackson package versions.)
The alternative is to register an AnnotationIntrospector as part of your deserialization chain - ie. (from a unit test):
XmlMapper mapper = new XmlMapper();
AnnotationIntrospector aiJaxb = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
mapper.setAnnotationIntrospector(aiJaxb);
// EVENTS_RESPONSE is the incoming XML
EventsResponse response = mapper.readValue(EVENTS_RESPONSE, EventsResponse.class);
This recognises the #XmlElement annotation. There are more details in this answer if you need to include this as part of a Spring configuration, for example.
(In order to use the JaxbAnnotationIntrospector class, you will need the jackson-module-jaxb-annotation module from Maven.)

Resolve JPA association while deserializing JAX-RS JSON Object

I have some problems combining JAX-RS (in JBoss Wildfly container) with JSON payload and with JPA assiciations. Follwoing scenario:
There are two JPA entities
#Entity
class Organization {
#Id
private long id;
private String name;
}
#Entity
class Empolyee {
#Id
private long id;
#Id
private String name;
#ManyToOne(fetch = FetchType.EAGER)
#JsonProperty("organization_id")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = true)
private Organization organization;
}
Next I have a JAX-RS service to create a new Employee with the following signature:
#POST
#Consumes({ "application/json" })
public Response create(final Employee employee) {
}
The JSON for a new Employee sent by the client looks like:
{
"name" : "Sam Sample",
"organization_id" : 2
}
My problem is that this JSON (obviously) cannot be deserialized into an instance of "Employee" since the mapping of the "organization_id" to the corresponding JPA Entity fails.
How can I configure JAX-RS (or the Jackson JSON mapper) to interpret the "orgainization_id" as the id of a JPA entity?
With Jackson you could define a custom deserializer (see this and this) that fetches the Organization entity based on the value of organization_id.
Edit:
Check this example for how to configure JAX-RS using Jackson with a custom deserializer programmatically (as opposed to via annotations):
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class JacksonContextResolver implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper;
public JacksonContextResolver() throws Exception {
this.objectMapper = new ObjectMapper().configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
SimpleModule myModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
myModule.addDeserializer(MyType.class, new MyTypeDeserializer());
objectMapper.registerModule(myModule);
}
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}

Categories