How can I have an interface as a ModelAttribute as in the below scenario?
#GetMapping("/{id}")
public String get(#PathVariable String id, ModelMap map) {
map.put("entity", service.getById(id));
return "view";
}
#PostMapping("/{id}")
public String update(#ModelAttribute("entity") Entity entity) {
service.store(entity);
return "view";
}
Above snippet gives the follow errors
BeanInstantiationException: Failed to instantiate [foo.Entity]: Specified class is an interface
I don't want spring to instantiate entity for me, I want to use the existing instance provided by map.put("entity", ..).
As been pointed out in comments, the Entity instance does not survive between the get and post requests.
The solution is this
#ModelAttribute("entity")
public Entity entity(#PathVariable String id) {
return service.getById(id);
}
#GetMapping("/{id}")
public String get() {
return "view";
}
#PostMapping("/{id})
public String update(#ModelAttribute("entity") Entity entity) {
service.store(entity);
return "view";
}
What happens here is that the Entity in update binds to the Entity created from the #ModelAttribute annotated entity method. Spring then applies the form-values to the existing object.
Related
I have a controller that accepts a dto object. I need to change the fields that are present in the dto object.
#PatchMapping(value = "/update/{uuid}")
public ResponseEntity<UserDto> update(
#RequestBody UserDto userDto,
#PathVariable("uuid")UUID uuid) throws UserNotFoundException {
User updatedUser = userService.update(
userMapper.userDtoToUser(userDto),
uuid
);
return .....
}
But a userService can only accept entities. I need to use mapper dto -> entity. But the entity cannot have empty fields that come in dto (Let's say that you need to change only one field). What to do in this situation? I know that the controller should not contain logic
Two possible ways to resolve this problem. You have to either change the service method to accept the dto and not the entity or you have to create a #Component class which implements Converter, override the convert method and do the necessary field changes there and then #Autowire GenericConversionService in your controller and call genericConversionService.convert(userDto, User.class);
The converter should look like this:
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
#Component
public class UserDtoToUser implements Converter<UserDto, User> {
#Override
public User convert(UserDto source) {
User user = new User();
// user.set ..... for all necessary fields
return user;
}
}
EDIT
In case you want to check the validity of the fields you're receiving you can simply use the following annotations to ensure your data is correct: #NotBlank - this checks if a field is null or an empty string, #NotNull - this checks if a field is null, #NotEmpty - this checks if a field is null or an empty collection. Important to remember - you must add the #Valid annotation before the object you want to validate (side note - in case of nested objects you also need to add it to your object fields) so it looks like this #RequestBody #Valid UserDto userDto,
And then for the dto it should look something like this:
import java.util.List;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
public class UserDto {
#NotNull
private Integer id;
#NotBlank
private String username;
#NotBlank
private String password;
#NotEmpty
private List<String> roles;
}
Change to fields to whatever is in your dto of course. Also in case you need to do more validations there are a number of other validation annotations you can add.
You could use reflection to check the null properties and BeanUtils for the copying
In Spring that would be the way that I'd to check the empty properties
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for(PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
return emptyNames.toArray(new String[0]);
}
And then for the copying
User updatedUser = new User();
BeanUtils.copyProperties(userDto, updatedUser, getNullPropertyNames(userDto));
As you say the controller should not contain logic, so for this Spring has an interface, the interface is Validator
There are pros and cons for considering validation as business logic, and Spring offers a design for validation (and data binding) that does not exclude either one of them. Specifically validation should not be tied to the web tier, should be easy to localize and it should be possible to plug in any validator available. Considering the above, Spring has come up with a Validator interface that is both basic ands eminently usable in every layer of an application.
This is what you need to do:
Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.
we have a DTO to which we will validate the fields:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
To do validations we must implement Validator interface:
public class PersonValidator implements Validator {
/**
* This Validator validates *just* Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
if (supports(obj.getClass())) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
}
If the DTO passes all validations you can map the DTO to an Entity object
You can find more information here Spring Validator
public interface LmsRepository extends CrudRepository
I have no findOne method for getting single count so when i using findById I got this exception."Property [id] not found on type [java.util.Optional]" How can i solve this trouble ?
This is my CrudRepo
#Repository
public interface LmsRepository extends CrudRepository<Book, Long> {
}
Entity File
#Entity(name="lms_tbl")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="book_name")
private String bookName;
private String author;
#Column(name="purchase_date")
#Temporal(TemporalType.DATE)
private Date purchaseDate;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
and other....
Service File
#Service
public class LmsService {
#Autowired
private LmsRepository lmsRepository;
public Collection<Book> findAllBooks(){
ArrayList<Book> books = new ArrayList<Book>();
for (Book book : lmsRepository.findAll()) {
books.add(book);
}
return books;
}
public void deleteBook(long id) {
lmsRepository.deleteById(id);
}
public Optional<Book> findById(Long id) {
return lmsRepository.findById(id);
}
}
Controller File
#Controller
public class MainController {
#Autowired
private LmsService lmsService;
#GetMapping("/")
public String index(HttpServletRequest req) {
req.setAttribute("books", lmsService.findAllBooks());
req.setAttribute("mode","BOOK_VIEW");
return "index";
}
#GetMapping("/updateBook")
public String index(#RequestParam Long id,HttpServletRequest req) {
req.setAttribute("book", lmsService.findById(id));
req.setAttribute("mode","BOOK_EDIT");
return "index";
}
}
I tried add new method in CrudRepo but it doesnt work.
In your service class change this
public Optional<Book> findById(Long id) {
return lmsRepository.findById(id);
}
to this
public Book findById(Long id) {
return lmsRepository.findById(id).get();
}
Explanation: Optional is a wrapper class which may or may not contain a non-null value. You get that error because you are trying to insert Optional in the model, not Book. As Optional does not contain any id field, you get the error. Optional is used for having defalut values of throwing exception when you have a null object where you do not expect it to be null. You can create for example an automatic exception throwing in case of null optional. For example, you can upgrade your service in this way:
public Book findById(Long id) {
return lmsRepository.findById(id).orElseThrow(RuntimeException::new);
}
This will throw a RuntimeException any time Book is null inside the Optional, or will give you back the value of the Book class.
A more elegant solution is the following:
public Book findById(Long id) {
return lmsRepository.findById(id).orElseThrow(NotFoundException::new);
}
having:
#ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{
}
In this way when the optional contains a Book, that is returned back to the controller and inserted in model. If the Optional contains a null value then NotFoundException will be thrown, it does not need to be catched, and will be mappet to 404 HTTP error.
You can create own methods in the repository.
LmsRepository<CustomClass> extends CrudRepository<CustomClass> {
Optional<CustomClass> findById(int id);
}
in your entity you declare #Id on id, so findById follow on this (primary key) datatype, id of the entity the repository manages.
Simply put .get() after lmsRepository.findById(id).get();
.get() parses and will return the [object of lms] caught from the database
No need to override, implement and et cetera.
You can add a method in your interface
#Repository
public interface LmsRepository extends CrudRepository<Book, Long> {
Optional<Book> findById(Long id);
}
Consider following UserDTO class and UserController exposing endpoints to create, update and get User.
Having the id property in the UserDTO class does not make sense for create and update. If I use swagger or another auto generated API documentation then it shows that the id can be passed in create end point. But the system does not use it as ids are generated internally.
If I look at get then probably I can get rid of the id property but it is certainly required in a list user end point.
I was thinking of returning internal User domain object in get/list end points. This way I can then get rid of id property form UserDTO class.
Is there any better option I can employ for this?
public class UserDTO {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#RestController
#RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Void> create(#RequestBody UserDTO user) {
}
#RequestMapping(value = "{id}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<UserDTO> get(#PathVariable("id") int id) {
}
#RequestMapping(value = "{id}", method = RequestMethod.PUT)
#ResponseBody
public ResponseEntity<Void> update(#PathVariable("id") int id, #RequestBody UserDTO user) {
}
}
This question may have been asked but I could not find. So excuse me for duplicate question.
Data Transfer Object (DTO) is a pattern that was created with a very well defined purpose: transfer data to remote interfaces, just like web services. This pattern fits very well in REST APIs and DTOs will give you more flexibility in the long run.
I would recommend using tailored classes for your endpoints, once REST resource representations don't need to have the same attributes as the persistence objects.
To avoid boilerplate code, you can use mapping frameworks such as MapStruct to map your REST API DTOs from/to your persistence objects.
For details on the benefits of using DTOs in REST APIs, check the following answers:
Why you should use DTOs in your REST API
Using tailored classes of request and response
To give your DTOs better names, check the following answer:
Giving meaningful names to your DTOs
What's about creating two different interfaces :
interface UserDTO {
public String getName ();
public void setName (String name);
}
interface IdentifiableUserDTO extends UserDTO {
public Long getId ();
public void setId (Long id);
}
class DefaultUserDTO implements IdentifiableUserDTO {
}
and then use the Interface in your controller instead of the DTO class :
#RestController
#RequestMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Void> create(#RequestBody IdentifiableUserDTO user) {
}
#RequestMapping(value = "{id}", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<UserDTO> get(#PathVariable("id") int id) {
}
#RequestMapping(value = "{id}", method = RequestMethod.PUT)
#ResponseBody
public ResponseEntity<Void> update(#PathVariable("id") int id, #RequestBody UserDTO user) {
}
}
I am trying to incorporate annotated validation rules along with some custom validation. I have a details entity which looks like the following:
public class DetailsEntity {
#NotEmpty(message = "Name is required")
private String name;
private String customField;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCustomField() {
return customField;
}
public void setCustomField(String customField) {
this.customField = customField;
}
}
I then have a controller that looks like this:
#Controller
public class EntityController {
#RequestMapping(value = "/create", method = RequestMethod.POST)
public #ResponseBody DetailsEntity create(#RequestBody #Valid
DetailsEntity details) {
//Do some creation work
}
}
This all works great out of the box. The problem is when I try to use a custom validator along with my entity. My validator looks like this:
#Component
public class EntityValidator implements Validator {
#Override
public boolean supports(Class<?> aClass) {
return aClass.isAssignableFrom(DetailsEntity.class);
}
#Override
public void validate(Object o, Errors errors) {
DetailsEntity entity = (DetailsEntity) o;
if (entity.getCustomField().equals("Some bad value")) {
errors.reject("Bad custom value supplied");
}
}
}
I've tried injecting my validator two ways. One is using the #InitBinder in the controller, and the other is setting a global validator in the spring configuration (<mvc:annotation-driven validator="entityValidator" />). Either way I do it, the custom validator works fine, but my #NotEmpty annotation gets ignored. How can I use both the annotations as well as a custom validator?
Use SpringValidatorAdapter as base class of your custom validator and override validate() method:
public void validate(Object target, Errors errors) {
// check JSR-303 Constraints
super.validate(target, errors);
// Add you custom validation here.
}
Or inject a LocalValidationFactoryBean in you custom validator and call to validate(target, errors) before or after your custom validation.
#NotEmpty is a JSR-303 annotation, and we need to use an implementation of it like HiberanteValidator, we need to add Hibernate-Validator jar to your lib directory. Using this jar we can use #NotEmpty, #NotNull...all JSR 303 annotations.
In my Spring MVC webapp I have a generic RESTful controller for CRUD operations. And each concrete controller had to declare only a #RequestMapping, for example /foo. Generic controller handled all request to /foo and /foo/{id}.
But now I need to write a bit more complex CRUD controller which will get additional request params or path variables, e.g /foo/{date} and /foo/{id}/{date}. So I extend my generic CRUD controller and write overloaded fetch(id, date) method which will deal with both {id} and {date}. That is not a problem.
But I also need to 'disable' fetch(id) implementation derived from base class (resource mustn't be available at /foo/{id} anymore, only at /foo/{id}/{date}). The only idea I came up with is to override this method in my concrete controller, to map it on a fake uri and return null. But this looks like rather ugly dirty hack because we expose some fake resource uri, instead of disabling it. May be there is a better practice?
Any ideas?
//My generic CRUD controller
public abstract class AbstractCRUDControllerBean<E, PK extends Serializable> implements AbstractCRUDController<E, PK> {
#RequestMapping(method=RequestMethod.GET)
public #ResponseBody ResponseEntity<E[]> fetchAll() { ... }
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public #ResponseBody ResponseEntity<E> fetch(#PathVariable("id") PK id) { ... }
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody ResponseEntity<E> add(#RequestBody E entity) { ... }
#RequestMapping(value="/{id}", method=RequestMethod.PUT)
public #ResponseBody ResponseEntity<E> update(#PathVariable("id") PK id, #RequestBody E entity) { ... }
#RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public #ResponseBody ResponseEntity<E> remove(#PathVariable("id") PK id) { .. }
}
.
//Concrete controller, working with Foo entities
#Controller
#RequestMapping("/foo")
public class FooControllerImpl extends
AbstractCRUDControllerBean<Foo, Long> implements FooController {
//ugly overriding parent's method
#RequestMapping(value="/null",method=RequestMethod.GET)
public #ResponseBody ResponseEntity<Foo> fetch(#PathVariable("id") PK id) {
return null;
}
//new fetch implementation
#RequestMapping(value="/{id}/{date}", method=RequestMethod.GET)
public #ResponseBody ResponseEntity<Foo> fetch(#PathVariable("id") PK id, #PathVariable("date") Date date) { .... }
}
Are you trying to achieve the resource, subresource type of jersey using spring? That may not be directly possible. Instead of declaring the generic RESTful service as controller, why don't you delegate it to them?
//My generic CRUD Operations
public abstract class AbstractCRUDControllerBean<E, PK extends Serializable> implements AbstractCRUDController<E, PK> {
public ResponseEntity<E[]> fetchAll() { ... }
public ResponseEntity<E> fetch(#PathVariable("id") PK id) { ... }
public ResponseEntity<E> add(#RequestBody E entity) { ... }
public ResponseEntity<E> update(#PathVariable("id") PK id, #RequestBody E entity) { ... }
public ResponseEntity<E> remove(#PathVariable("id") PK id) { .. }
}
and delegate in the controller.
//Concrete controller, working with Foo entities
#Controller
#RequestMapping("/foo")
public class FooControllerImpl extends
AbstractCRUDControllerBean<Foo, Long> implements FooController {
//we are interested in using fetchall but not others
#RequestMapping(method=RequestMethod.GET)
public #ResponseBody ResponseEntity<Foo> fetch(#PathVariable("id") PK id) {
return fetchAll();
}
//fetch with id and date
#RequestMapping(value="/{id}/{date}", method=RequestMethod.GET)
public #ResponseBody ResponseEntity<Foo> fetch(#PathVariable("id") PK id, #PathVariable("date") Date date) { .... }
}
also, you can map method based on the availability of the parameters too,
#RequestMapping(value="/{id}/{date}", params={"param1","param2","!param3"})
public #ResponseBody ResponseEntity<E> customFetch(#PathVariable("id") PK id,
#PathVariable("date") Date date, #RequestParam("param1") String param1,
#RequestParam("param2") String param2) {...}
This method maps /foo/id/date when param1 and param2 exists and param3 does not exist.