JAX-RS unrmashalling with #XmlIDREF - java

I have been looking into this issue for hours now, probably simple but I don't get it anymore:
I have an entity (Param) which is rendered to json via jax-rs. The entity references another entity (Step).
When writing / reading json, I dont want to see the whole step-entity but merely its id, so I use this code :
#Entity
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Param implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
long id;
#Column(name = "KEYNAME")
String key;
String value;
#XmlIDREF
Step step;
}
Works perfectly for marshalling.
So any GET-request shows me something the following:
{id: 1,
key: "a",
value: "b",
step: 53
}
But when I post some param to the server, it cant map back the numeric id to a step-entity.
I need to provide the unmarshaller with a custom IDResolver. But how can I configure the unmarshaller???? The Jax-RS servlet is doing the marshalling for me. My code looks like that:
#Path("param")
public class ParamRepresentation {
/**
* Retrieves representation of an instance of ParamRepresentation
* #return an instance of Param
*/
#GET
#Produces("application/json")
#Path("{ID}")
public Param getJson(#PathParam("ID") long id) {
return (Param) ctr.find(id, Param.class);
}
#PUT
#Path("{ID}")
#Consumes("application/json")
#Produces("application/json")
public SuccessMessage updateStep(#PathParam("ID") long id, Param p) {
ctr.update(p);
ParamSuccessMessage sm = new ParamSuccessMessage();
sm.setSuccess(true);
sm.setParam(p);
return sm;
}
}
so how can i configure the unmarshaller ?????

I think you've misunderstood the purpose of IDREF in XML schemas. It's there to allow you to refer to another element that is marked as an ID (i.e., with an #XmlID annotation in JAXB) in the same document. You can't use it to refer to an ID elsewhere in the world; for that you'd use a URI (possibly with a fragment identifier part). To do those in JAXB, you use:
#XmlElement // You might be able to omit this part; it's here for clarity
#XmlSchemaType(name = "anyURI")
public URI exampleField;
You then need to work out whether the URI refers to something you know (i.e., resolve the URI and see if it points into yourself) and deal with the fragment identifier. Or do the more common trick of just using a string and don't worry about trying to magically hook everything up in the binding layer. (That works rather well in practice.)

I've done a similar thing using Jersey and xml representations. I used an xml adapter to symmetrically map between the complete child element and the partial (just id) element.
I would annotate the Step entity in your Param entity as follows:
//javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter
#XmlJavaTypeAdapter(PartialStepEntityAdapter.class)
Step step
You would then need to define both the partial Step entity and the Adapter. The PartialStep would be identical to your original step class, but with just the id field.
The PartialStepEntityAdapter would map a Step to a PartialStep when marshalling and a PartialStep to a Step when unmarshalling:
//javax.xml.bind.annotation.adapters.XmlAdapter
public class PartialStepEntityAdapter extends XmlAdapter<PartialStep, Step> {
#Override
public Step unmarshal(PartialStep partialStep) throws Exception {
Step step = new Step();
step.setId(partialStep.getId());
return step;
}
#Override
public PartialStep marshal(Step step) throws Exception {
PartialStep partialStep= new PartialStep();
partialStep.setId(step.getId());
return partialStep;
}
}
Hope that's some help.

Related

How to ignore field/column only when return response from spring boot

I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.

OmniPersistence: usage of VersionedEntity is producing OptimisticLockException

I am experimenting with the library OmniPersistence.
I have a problem using the class org.omnifaces.persistence.model.VersionedEntity. In my code there is a simple entity class City.
#Entity
public class City extends VersionedEntity<Long> {
#Id
#GeneratedValue
private Long id;
private String postalCode;
private String name;
... (getter + setter)
}
There is a REST-Service that exposes the Entity for some client-applications. But every time I want to update an object a javax.persistence.OptimisticLockException is thrown. The problem is that the version attribute is always null. A look in the code of VersionedEntity revealed that there is no setter method, but a comment
// No setter! JPA takes care of this.
I do understand the intention of the absence of the setter method but that is the reason for the exception.
Question
Is my architecture so poor (exposing the entity class in a web-service) or is it maybe reasonable to add a setter method although JPA should handle the value/manipulation of the #Versioned attribute?
Edit (as requested by the comment)
My update method is basically the one in OmniPersistence' BaseEntityService. My service class looks like the following.
#Stateless
public class CityService extends BaseEntityService<Long, City> {
public Long count() {
return super.createLongQuery("select count(c) from City c").getSingleResult();
}
}
My controller is the REST endpoint.
#Path("city")
public class CityEndpoint {
#Inject
private CityService cityService;
#GET #Produces(MediaType.APPLICATION_JSON)
public Response getAll() {
List<City> cities = cityService.list();
return Response.ok(cities).build();
}
#GET #Produces(MediaType.APPLICATION_JSON)
#Path("{id}")
public Response get(#PathParam("id") Long id) {
return Response.ok(cityService.getById(id)).build();
}
#POST #Consumes(MediaType.APPLICATION_JSON)
public Response create(City city) {
cityService.persist(city);
return Response.created(URI.create(String.format("city/%s", Objects.toString(city.getId())))).build();
}
#POST #Consumes(MediaType.APPLICATION_JSON)
#Path("update")
public Response update(City city) {
System.out.println(city);
City updated = cityService.update(city);
return Response.ok(updated).build();
}
#GET
#Path("count")
public Response count() {
return Response.ok(cityService.count()).build();
}
}
The JPA specification document provides an important hint that you must not manipulate the #Version attribute, see section 3.4.2, on page 90
An entity may access the state of its version field or property or
export a method for use by the application to access the version,
but must not modify the version value.
and
The version attribute is updated by the persistence provider runtime
when the object is written to the database.
So the comment (”No setter! JPA takes care of this.“) you find in VersionedEntity is absolutely reasonable. In essence, you should not change (or null) the #Version attribute from higher application levels.
In your case, it seems, you must compensate the ”lost“ (=nulled) version effect, eg by introducing a DTO for City. Otherwise, you will always run into an OptimisticLockException.

SAXException2: Object is found in an IDREF property but this object doesnt have an ID

I am working on a SpringBoot application with Java8 that is a proxy used to put a REST interface on a SOAP service. As a result, I need to convert a number of POJO's to generated SOAP Objects to call the SOAP service.
This all works perfectly, however I have one generated class that is giving an error, because it does not have a defined member variable (i.e. it is of type Object).
ProximityToLocation.java
public class ProximityToLocation extends Locator implements Serializable {
#XmlElement(
required = true
)
#XmlIDREF
#XmlSchemaType(
name = "IDREF"
)
protected Object to;
As you can see with the generated ProximityToLocation class above, it has a member variable named to of type Object. I know from looking at other SOAP calls to the service, that the to object needs to be of generated type GPSLocator.
GPSLocator.java
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(
name = "GPSLocator",
propOrder = {"latitude", "longitude"}
)
public class GPSLocator extends Locator implements Serializable {
private static final long serialVersionUID = 1L;
protected double latitude;
protected double longitude;
As a result, I set the to object to a GPSLocator, but when it tries to call the SOAP service, it gets the following error:
SAXException2: Object
"com.xxx.transit._2008a.location.GPSLocator#6b3cda96" is found in an
IDREF property but this object doesnt have an ID
I am setting the Object to a GPSLocator because that's what I see being set as when I intercept the direct SOAP calls that are working.
Solution
To me it seems like the to object needs an ID, but I don't understand how to set it because the classes are not mine (i.e. I can't change them).
I guess if the GPSLocator class had the following it would work:
#XmlID
#XmlElement
public String getId() {
return id;
}
Problem
The classes referred to above a classes that are generated by a third part who hosts the SOAP service, so I cannot modify these classes.
Question
How do I add the id to the generated class that is not mine?
More Info
Thanks to the comments below, it was suggested that I subclass the GPSLocator class and add the id. So I tried the folloiwng:
public class GPSLocator extends com.xxx.transit._2008a.location.GPSLocator {
protected String id = Double.toString(Math.random()*1000);
#XmlID
#XmlElement
public String getId() {
return id;
}
}
However, even if I use this new class, I still get the same error above.
[com.sun.istack.SAXException2: Object "com.xxx.restosgi.dto.transit.location.GPSLocator#6ea818f2" is found in an IDREF property but this object doesnt have an ID.]
Am I doing something wrong?

