I have child collection
public class SectorDto {
List<RentalObjectDto> rentalObjects;
}
#Entity
public class Sector {
List<RentalObject> rentalObjects;
}
and before mapping I have Sector with child collection from DB. Then I use #MappingTarget to update existing bean
Sector map(SectorDto sectorDto, #MappingTarget Sector sectorDb);
I need the same possibility for child elements. But generated code doesn't want to use this method with #MappingTarget
RentalObject map(RentalObjectDto rentalObjectDto, #MappingTarget RentalObject rentalObjectDb)
the same when I add method for list with #MappingTarget it also generates own method without #MappingTarget for collection members.
List<RentalObject> mapRentalObjects(List<RentalObjectDto> rentalObjectDtos,
#MappingTarget List<RentalObject> rentalObjectsDb);
My intention is to update already existing in DB collection members, to add new, and to remove orphans, because child Entity class (the same as parent) has additional properties witch aren't in Dto class, and it is important not to lose them. I ended up with complicated default method witch works in my case,
default List<RentalObject> mapRentalObjects(List<RentalObjectDto> rentalObjectDtos,
#MappingTarget List<RentalObject> rentalObjectsDb) {
if ( rentalObjectDtos == null ) { return null;}
List<RentalObject> rentalObjectsDbForRemove = new ArrayList<>();
// map existing
for ( RentalObject rentalObjectDb : rentalObjectsDb ) {
Optional<RentalObjectDto> rentalObjectDto = rentalObjectDtos.stream()
.filter(ro->ro.getId()!=null)
.filter(ro->ro.getId().equals(rentalObjectDb.getId())).findFirst();
if (rentalObjectDto.isPresent()){
map(rentalObjectDto.get(), rentalObjectDb);
} else {
rentalObjectsDbForRemove.add(rentalObjectDb);
}
}
// remove orphan
rentalObjectsDb.removeAll(rentalObjectsDbForRemove);
// add new
rentalObjectDtos.stream()
.filter(roDto->roDto.getId()==null)
.map(roDto-> mapRentalObjectDb(roDto,new RentalObject())
.forEach((roDto)->rentalObjectsDb.add(roDto));
return rentalObjectsDb;
}
Does mapstuct have an easier solution for this case?
Updating collections is a tricky problem and requires a complex solution like you can see in your example.
Therefore MapStruct does not provide a way to achieve such a complex scenario.
The solution for this would be to write a custom method like you did and use that for mapping is the only way in the moment.
Related
Our team are using Spring Boot 2 with sql2o as db library. In the paste in our services, for trivial methods, we simply call the repository and returns the model. For example, if I have a Supplier table, I had in the service
#Override
public List<Supplier> findAll() {
return supplierRepository.findAll();
}
Now, since in our controllers we need 99% in the cases other objects correlated to the model, I would create a composite class that holds the model and other models. For example:
#Override
public List<UnknownName> findAll() {
List<Supplier> suppliers = supplierRepository.findAll();
List<UnknownName> res = new ArrayList<>();
UnknownName unknownName;
LegalOffice legalOffice;
if (suppliers != null) {
for (Supplier supplier in suppliers) {
unknownName = new UnknownName();
unknownName.setSupplier(supplier);
legalOffice = legalOfficeService.findByIdlegaloffice(supplier.getLegalofficeid);
unknownName.setLegalOffice(legalOffice);
res.add(unknownName);
}
}
return res;
}
What should the name of class UnknownName?
PS: I simplified the code for better redability, but I use a generic enrich() function that I call for all the find methods, so I don't have to dupe the code.
I would recommend SupplierDto or SupplierLegalOfficeDto. DTO stands for Data Transfer Objects and it's commonly used for enriched models (more here).
Also you shouldn't check suppliers for null as repository always returns a non-null list.
In the end, I adopted the suffix Aggregator, following the Domain-driven design wording.
I am working on a project using ISIS 1.16.2. I have a superclass, called ConfigurationItem, which has some common properties (name, createdTimestamp etc.).
For example it has a delete action method, annotated with #Action(invokeOn = InvokeOn.OBJECT_AND_COLLECTION, ...), which I need to be callable from entitys detail view as well as from collection views with selection boxes.
Example:
public class ConfigurationItem {
#Action(
invokeOn = InvokeOn.OBJECT_AND_COLLECTION,
semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
domainEvent = DeletedDomainEvent.class)
public Object delete() {
repositoryService.remove(this);
return null;
}
// ...
}
public class ConfigurationItems {
#Action(semantics = SemanticsOf.SAFE)
public List<T> listAll() {
return repositoryService.allInstances(<item-subclass>.class);
}
// ...
}
This works pretty well but the "invokeOn" annotation is now deprecated. The JavaDoc says that one should switch to #Action(associateWith="...") but I don't know how to transfer the semantics of 'InvokeOn' since I have no collection field for reference.
Instead I only have the collection of objects returned by the database retrieve action.
My question is: How do I transfer the deprecated #Action(invokeOn=...) semantics to the new #Action(associateWith="...") concept for collection return values with no backed property field?
Thanks in advance!
Good question, this obviously isn't explained well enough in the Apache Isis documentation.
The #Action(invokeOn=InvokeOn.OBJECT_AND_COLLECTION) has always been a bit of a kludge, because it involves invoking an action against a standalone collection (which is to say, the list of object returned from a previous query). We don't like this because there is no "single" object to invoke the action on.
When we implemented that feature, the support for view models was nowhere near as comprehensive as it now is. So, our recommendation now is, rather than returning a bare standalone collection, instead wrap it in a view model which holds the collection.
The view model then gives us a single target to invoke some behaviour on; the idea being that it is the responsibility of the view model to iterate over all selected items and invoke an action on them.
With your code, we can introduce SomeConfigItems as the view model:
#XmlRootElement("configItems")
public class SomeConfigItems {
#lombok.Getter #lombok.Setter
private List<ConfigurationItem> items = new ArrayList<>();
#Action(
associateWith = "items", // associates with the items collection
semantics = SemanticsOf.NON_IDEMPOTENT_ARE_YOU_SURE,
domainEvent = DeletedDomainEvent.class)
public SomeConfigItems delete(List<ConfigurationItem> items) {
for(ConfigurationItem item: items) {
repositoryService.remove(item);
}
return this;
}
// optionally, select all items for deletion by default
public List<ConfigurationItem> default0Delete() { return getItems(); }
// I don't *think* that a choices method is required, but if present then
// is the potential list of items for the argument
//public List<ConfigurationItem> choices0Delete() { return getItems(); }
}
and then change the ConfigurationItems action to return this view model:
public class ConfigurationItems {
#Action(semantics = SemanticsOf.SAFE)
public SelectedItems listAll() {
List<T> items = repositoryService.allInstances(<item-subclass>.class);
return new SelectedItems(items);
}
}
Now that you have a view model to represent the output, you'll probably find other things you can do with it.
Hope that makes sense!
Let's pretend a RESTful service receives a PATCH request to update one or more fields of an entity that might have tens of fields.
#Entity
public class SomeEntity {
#Id
#GeneratedValue
private Long id;
// many other fields
}
One dirty way to patch the corresponding entity is to write something like this:
SomeEntity patch = deserialize(json);
SomeEntity existing = findById(patch.getId());
if (existing != null)
{
if (patch.getField1() != null)
{
existing.setField1(patch.getField1());
}
if (patch.getField2() != null)
{
existing.setField2(patch.getField2());
}
if (patch.getField3() != null)
{
existing.setField3(patch.getField3());
}
}
But this is insane! And if I want to patch 1 to many & other associations of the entity the insanity could even become hazardous!
Is there a sane an elegant way to achieve this task?
Modify the getter's of SomeEntity and apply check, if any value is blank or null just return the corresponding entity object value.
class SomeEntity {
transient SomeEntity existing;
private String name;
public String getName(){
if((name!=null&&name.length()>0)||existing==null){
return name;
}
return existing.getName();
}
}
You can send an array containing the name of the fields you are going to patch. Then, in the server side, by reflection or any field mapping, set each field to the entity. I have already implemented that and it works, thought my best advice is this:
Don't publish an endpoint to perform a "generic" PATCH (modification), but one that performs a specific operation. For instance, if you want to modify an employee's address, publish an endpoint like:
PUT /employees/3/move
that expects a JSON with the new address {"address" : "new address"}.
Instead of reinventing the wheel by writing the logic yourself, why don't you use a mapping library like Dozer? You want to use the 'map-null' mapping property: http://dozer.sourceforge.net/documentation/exclude.html
EDIT I am not sure whether or not it would be possible to map a class onto itself. You could use an intermediary DTO, though.
I have two classes that share the same flow: I need to get info from the DB, process it and update it on a nosql DB.
The difference is very small: in one case I am sure I will get only one entity for each nosql record, so I can just process them, store them temporarily in a list and then get all the items in the list after the entities were processed.
In the second case I can have more than one entity for each index record. In this case I have to retrieve the current processed data, merge, and store again. After every entity was processed I am able to get the map.values() so I can update to the nosql.
As seen the difference is only on how the items are processed and how the data structure is temporarily stored.
My parent class has something like this:
public void run()
{
process();
sendToNosql(getProcessed())
}
protected abstract void process();
protected abstract List<Stuff> getProcessed();
The simple children is something like this:
List<Stuff> myStuff = new ArrayList<>();
#Override
protected void process()
{
for(Entity e : loadFromDB()){
myStuff.add(process(e));
}
}
#Override
protected List<Stuff> getProcessed(){
return myStuff;
}
And the complex one:
Map<int, Stuff> myStuff = new HashMap<>();
#Override
protected void process()
{
for(Entity e : loadFromDB()){
myStuff.put(e.getId(), process(e));
}
}
#Override
protected List<Stuff> getProcessed(){
return myStuff.values();
}
Because everything will be processed and the results will be used later, myStuff is an object attribute. This works fine.
But because these are stateless beans, I am only safe while I am in the run() method, if any other method is called, the myStuff attribute is not reliable.
I would like to avoid this risk, so I would like to have the run() method as myStuff attribute scope. How can I improve my architecture to achieve this while abstracting the underlying data structure?
Or is this problem telling me I made something very wrong?
For information in case anyone finds this question:
I used the State pattern, so the child class generates a state when the process method starts. The state hides the way the information is stored (hash or list).
I am working on an object cache of CMS objects. I need to maintain a parent/child relationship between a Product and child objects (Options and Attributes). The parent (Product) is illustrated in the first code sample.
It is easy enough to do, but I am looking for a way to make the assignment of the child to the parent, as shown in the 2nd code block, generic.
Since all CMS objects extend CMSContent, I can use ProductID. However, is there a way to make the field (e.g. ProductAttribute) generic so that I can put the algorithm in a method and call the method with a parent and child object to make the attribute assignment?
I know that an ORM framework like Hibernate is appropriate here, but that won't fit since I have a fixed database structure.
public class Product extends CMSContent {
private List<ProductAttribute> productAttributes;
private List<ProductOptions> productOptions;
// getters,setters
}
Algorithm to match them up.
// attach Product Attributes to Product
for (Product p : listP) {
Map<String, Object> parameters = new HashMap<String, Object>();
for (ProductAttribute po : listPA) {
parameters.put("pid", p.getPid());
parameters.put("ref", po.getRid());
int i = jdbcTemplate.queryForInt(sqlAttr, parameters); // select count(*), 1 if matched.
if (i == 1) {
p.getProductAttributes().add(po); // generic field?
}
}
}
Wouldn't this two Methods in Product help
public void add(ProductAttribute item){
productAttributes.add(item);
}
public void add(ProductOption item){
productOption.add(item);
}
so you should be able to just add a ProductAttribute or a ProductOption