I have two classes:
public class A{
private String property;
private Set<B> bs = new HashSet<B>();
}
public class B{
private String property;
private A owner;
}
I created a basic JAX-RS + Spring boot application, and I want to return A.
Problem is that A contains a Set<B>, so I get an infinite nested level problem.
I found a solution: provide a link instead of the resource itself, this way I can have this:
{ "property" : "value", "bs" : "http://mywebsite/api/a/2/bs" }
And i don't get any nested level problem, since each level is serialized seperately.
How can I implement such a thing in my JAX-RS application? I found nothing about it but I know it's possible since Spring Data Neo4j REST is using it, and it works well.
I can think of the following possible ways how to achieve that.
Convert the set to URI during serialization by using a XmlAdapter
#XmlRootElement
public class A
{
private int id;
private String property;
#XmlJavaTypeAdapter(BsAdapter.class)
private Set<B> bs = new HashSet<B>();
}
public class BsAdapter extends XmlAdapter<URI, Set<B>>
{
#Override
public Set<B> unmarshal(URI v) throws Exception
{
return new HashSet<>();
}
#Override
public URI marshal(Set<B> v) throws Exception
{
return URI.create(
Optional.ofNullable(v)
.filter(b -> !b.isEmpty())
.map(b -> "/" + b.stream().findFirst().get().getOwner().getId() + "/bs")
.orElse(""));
}
}
If the id of A is part of the URI then it can only be retrieved if the set is not empty. The result is a relative URI, since there are no further information available.
As an alternative set the URI manually.
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class A
{
private int id;
private String property;
#XmlTransient
private Set<B> bs = new HashSet<B>();
#XmlElement(name = "bs")
private URI bsUri;
}
And set the URL like this:
#Context
UriInfo uriInfo;
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getA()
{
// ...
a.setBsUri(UriBuilder.fromUri(uriInfo.getAbsolutePath())
.path("a")
.path("{id}")
.path("bs")
.build(2));
return Response.ok().entity(a).build();
}
Or if a link in HTTP Header is also fine, simply do it this way so you do not have to extend class A with the URI.
return Response.ok().entity(a).link(UriBuilder.fromUri(uriInfo.getAbsolutePath())
.path("a")
.path("{id}")
.path("bs")
.build(2), "bs").build();
Related
I'm creating DTO versions of all my entities. I have a problem with an entity that has one Enum value. This is my entity:
#Getter
#Setter
#Table(name = "TIPOS_MOVIMIENTO")
#Entity
public class TipoMovimiento {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
#Convert(converter = TipoMovEnumConverter.class)
private TipoMov tipo;
public String getTipo() {
return tipo.getTipoNombre();
}
#OneToMany(mappedBy = "tipoMov")
private List<Movimiento> movimientos;
No, I don't have #Enumerated because I followed a tutorial: "JPA 2.1 Attribute Converter – The better way to persist enums" and I had to remove it. I use a converter, as you can see.
This is my DTO of the previous entity:
#Getter
public class TipoMovimientoDto implements DtoEntity {
#Convert(converter = TipoMovEnumConverter.class) //I don't even know if write this here!!!!!
private TipoMov tipo;
}
The reason why I've followed that tutorial ↑ is because I wanted to write in database the variable values (tipoNombre) of enum (not enum name itself) because format. I want to store it in DB with accents, and I want to show it in Postman or whatever REST Client app with accents! Don't tell me anything about format it in front-end because this project is only back-end :(
Well, I think you will understand what I found with this with a image:
If you know a better way to do this, let me know, but this is not my problem now.
Let me show you the Enum:
public enum TipoMov {
INGRESO("Ingreso"),
PRESTAMO("Prestamo"),
PAGO("Pago"),
AMORTIZACION("Amortización"),
INTERES("Interés");
private String tipoNombre;
public String getTipoNombre() {
return tipoNombre;
}
TipoMov(String tipoNombre) {
this.tipoNombre = tipoNombre;
}
public static TipoMov fromDBName(String tipoNombre) {
switch (tipoNombre) {
case "Ingreso":
return TipoMov.INGRESO;
case "Préstamo":
return TipoMov.PRESTAMO;
case "Pago":
return TipoMov.PAGO;
case "Amortización":
return TipoMov.AMORTIZACION;
case "Interés":
return TipoMov.INTERES;
default:
throw new IllegalArgumentException("ShortName [" + tipoNombre
+ "] not supported.");
}
}
}
The problem is that I can't get the output in Postman if I convert this to DTO version. I get the appropiate output without DTO. I'm using REST services. Let me show you the services and controller.
(They include both versions, without DTO and with DTO (that is not working)).
ServiceImpl
#Service
public class TipoMovimientoServiceImpl implements TipoMovimientoService {
#Autowired
TipoMovimientoRepository repository;
#Autowired
DtoUtils dtoUtils;
public List<DtoEntity> findAllDto() {
List<TipoMovimiento> tiposMovimiento = repository.findAll();
List<DtoEntity> tiposMovimientoDto = new ArrayList();
for (TipoMovimiento tipoMovimiento : tiposMovimiento) {
DtoEntity tipoMovimientoDto= dtoUtils.convertToDto(tipoMovimiento, new TipoMovimientoDto());
tiposMovimientoDto.add(tipoMovimientoDto);
}
return tiposMovimientoDto;
}
public List<TipoMovimiento> findAll() {
List<TipoMovimiento> tiposMovimiento = repository.findAll();
return tiposMovimiento;
}
}
Service Interface
public interface TipoMovimientoService {
List<DtoEntity> findAllDto();
List<TipoMovimiento> findAll();
}
Controller:
#RestController
public class PruebasController {
#Autowired
TipoMovimientoService service;
#GetMapping("tiposmovdto")
public ResponseEntity <List <DtoEntity> > findAllDto() {
return ResponseEntity.ok(service.findAllDto());
}
#GetMapping("tiposmov")
public ResponseEntity <List <TipoMovimiento> > findAll() {
return ResponseEntity.ok(service.findAll());
}
}
As I said, the nonDto version works perfectly, but DTO version no. Is not the fault of DTO converter, because I have other REST services (that don't have enums) working perfectly with DTO. This is a problem about making compatible Enum and Dto!
I got it!!! I never thought this would work.
#Getter
public class TipoMovimientoDto implements DtoEntity {
private TipoMov tipo;
}
I just changed in the code above (Dto):
private TipoMov tipo;
to
private String tipo;
I can't explain how Enum from Entity could have been converted to DTO, using String instead Enum... But that worked!
In case you have the same problem... this is my Attribute Converter between Enum and String
#Converter(autoApply = true)
public class TipoMovEnumConverter implements AttributeConverter<TipoMov, String> {
public String convertToDatabaseColumn(TipoMov tipoMov) {
return tipoMov.getTipoNombre();
}
public TipoMov convertToEntityAttribute(String dbData) {
return dbData == null ? null : TipoMov.fromDBName(dbData);
}
}
Is still necessary to use it in Entity class, above of the enum variable:
#Convert(converter = TipoMovEnumConverter.class)
But not necessary in DTO. Just use String instead Enum in DTO!
I have trying to implement MapStruct mapping library. I have made samples and for simple mapping it works fine but I stucked in 1 issue.
I have 2 jpa entity classes which have two way relationships. One is in another and another is in one. It creates cyclic mapping issue so MapStruct throws StackOverflow error.
I have created minimal code to reproduce the case on github.
Sample code:
public class A {
private Long id;
private String name;
private B bData;
//getter-setter
}
public class B {
private Long id;
private String name;
private Set<A> aData;
//getter-setter
}
DataGenerator
public class DataGenerator {
public static A generateData(){
A a = new A();
a.setId(1L);
a.setName("foo");
B b = new B();
b.setId(2L);
b.setName("bar");
A a2 = new A();
a2.setId(3L);
a2.setName("john");
a2.setbData(b);
A a3 = new A();
a3.setId(4L);
a3.setName("doe");
a3.setbData(b);
Set<A> aData = new HashSet<A>();
aData.add(a2);
aData.add(a3);
b.setaData(aData);
a.setbData(b);
return a;
}
}
Mapper
#Mapper
public interface CustomMapper {
CustomMapper INSTANCE = Mappers.getMapper(CustomMapper.class);
ADto atoADto(A a);
}
App
public class AppMain {
public static void main(String[] args) {
A a = DataGenerator.generateData();
ADto aDto = CustomMapper.INSTANCE.atoADto(a);
System.out.println(aDto.getId());
}
}
Dto/Destination classes are same as original source classes.
The main is cyclic/recursive mapping issue which causes stackoverflow error.
Same thing working with spring BeanUtils.copyProperties but I want to implement MapStruct. Currently I am thinking to replace spring BeanUtils with MapStruct.
any suggestions?
See this mapstruct github issue for the solution, which is to ignore the field causing the recursion. I quote:
"You can achieve it with the #Qualifier. You can use #Named and qualifiedByName, or you can use your own custom #CountryWithoutCities qualifier with qualifiedBy.
Class country{
String id;
String name;
List<City> cities;
}
Class City{
String id;
String name;
Country country;
}
#Mapper(uses = CityMapper.class)
interface CountryMapper {
#Mapping( target = "cities", qualifiedByName = "noCountry")
CountryDto toDto(Country country);
#CountryWithoutCities
#Mapping( target = "cities", ignore = true)
CountryDto toDtoWithoutCities(Country country);
}
#Mapper(uses = CountryMapper.class)
interface CityMapper {
#Named( "noCountry" )
#Mapping( target = "country", ignore = true)
CityDto toDtoWithoutCountry(City city);
#Mapping( target = "country", qualifiedBy= CountryWithoutCities.class)
CityDto toDto(City city);
}
There's an example here in the MapStruct repo how to deal with cycles and recursion. Basically you need to keep track of state. The example makes use of a context object to do so.
For a school assignment, I need to parse a CSV into a Bean and present it in a JavaFX GUI later. I decided to use the Library opencsv, which worked fine.
But now, I would like to parse the attributes directly into SimpleObjectProperties. How do I do that? Unfortunately, I couldn't find any further information.
Code looks like this:
public class Phone {
#CsvBindByName(column = "ENTITY_ID")
private SimpleIntegerProperty entityId;
#CsvBindByName(column = "OPERATING_COMPANY")
private SimpleStringProperty operatingCompany;
When I run the code, I get a CsvDataTypeMismatchException (Conversion of 1006 to javafx.beans.property.SimpleIntegerProperty failed).
Any help much appreciated, thank you!!
Looking at the documentation it looks like you can create CustomConverts for each type of property you have; the example they have on the documentation page, this is the start to an IntegerPropertyConverter.
public class IntegerPropertyConverter extends AbstractCsvConverter {
#Override
public Object convert(String value) {
return new SimpleIntegerProperty(Integer.parseInt(value));
}
#Override
public String convertToWrite(Object value) {
IntegerProprety prop = (IntegerProperty) value;
return String.format("%d", prop.get());
}
}
Then you'd use:
#CsvCustomBindByName(column = "ENTITY_ID", converter = IntegerPropertyConverter.class)
private SimpleIntegerProperty entityId;
If you need to create your properties using the longer format, you will need to override other methods in the AbstractBeanField, such as public final void setFieldValue(T bean, String value, String header) where you can actually use the bean to create the
There is no easy way around this.
You keep your Phone a POJO and map the entire object as a property
private SimpleObjectProperty<Phone> phone = new SimpleObjectProperty<Phone>();
Or you could add properties to the Phone
public class Phone {
#CsvBindByName(column = "ENTITY_ID")
private Integer entityId;
private final SimpleIntegerProperty entityIdProperty;
public Phone() {
entityIdProperty = new SimpleIntegerProperty();
entityIdProperty.addListener((o, oldValue,newValue)->{
entityId = newValue.intValue();
});
}
public Integer getEntityId() {
return entityId;
}
public void setEntityId(Integer entityId) {
this.entityId = entityId;
entityIdProperty.set(entityId);
}
public SimpleIntegerProperty getEntityIdProperty() {
return entityIdProperty;
}
// ...
}
If you do not need this object to have a bidirectional binding you could skip the listener.
There are other possibilities too, like having Methods and constructors to convert from a Phone to a PhoneFX (with properties instead of simple types) and viceversa.
I have an entity that owns another entity:
//psuedocode
public class ClassA{
private String name;
#OneToOne
private ClassB classb;
}
public class ClassB{
private String thing1;
private String thing2;
private String thing3;
}
When I retrieve ClassA objects, I don't want to see ClassB.thing3, but I do want to see thing1 and thing 2:
{
"name":"classa",
"classb":{
"thing1":"hi",
"thing2":"there"
}
}
But if I query for ClassB I want to see everything:
{"thing1":"hi",
"thing2":"there",
"thing3":"joseph"}
So I can't just put an ignore annotation over thing3, because then I'll ignore it on the second fetch. I tried a Converter<ClassB>, but that forces me to implement toString() and fromString() for the JSON, which dies on converting the JSON object to Java-side (the converter expects a String, but gets the object instead).
I want to avoid building/parsing the JSON object myself if possible to let my json provider do the work, if possible. I'm on Johnzon.
This is possible, you need to use #NamedEntityGraph,
This should help, http://www.thoughts-on-java.org/jpa-21-entity-graph-part-1-named-entity/
Something like this should be possible by querying using SELECT NEW, but you're going to need some new Classes for that ... and won't be passing your entities directly to JSON.
new Classes:
(pseudocode)
class ResultB {
String thing1;
String thing2;
public ResultB(ClassB classB) {
this.thing1 = classB.thing1;
this.thing2 = classB.thing2;
}
}
class ResultA {
String name;
ResultB resultB;
public ResultA(ClassA classA) {
this.name=classA.name;
this.resultB=new ResultB(classA);
}
}
Query:
select new ResultA(a) from ClassA a fetch join a.classB;
Then you can pass ResultA instead of ClassA to JSON.
PS: As mentioned in the comment above, I don't think NamedEntityGraphs are the way to go here
I would always fetch all the data from the database and let the filtering to be done by the JSON provider if you want to serialize it anyway. If you use Jackson you can simply add views to your fields:
public class ClassA {
#JsonView(Views.AlwaysInclude.class)
private String name;
#JsonView(Views.AlwaysInclude.class)
#OneToOne
private ClassB classb;
}
public class ClassB {
#JsonView(Views.AlwaysInclude.class)
private String thing1;
#JsonView(Views.AlwaysInclude.class)
private String thing2;
private String thing3;
}
public class Views {
public static class AlwaysInclude {
}
}
Later when you serialize your object you just need to use your view:
String result = mapper
.writerWithView(Views.AlwaysInclude.class)
.writeValueAsString(new ClassA());
When you want to serialize only ClassB then you shouldn't use views.
String result = mapper.writeValueAsString(new ClassB());
I have an Object Ticket which has an element totalNoOfTickets associated with it. I use this object Ticket in many methods of my restful service. However, i only need the element 'totalNoOfTickets' for one method only and not others....
For example, in one of my method, i say...
Ticket.setTotalNoOfTickets(7);
I do not use this totalNoOfTickets in other methods. Hence,i do not set it in other methods.
Now in my Ticket.java...I have
public class Ticket
extends ActionType {
#XmlElementWrapper(name = "Tickets")
#XmlElement(name = "Ticket")
protected List<Ticket> tickets;
#XmlElement(name = "TotalNumOfTickets")
protected int totalNumofTickets;
public int getTotalNumofTickets() {
return totalNumofTickets;
}
public void setTotalNumofTickets(int totalNumoftokens) {
this.totalNumofTickets = totalNumoftickets;
}
In method responses, where i set the totalNumoftickets, response contains:
<TotalNumOfTickets>7</TotalNumOfTickets>
But, even in responses, where i do not need totalNumoftickets, reponse contains
<TotalNumOfTickets>0</TotalNumOfTickets>
Is there any way in Jax-B where it would not have
<TotalNumOfTickets>0</TotalNumOfTickets>
in the response of methods, where i do not set totalNumoftickets?...can i use that element optionally
By default a JAXB (JSR-222) implementation will not marshal null values. You could change totalNumofTickets field/property to an Integer and have the value null when you don't want to marshal it.
public class Ticket
extends ActionType {
#XmlElementWrapper(name = "Tickets")
#XmlElement(name = "Ticket")
protected List<Ticket> tickets;
#XmlElement(name = "TotalNumOfTickets")
protected Integer totalNumofTickets;
public Integer getTotalNumofTickets() {
return totalNumofTickets;
}
public void setTotalNumofTickets(Integer totalNumoftokens) {
this.totalNumofTickets = totalNumoftickets;
}
}
For More Information
http://blog.bdoughan.com/2012/04/binding-to-json-xml-handling-null.html