#JsonIgnore only for response but not for requests

Is there a way to make #JsonIgnore annotation that it will only ignore during API or HTTP response but not ignore when doing API request.
I also understand that Jackson is used with several frameworks like Restlet, Spring, etc. so what is the generic way of doing this with the ignore annotation. The annotation class does not seem to have any parameters to set this.
Consider the code below:
public class BoxModel extends Model {
#JsonIgnore
private String entityId;
#JsonIgnore
private String secret;
}
In this example, the "secret" field should not be ignored during an API request but should not return back when doing a response, e.g. a JSON response. setting this field to null does not make the field go away, it just sets the value to null and so the field is still on the response payload.
Actually, the standard way is to have 2 separate classes for request and response, so you won't have any problem at all.
If you really need to use the same class for both cases, you can put #JsonInclude(Include.NON_NULL) onto the field instead of #JsonIgnore and set secret = null; before returning the response (as you said in question) - nullable field will be hidden after that. But it's some kind of a trick.
You could potentially find a way to achieve this using Jackson JSON Views by hiding fields when serializing the object.
Example
public class Item {
#JsonView(Views.Public.class)
public int id;
#JsonView(Views.Public.class)
public String itemName;
#JsonView(Views.Internal.class)
public String ownerName;
}
#JsonView(Views.Public.class)
#RequestMapping("/items/{id}")
public Item getItemPublic(#PathVariable int id) {
return ItemManager.getById(id);
}

How to deserialize the following json using Jackson

I have the following json:
{
"id":"myid",
"fields":{
"body":"text body"
}
}
which I want to deserialize into the following Java class:
class TestItem {
private String id;
private String body;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
using the Jackson Json deserializer. This doesn't work because the body field is nested inside a fields inner class.
I can't change the json structure, so is there any way (perhaps using annotations) I can remap of the body field up from TestItem.fields.body to TestItem.body?
Edit:
I should have said this is part of a larger class hierarchy and the aim of the excercise is to reduce the depth of it. In other words, I know that I COULD declare an inner class and then access that, but that is not what I'm trying to achieve.
There are couple of feature requests that (if implemented) would allow limited one-level wrapping/unwrapping. But currently there is no declarative way to do this. And to some degree it is edge case, since this goes into data transformation as opposed to data binding (unfortunately I can't think of good object transformation libs, so there may be bit of gap there).
What is usually done, then, is to do two-phase binding: first into intermediate types (often java.util.Map, or jackson JsonNode (Tree model)); modify those, and then convert from this type to actual result. For example something like this:
JsonNode root = mapper.readTree(jsonSource);
// modify it appropriately (add, remove, move nodes)
MyBean bean = mapper.convertValue(root, MyBean.class);

Categories

Resources