Spring MVC Generics Object Binding/Type Conversion - spring

I'm looking for a way to wrap my models or DTOs with a generic class to add a 'selected' Boolean property and be able to bind the object in my POST method controller.
Like this:
public class RowForm<T> implements Serializable {
private static final long serialVersionUID = 1L;
private T model;
private Boolean selected=false;
public RowForm() {
super();
}
public RowForm(T model) {
super();
this.model = model;
}
public T getModel() {
return model;
}
public void setModel(T model) {
this.model = model;
}
public Boolean getSelected() {
return selected;
}
public void setSelected(Boolean selected) {
this.selected = selected;
}
}
public class ProductsForm implements Serializable{
private static final long serialVersionUID = 1L;
private RowForm<Product> row;
//...other stuff and getters/setters
}
and use it like this:
#PostMapping ("/postProduct")
public String POSTproduct(Model model, #ModelAttribute ProductsForm pf)
{
....
}
But I'm stuck with the conversion...
When I call pf.getRow() it returns a plain Object, not a RowForm<Product>.
How can I implement a ConversionService/PropertyEditor to bind my posted data to my extended generic object?

Why not use a abstract base class with the selected property, and extend it for your DTOs?

Related

Can I return DTO and domain entities from services?

I have a spring-boot application and I use DTO like that:
Service
#Service
public class UnitOfMeasureServiceImpl implements IUnitOfMeasureService {
private final IUnitsOfMeasureRepository unitOfMeasureRepository;
#Autowired
public UnitOfMeasureServiceImpl(IUnitsOfMeasureRepository unitOfMeasureRepository) {
this.unitOfMeasureRepository = unitOfMeasureRepository;
}
#Override
public UnitOfMeasureDTO getUnitOfMeasureById(UUID id) {
Optional<UnitOfMeasure> optionalUnitOfMeasure = unitOfMeasureRepository.findById(id);
if (!optionalUnitOfMeasure.isPresent()){
// throw new ComponentNotFoundException(id);
return null;
}
return UnitOfMeasureDTO.factory(optionalUnitOfMeasure.get());
}
dto:
#Data
#JsonInclude(JsonInclude.Include.NON_NULL)
public class UnitOfMeasureDTO {
private String id;
private String name;
private String description;
private String sourceInfoCompanyName;
private String originalId;
public static UnitOfMeasureDTO factory(UnitOfMeasure unitOfMeasure) {
UnitOfMeasureDTO dto = new UnitOfMeasureDTO();
dto.id = unitOfMeasure.getId().toString();
dto.name = unitOfMeasure.getName();
dto.description = unitOfMeasure.getDescription();
dto.sourceInfoCompanyName = unitOfMeasure.getSourceInfo().getSourceCompany().getName();
dto.originalId = unitOfMeasure.getOriginalId();
return dto;
}
}
controller:
#RestController
#RequestMapping(UnitOfMeasureController.BASE_URL)
public class UnitOfMeasureController {
public static final String BASE_URL = "/api/sust/v1/unitOfMeasures";
private final IUnitOfMeasureService unitOfMeasureService;
public UnitOfMeasureController(IUnitOfMeasureService unitOfMeasureService) {
this.unitOfMeasureService = unitOfMeasureService;
}
#GetMapping(path = "/{id}")
#ResponseStatus(HttpStatus.OK)
public UnitOfMeasureDTO getUnitOfMeasureDTO(#PathVariable("id") UUID id) {
UnitOfMeasureDTO unitOfMeasureDTO = unitOfMeasureService.getUnitOfMeasureById(id);
return unitOfMeasureDTO;
}
So in my service I have getUnitOfMeasureById(UUID id) that return a UnitOfMeasureDTO.
Now I need to call, from another service, getUnitOfMeasureById(UUID id) that return the domain entity UnitOfMeasure. I think it's correct to call a service method from another service (not a controller method!) and the separation between business logic is at the service layer. So is it correct to have 2 methods: getUnitOfMeasureDTOById and getUnitOfMeasureById in the service? (getUnitOfMeasureDTOById call getUnitOfMeasureById to avoid code duplication)

java.lang.ClassCastException: Entity A incompatible with Entity B

I'm trying to get proficient in generics in Java. I have some 100 entities that use the same findBy method in JPA interface. Almost all of them require a call to AwrSnapDetails so instead of adding
#ManyToOne private AwrSnapDetails awrSnapDetails; to each Entity, I've created a HelperEntity class and using #Embedded annotation. Now I have gotten to the point in coding where I can't figure out what I am doing wrong and how to go about resolving this error.
Entity
#Entity
public class AwrMemStats {
String description;
double begin_;
double end_;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Embedded
private HelperEntity helperEntity;
public AwrMemStats() {
}
public AwrMemStats(String description, double begin_, double end_, AwrSnapDetails awrSnapDetails) {
this.description = description;
this.begin_ = begin_;
this.end_ = end_;
HelperEntity h = new HelperEntity(awrSnapDetails);
}
// getters/setters removed for clarity
}
Embedded Entity
#Embeddable
public class HelperEntity implements Serializable{
private static final long serialVersionUID = 1L;
#ManyToOne
AwrSnapDetails awrSnapDetails;
public HelperEntity() {
}
public HelperEntity(AwrSnapDetails awrSnapDetails) {
super();
this.awrSnapDetails = awrSnapDetails;
}
public AwrSnapDetails getAwrSnapDetails() {
return awrSnapDetails;
}
public AwrSnapDetails setAwrSnapDetails(AwrSnapDetails awrSnapDetails) {
return this.awrSnapDetails = awrSnapDetails;
}
}
Service Class
#Service
public class HelperService<T> {
#Autowired
private HelperRepository<T> repository;
public void add(T entity) {
repository.save(entity);
}
public void add(List<T> entities) {
repository.saveAll(entities);
}
public T get(T entity) {
T t = repository.findByHelperEntityAwrSnapDetailsStartSnapIdAndHelperEntityAwrSnapDetailsInstanceDetailDbNameAndHelperEntityAwrSnapDetailsInstanceDetailDbId(
((HelperEntity) entity).getAwrSnapDetails().getStartSnapId(),
((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbName(),
((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbId());
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getStartSnapId(),
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbName(),
//((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbId());
if (t!= null) {
return t;
}
return null;
}
}
Controller
#RestController
public class HelperController<T> {
#Autowired
private HelperService<T> service;
public void add(T entity) {
service.add(entity);
}
public void add(List<T> entities) {
service.add(entities);
}
public T get(T entity) {
return service.get(entity);
}
}
Execution
getAwrSnapDetails() initilized in HelperLoader
#Component
public class LoadAwrMemStats extends HelperLoader{
#Autowired
private HelperController<AwrMemStats> controller;
public void doThis() {
AwrMemStats profile = new AwrMemStats("a",1.0,1.0,getAwrSnapDetails());
AwrMemStats s = controller.get(profile);
ANd finally the ERROR message
Exception in thread "restartedMain" java.lang.reflect.InvocationTargetException
...
...
Caused by: java.lang.ClassCastException: net.mharoon.perfmon.awr.entities.AwrMemStats incompatible with net.mharoon.perfmon.awr.entities.HelperEntity
at net.mharoon.perfmon.awr.service.HelperService.get(HelperService.java:27)
at net.mharoon.perfmon.awr.controller.HelperController.get(HelperController.java:24)
...
...
Update this code works but only for given class AwrMemStats.
public List<T> get(T entity) {
List<T> ts = repository.findByHelperEntityAwrSnapDetailsStartSnapIdAndHelperEntityAwrSnapDetailsInstanceDetailDbIdAndHelperEntityAwrSnapDetailsInstanceDetailDbName(
//((HelperEntity) entity).getAwrSnapDetails().getStartSnapId(),
//((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbName(),
//((HelperEntity) entity).getAwrSnapDetails().getInstanceDetail().getDbId());
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getStartSnapId(),
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbId(),
((AwrMemStats) entity).getHelperEntity().getAwrSnapDetails().getInstanceDetail().getDbName());
if (!ts.isEmpty()) {
return ts;
}
return null;
}
The reason is because you are returning an Object that is not AwrMemStats and assigning it to AwrMemStats.
A simple work around is to replace
public T get(T entity)
with
public <T extends AwrMemStats> T get(T entity)
EDIT : Another solution (which is more generic) is..
replace
public class AwrMemStats
with
public class AwrMemStats extends HelperEntity
then replace
AwrMemStats s = controller.get(profile);
with
AwrMemStats s = (AwrMemStats) controller.get(profile);

Using is method instead of a getter method

can I tell supercsv that the class has an is method instead of a getter?
public class Decision {
private boolean isAccepcted;
public boolean isAccepcted() {
return isAccepcted;
}
public boolean getIsAccepted() {
return isAccepcted;
}
}
Here a part as the class tried to read the values.
private static final String[] NAME_MAPPING = new String[]{
"isAccepcted"
};
private static final CellProcessor[] PROCESSORS = new CellProcessor[] {
new org.supercsv.cellprocessor.ConvertNullTo(DEFAULT_NULL_VALUE)
};
private final CsvBeanWriter csvWriter;
csvWriter.write(consent, NAME_MAPPING, PROCESSORS);
With out the method getIsAccepted the lib won't find the method.
Any idea how I can call only isAccepted?
Thanks a lot,
Markus

Mixed Entity and Business classes - Refactor help needed

I have a project where Entity Classes and Business classes are mixed up. The entity beans are part of the business and all is used through the whole project.
How can I best refactor those classes to separate those layers. I also want to keep the changes to the implementers as minimal as possible. Preferable no changes, otherwise hundreds of references need to be updated.
How should I rename the classes and work through this?
Example of mixed code:
// Mixed business-entity class
public final class Language {
private final Long id;
private final String code;
private final String description;
//Constructor
public Language() {
}
//getters and setters
public String getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
...
//Business is a part of this class
public static Language findByUser(User user) {
Language language;
...implementation to find user language...
return language;
}
....
}
// Implementing class
public class Messenger {
public Messenger() {
}
public static void sendEmail() {
...
Language emailLanguage = Language.findByUser(user):
...
}
}
I want to separte those layers in:
// Entity Class
public final class Language {
private final Long id;
private final String code;
private final String description;
//Constructor
public Language() {
}
//getters and setters
public String getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
...
}
// Business Class
public final class LanguageImpl {
public LanguageImpl() {
}
public static Language findByUser(User user) {
Language language;
...implementation to find user language...
return language;
}
....
}
Provide minimal changes to implementation classes, preferable no changes. Otherwise a lot of work will come because of the references all over the code-base.
// Implementing class
public class Messenger {
public Messenger() {
}
public static void sendEmail() {
...
Language emailLanguage = Language.findByUser(user);
...
}
}
How do I work through this refactoring?
How should I rename my classes?
Any thoughts would be very helpful! Thanks!
This is my solution. Please review and accept this if it looks good. Thanks!
The mixed business-entity class is re-used as a wrapper class. This makes it possible to re-use this in all implementing classes where no changes are needed.
public final class Language Extends LanguageImpl{
private final LanguageEntity languageEntity;
//Constructor
public Language(LanguageEntity le) {
languageEntity = le;
}
//Wrapper method
public static Language findByUser(User user) {
LanguageEntity le = findEntityByUser(user);
Language language = new Language(le);
return language;
}
....
}
A new Entity class is created (LanguageEntity) in a new package. This avoids package and naming conflicts with the original mixed class (Language). All entity fields and methods from the mixed class are moved here.
package com.test.entity;
public final class LanguageEntity {
private final Long id;
private final String code;
private final String description;
//Constructor
public LanguageEntity() { }
//getters and setters
public String getId() { return this.id; }
public void setId(Long id) { this.id = id; }
...
}
A new business class is created (LanguageImpl) in a new package. All business methods are moved here. The original mixed class will extend this new business class.
package com.test.impl
public final class LanguageImpl {
//Constructor
public LanguageImpl() { }
//Business is a part of this class
public static LanguageEntity findEntityByUser(User user) {
LanguageEntity language;
...implementation to find user language...
return language;
}
....
}
This is an implementing class that does not need changes. Hundreds of implementation locations remain unchanged, which saves a lot of work. Hurray!
public class Messenger {
public Messenger() { }
public static void sendEmail() {
...
Language emailLanguage = Language.findByUser(user):
...
}
}
And for future development, the new combination LanguageEntity and LanguageImpl will be used. The original Language will be deprecated.
Please leave comments on this solution. Other solutions are more than welcome!

Not able to deserialize an object with with List using Gson api

Getting the following exception while deserializing an object:
com.google.gson.JsonParseException:
The JsonDeserializer com.google.gson.DefaultTypeAdapters$CollectionTypeAdapter#1e2befa
failed to deserialized json object
{"com.gsicommerce.analytics.platform.model.webstore.AnalyticsProduct":
[
{"id":"3680231","longTitle":"Graco SnugRide Infant Car Seat - Pippin","available":"true"}
]
}
given the type com.google.gson.ParameterizedTypeImpl#b565dd
Here is the class that I am trying to deserialize:
public class TrusRegWishAddItemEvent implements Serializable {
static final long serialVersionUID = 1L;
private final List<AnalyticsProduct> items;
private TrusRegWishAddItemEvent() {
items = null;
}
public TrusRegWishAddItemEvent(List<AnalyticsProduct> items) {
this.items = items;
}
public List<AnalyticsProduct> getItems() {
return items;
}
}
public class AnalyticsProduct implements Serializable {
static final long serialVersionUID = 1L;
private final long id;
private final String longTitle;
private final boolean available;
public AnalyticsProduct() {
id = 0;
longTitle = null;
available = false;
}
public AnalyticsProduct(long id, String longTitle, boolean available) {
this.id = id;
this.longTitle = longTitle;
this.available = available;
}
public long getId() {
return id;
}
public String getLongTitle() {
return longTitle;
}
public boolean isAvailable() {
return available;
}
}
Please guide.
If the JSON is
{
"items":
[
{
"id":"3680231",
"longTitle":"Graco SnugRide Infant Car Seat - Pippin",
"available":"true"
}
]
}
then the following example uses Gson to easily deserialize/serialize to/from the same Java data structure in the original question.
public static void main(String[] args) throws Exception
{
Gson gson = new Gson();
TrusRegWishAddItemEvent thing = gson.fromJson(new FileReader("input.json"), TrusRegWishAddItemEvent.class);
System.out.println(gson.toJson(thing));
}
If instead the JSON must be
{"com.gsicommerce.analytics.platform.model.webstore.AnalyticsProduct":
[
{"id":"3680231","longTitle":"Graco SnugRide Infant Car Seat - Pippin","available":"true"}
]
}
then it's necessary to translate the JSON element name "com.gsicommerce.analytics.platform.model.webstore.AnalyticsProduct" to the Java member items. To do so, Gson provides a couple of mechanisms, the simplest of which is to just annotate the items attribute in TrusRegWishAddItemEvent as follows.
class TrusRegWishAddItemEvent implements Serializable
{
static final long serialVersionUID = 1L;
#SerializedName("com.gsicommerce.analytics.platform.model.webstore.AnalyticsProduct")
private final List<AnalyticsProduct> items;
...
}
But without this #SerializedName annotation Gson doesn't throw an exception when attempting to deserialize, instead it just constructs a TrusRegWishAddItemEvent instance with items as a null reference. So, it's not clear what was done to generate the error message in the original question.

Resources