I'm using the 19.11 version of seedstack, and I want to use the FluentAssembler assembler to convert an aggregate List into a DTO List.
I'm getting the following error when I call the fluentAssembler.assemble method :
org.seedstack.business.internal.BusinessException: [BUSINESS] Unable to find assembler
Description
-----------
No assembler was found to assemble 'com.inetpsa.svr.domain.model.customer.Customer(Customer.java:1)' to
'com.inetpsa.svr.interfaces.rest.customer.CustomerRepresentation(CustomerRepresentation.java:1)'.
Fix
---
Make sure that an assembler without qualifier exists. If you want to use a qualified assembler (like a default
assembler), specify its qualifier.
I don't know howto specify the qualifier, I'd like to use a default model mapper...
Here is The Resource code :
#Path("customers")
public class CustomerResource {
#Inject
private FluentAssembler fluentAssembler;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<CustomerRepresentation> listAllCustomers() {
List<Customer> customerList = fetchAllCustomers();
return fluentAssembler.assemble(customerList).toListOf(CustomerRepresentation.class);
}
/**
* Test method - Should be replaced by a repository
* #return List<Customer> all customers
*/
private List<Customer> fetchAllCustomers(){
List<Customer> customerList = new ArrayList<>();
customerList.add(buildCustomer("005","Edward Teach","edward.teach#pirates.org"));
customerList.add(buildCustomer("006","Olivier Levasseur","olivier.levasseur#pirates.org"));
customerList.add(buildCustomer("007","James Bond","james.bond#mi6.uk"));
return customerList;
}
private Customer buildCustomer(String id, String name, String mail){
Customer result = new Customer(id);
result.updateNameAndMail(name, mail);
return result;
}
}
The aggregate :
public class Customer extends BaseAggregateRoot<String> {
#Identity
private String identifier;
private String name;
private String mail;
public Customer(String identifier){
this.identifier=identifier;
}
public void updateNameAndMail(String name, String mail){
if(StringUtils.isBlank(name)){
throw new IllegalArgumentException("Name can't be blank");
}
if(StringUtils.isBlank(mail)){
throw new IllegalArgumentException("Mail can't be blank");
}
this.name=name;
this.mail=mail;
}
public String getIdentifier() {
return identifier;
}
public String getName() {
return name;
}
public String getMail() {
return mail;
}
}
And the DTO :
#DtoOf(Customer.class)
public class CustomerRepresentation {
private String identifier;
private String name;
private String mail;
/**
* Required public no parameters constructor
*/
public CustomerRepresentation(){}
public CustomerRepresentation(String identifier, String name, String mail){
}
#AggregateId
public String getIdentifier() {
return identifier;
}
public String getMail() {
return mail;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setIdentifier(String identifier) {
this.identifier = identifier;
}
public void setMail(String mail) {
this.mail = mail;
}
}
FluentAssembler only takes care of matching a Dto with an Assembler, but does not provide a default implementation of an Assembler by itself.
You have 2 Options to provide a Default Assembler.
Build a class that implements Asselmber
Include an addon that provides that Default Assember for you (As stated on the docs)
Related
I am currently building a rest api that lets the user enter a recipe and describe it. I am using spring-boot as backend and angularjs as frontend.
This is springboot recipe file
package com.example.springboot;
import com.example.springboot.Recipe;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
#Entity // This tells Hibernate to make a table out of this class
public class Recipe {
public Recipe(){
}
public Recipe(Integer id, String name, String description, String type, Integer preptime, Integer cooktime, String content, Integer difficulty){
this.id = id;
this.name = name;
this.description = description;
this.type = type;
this.preptime = preptimee;
this.cooktime = cooktime;
this.content = content;
this.difficulty = difficulty;
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String name;
private String description;
private String type;
private Integer preptime;
#Column(columnDefinition = "TEXT")
private String content;
private Integer difficulty;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Integer getDifficulty() {
return difficulty;
}
public void setDifficulty(Integer difficulty) {
this.difficulty = difficulty;
}
public Integer getpreptime {
return preptime;
}
public void setpreptime(Integer preptime) {
this.preptime = preptime;
}
}
I created an Endpoint where the user can edit the whole recipe. The user can edit the name , description, content and so on in the recipes/edit/{id} endpoint.
The Endpoint looks like this.
#PutMapping("/recipes/edit/{id}")
void updateRecipe(#PathVariable int id, #RequestBody Recipe recipe ) {
System.out.println("entering");
Recipe recipe_ = recipeRepository.findById(id).get();
recipe_.setName(recipe.getName());
recipe_.setDescription(recipe.getDescription());
recipe_.setType(recipe.getType());
recipe_.setpreptime(recipe.getpreptime());
recipe_.setContent(recipe.getContent());
System.out.println("entering " + recipe.getTitle());
System.out.println("entering" + recipe.getType());
System.out.println("entering" + recipe.getDescription());
System.out.println("adding");
recipeRepository.save(recipe_);
}
Now I just want to create an Endpoint which only serves the purpose for renaming the name of the recipe. This putmapping should accept a list as its input then only rename the name of the recipe.
#PutMapping("/recipes/rename")
public List<Recipe> {
System.out.println("entering renaming");
// recipe_.setName(recipe.getName()); ?
}
I don't know how I can implement this. This is what I have come up with so far. An endpoint which takes a list as a parameter.
This is the service.ts file that updates the Recipes in the edit function
service.ts:
updateRecipe (id: number, recipe: any): Observable<any > {
const url = `${this.usersUrl}/edit/${id}`;
return this.http.put(url ,recipe);
}
where the updateRecipe gets called:
save(): void {
const id = +this.route.snapshot.paramMap.get('name');
this.recipeService.updateRecipe2(id, this.recipes)
.subscribe(() => this.gotoUserList());
}
This implementation work , I don't know how I can get it work or how I can rewrite the functions so that it can update only the name of the recipe and not the whole file.
Could someone help me?
Your update the name method should look like that:
#PutMapping("...{id}")
public void updateName(#PathVariable Integer id, #RequestParam String name){
Recipe recipe = repository.findById(id).orElseThrow(...);
recipe.setName(name);
}
if you want to rename list of recipes
public void renameRecipes(String oldName, String newName){
repository.findByName(oldName)
.forEach(r -> r.setName(newName));
}
#PutMapping("recipes/rename")
public void updateNames(#PequestParam String oldName, #RequestParam String newName){
renameRecipes(oldName, newName);
}
Try that.
I using RestController update data to db but I have problem. When i update value, if value from my update is null , it allways update data to db is null. I dont't want it. I want if 1 field with value is null from my request, i don't want update it.
This bellow my code :
Controller:
RestController
#RequestMapping("/api/products")
#Api(value = "ProductControllerApi",produces = MediaType.APPLICATION_JSON_VALUE)
public class ProductController {
#Autowired
private ProductService productService;
#PatchMapping("/{id}")
public ResponseEntity<ProductResDto> updateProduct(#RequestBody ProductReqDto productReqDto, #PathVariable String id) {
return ResponseEntity.ok(productService.updateProduct(product,id));
}
ProductReqDto:
public class ProductReqDto {
private String name;
private String type;
private String category;
private String description;
private Double prince;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrince() {
return prince;
}
public void setPrince(Double prince) {
this.prince = prince;
}
}
ProductResDto:
public class ProductResDto {
private String name;
private String type;
private String category;
private Double prince;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Double getPrince() {
return prince;
}
public void setPrince(Double prince) {
this.prince = prince;
}
}
MappingDto:
private ProductDto convertToProductDto(ProductReq product) {
return modelMapper.map(product, ProductResDto.class);
}
How to i handle method convertToProductDto only mapping with value not null. Because if , mapping one field : example : product_name = null , it insert to db null. I want if field ProductReq have value, it mapping and keep other different field in database(not set it null if not contain value from ProductReq) .
Example:
**ReqProductDto.class**
private String name;
private String type;
private String category;
private String description;
private Double prince;
but if user only update two field:
private String name;
private String type;
I want spring update field name, and field type user input and keep category,description,prince in my database. In my case, if user update two field: name, and field type,spring update it but spring set category,description,prince is null in my database. I don't want it.
Please help me, thanks.
You've tagged this as spring-boot, so I'm assuming you might be using controllers and validating their parameters. If that is the case, just do
import javax.validation.constraints.NotNull;
public class ProductReqDto {
#NotNull
private String name;
#NotNull
private String type;
#NotNull
private String category;
#NotNull
private String description;
#NotNull
private Double prince;
...
}
and use #Valid for your controllers like this
#PatchMapping("/{id}")
public ResponseEntity<ProductResDto> updateProduct(#RequestBody #Valid ProductReqDto productReqDto, #PathVariable String id) {
return ResponseEntity.ok(productService.updateProduct(product,id));
}
Then your object will be validated on instantiation.
What you want is mainly used for PATCH mapping.
IN a PUT mapping, all fields of an object need to override, but in a PATCH mapping only the fields which are provided needs to be overridden, others need not be changed.
So,
for an existing record,
employee{ employeeId = "A2RTD", empName = "satish", "country": "India"}
And, now one non-mandatory field mobileNo needs to be updated along with the country
DTO request will contain all field other than id, but only country & mobile no will not be null
In this scenario, we can use BeanUtils which is part of spring package
import org.springframework.beans.BeanUtils;
public static Object getDtoMapping(Object source, Object destination) {
BeanUtils.copyProperties(source, destination, getNullFieldNames(source));
return destination;
}
public static String[] getNullFieldNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> fieldNames = new HashSet<>();
for (PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null)
fieldNames.add(pd.getName());
}
String[] result = new String[fieldNames.size()];
return fieldNames.toArray(result);
}
Ths function "getNullFieldNames" will return fieldNames which have value null. So, those fields will not be mapped, as per 3rd optional paramter in BeanUtils
And, you need to pass
// PATCH
EmployeeDao emp = findById(empCode);
emp = (EmployeeDao) getDtoMapping(empUpdateDto, emp);
Here, in BeanUtil copyProperties, 3rd param is optional. If you give it works for PATCH mapping, if you don't give it behaves as PUT mapping.
Since, for PUT mapping, ignoring null as same as not ignoring.
You can use the same in POST, PUT mapping also.
// POST MAPPING
EmployeeDao emp = (EmployeeDao) getDtoMapping(empCreateDto, new Employee());
I'm working with Spring Boot 2.0RC2 and in the documentation I read you can return a projection of an entity instead of the entity as a whole when calling the Repository. This is working fine in case I use a String in my Entity but not when I use an embedded value objects.
Let's say I have the Product entity:
#Entity
#Table(name = "t_product")
public class Product extends BaseEntity {
#Column(nullable = false)
private String name;
private Product() {}
private Product(final String name) {
this.name = name;
}
public static Result<Product> create(#NonNull final String name) {
return Result.ok(new Product(name));
}
public String getName() {
return name;
}
public void setName(#NonNull final String name) {
this.name = name;
}
}
The BaseEntity simply holds the id, created and updated attributes.
I have my projection interface called ProductSummary:
interface ProductSummary {
String getName();
Long getNameLength();
}
And in my ProductRepository I have the following method that returns the ProductSummary:
public interface ProductRepository extends JpaRepository<Product, Long> {
#Query(value = "SELECT p.name as name, LENGTH(p.name) as nameLength FROM Product p WHERE p.id = :id")
ProductSummary findSummaryById(#Param("id") Long id);
}
This works perfectly fine. Now let's say I am doing DDD and instead of using a String to represent the name attribute in the Product entity, I want to use a value object called Name:
#Embeddable
public class Name implements Serializable {
public static final int MAX_NAME_LENGTH = 100;
#Column(nullable = false, length = Name.MAX_NAME_LENGTH)
private String value;
private Name() {}
private Name(final String value) {
this.value = value;
}
public static Result<Name> create(#NonNull final String name) {
if (name.isEmpty()) {
return Result.fail("Name cannot be empty");
}
if (name.length() > MAX_NAME_LENGTH) {
return Result.fail("Name cannot be longer than " + MAX_NAME_LENGTH + " characters");
}
return Result.ok(new Name(name));
}
public String getValue() {
return value;
}
}
I change my Product entity to:
#Entity
#Table(name = "t_product")
public class Product extends BaseEntity {
#Embedded
private Name name;
private Product() {}
private Product(final Name name) {
this.name = name;
}
public static Result<Product> create(#NonNull final Name name) {
return Result.ok(new Product(name));
}
public Name getName() {
return name;
}
public void setName(final Name name) {
this.name = name;
}
}
And in the ProductSummary I change the return type from String to Name.
When I run that I always get the exception:
Caused by: java.lang.IllegalAccessError: tried to access class com.acme.core.product.ProductSummary from class com.sun.proxy.$Proxy112
Can I make this work or am I missing some restriction which doesn't allow this?
If you wish to get the complete Name field(not a particular field in Name class), then you need to create another interface like ProductSummary.
interface ProductSummary {
NameSummary getName();
interface NameSummary {
String getValue();
}
}
No need to change anything in your repository.
It is quite clearly documented here
And make sure your interfaces and the methods are public.
I have a model and repository. Model has getter and setter values which has to be added in the response. My model is like follows
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.katharsis.resource.annotations.JsonApiId;
import io.katharsis.resource.annotations.JsonApiResource;
#JsonApiResource(type="employee") //no i18n
public class Employee {
#JsonApiId
private String name;
private int emp_id;
private String dob;
private String profile_url;
private String status_message;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getEmp_id() {
return this.emp_id;
}
public void setEmp_id(int empid) {
this.emp_id = empid;
}
public String getDob() {
return this.dob;
}
public void setDob(String dob) {
this.dob = dob;
}
public String getProfile_url() {
return this.profile_url;
}
public void setProfile_url(String url) {
this.profile_url = url;
}
public String getStatus_message() {
return this.status_message;
}
public void setStatus_message(String message) {
this.status_message = message;
}
}
Here the variable status_message represents my api status. The api response should be like
When the database has the employee with inputted id
{name : "rajasuba", emp_id : "123", dob : "March301993", profile_url : "https:", status_message : "success"}
When the employee left the organization the response should be like
{name : "rajasuba", emp_id : "567", status_messsage : "fired"}
When there is no such employee then my response should be like
{status_message : "Invalid employeed id"}
But for all the above cases i am getting all the attribute values. How can i ignore the attribute value (like #JsonIgnore) selectively for a particular scenario?
I don't know what version of katharsis you're using, but in 2.8.2 it's supported
Google endpoint message objects are very simple POJOs. I have a compound POJO that used to work but now is no longer working. The error I am getting, when android client makes the call, is that the JSON cannot be parsed because of AnimalTag. Here are the POJOs. To migrate to Java 7, I copied and pasted the code manually. So I thought that could have been the cause, that perhaps I left something out. But I can't think of what the problem may be. Other calls work fine. But this one keeps failing.
The usage is that the method receives Dog from client to save on server. Not all the data in Dog is filled, but many, including AnimalTag, are filled. Also, AnimalTag only has the manufacturer filled. Again This all used to work.
public class Dog {
private String name;
private String owner;
private AnimalTag tag;
public Dog(String name, String owner, AnimalTag tag) {
super();
this.name = name;
this.owner = owner;
this.tag = tag;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getOwner() {
return this.owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public AnimalTag getTag() {
return this.tag;
}
public void setTag(AnimalTag tag) {
this.tag = tag;
}
}
class AnimalTag{
private long number;
BlobKey imageKey;
String manufacturer;
public AnimalTag(long number, BlobKey imageKey, String manufacturer) {
super();
this.number = number;
this.imageKey = imageKey;
this.manufacturer = manufacturer;
}
public long getNumber() {
return this.number;
}
public void setNumber(long number) {
this.number = number;
}
public BlobKey getImageKey() {
return this.imageKey;
}
public void setImageKey(BlobKey imageKey) {
this.imageKey = imageKey;
}
public String getManufacturer() {
return this.manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
}
I got the answer, AnimalTag was missing the following constructor:
public AnimalTag(){}