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.
Related
What I would like to achieve is to use interfaces for domain classes and generic types for service layer and be able to change the implementation of the persistence layer from current which is MongoDb to e.g. JPA. Interfaces for domain classes are necessary because of e.g different annotations for JPA and MongoDB (#Entity and #Document).
Let's look at the structure of the following demo project:
For each element of the domain model there can be three interfaces, let's explain it using the user package:
User - representation of domain object
UserDao - providing persistence layer methods
UserService - providing business logic methods
Here are interfaces for each of them:
public interface User {
String getId();
String getFirstName();
String getLastName();
List<Consent> getConsents();
Boolean getBlocked();
}
public interface UserDao <UserType extends User> {
UserType save(UserType user);
Optional<UserType> getById(String userId);
}
public interface UserService <UserType extends User> {
UserType create(String firstName, String lastName);
void addConsent(UserType user, ConsentType consentType);
}
As I mentioned earlier, current implementation of those interfaces is related to Mongo DB:
#Getter
#Setter
#Document(collection = "user")
public class MongoUser extends AbstractMongoCollection implements User {
private String firstName;
private String lastName;
private List<PojoConsent> consents;
private Boolean blocked;
void addConsent(PojoConsent consent) {
if(consents == null) {
consents = new ArrayList<>();
}
consents.add(consent);
}
#Override
public List<Consent> getConsents() {
return new ArrayList<>(consents);
}
}
#Component
public class MongoUserDao implements UserDao<MongoUser> {
private MongoUserRepository mongoUserRepository;
#Autowired
public MongoUserDao(MongoUserRepository mongoUserRepository) {
this.mongoUserRepository = mongoUserRepository;
}
#Override
public MongoUser save(MongoUser user) {
return mongoUserRepository.save(user);
}
#Override
public Optional<MongoUser> getById(String userId) {
return mongoUserRepository.findByIdAndDeletedIsFalse(userId);
}
}
#Component
public class MongoUserService implements UserService<MongoUser> {
private UserDao<MongoUser> userDao;
#Autowired
public MongoUserService(UserDao<MongoUser> userDao) {
this.userDao = userDao;
}
#Override
public MongoUser create(String firstName, String lastName) {
MongoUser user = new MongoUser();
user.setBlocked(false);
user.setFirstName(firstName);
user.setLastName(lastName);
user.setDeleted(false);
return userDao.save(user);
}
#Override
public void addConsent(MongoUser user, ConsentType consentType) {
PojoConsent pojoConsent = new PojoConsent();
pojoConsent.setActive(true);
pojoConsent.setType(consentType);
pojoConsent.setDate(LocalDateTime.now());
user.addConsent(pojoConsent);
userDao.save(user);
}
}
Ok, so what is the problem ? The problem occurs when I inject beans of type UserDao and UserService in other beans (as it happens in Spring Framework), like EntryPoint in this example (I'm aware of that there should be no logic in spring controller, but this is just an example):
#RestController
#RequestMapping("/api")
public class EntryPoint {
#Autowired
private ConversationService conversationService;
#Autowired
private UserDao userDao;
#PostMapping("/create/{userId}")
public ResponseEntity<String> createConversation(#PathVariable("userId") String userId) {
Optional<User> optionalUser = userDao.getById(userId);
if(optionalUser.isPresent()) {
User user = optionalUser.get();
Conversation conversation = conversationService.create(user, "default");
return ResponseEntity.ok(conversation.getId());
}
return ResponseEntity.notFound().build();
}
}
Interfaces ConversationService and UserDao have a generic type so warnings appear:
I don't want to give up generic types but on the other hand I'm aware that injecting without generic types will cause warnings which does not comply with clean code principles. It is true that this design will work despite warnings. I don't want to change implementation of the EntryPoint when I change persistence layer from MongoDb to JPA, I just want to provide new implementation for domain interfaces (User, UserDao, UserService etc.)
How to reconcile the interface issue for domain domain classes and injecting without generic type ?
Let's say I want to build a rest api for storing car information. To make it simple for the sake of this post let's say I would like it to look like this:
/api/cars/{carmake}/save
/api/cars/{carmake}/edit
/api/cars/{carmake}/delete
Now, let's say I have multiple car makes and each of them would require different car make services eg. BmwService, MercedesService, AudiService.
So this is my idea: one abstract controller that would look something like this:
#RequestMapping(value="/api/cars/")
public abstract class CarController {
protected final String CAR_MAKE;
public CarController(String carMake){
this.CAR_MAKE = carMake;
}
#PostMapping(value = CAR_MAKE + "/save")
public abstract void save(#Valid #RequestBody Serializable car)
#DeleteMapping(value = CAR_MAKE + "/delete")
public abstract void save(#Valid #RequestBody Serializable car);
#PatchMapping(value = CAR_MAKE + "/edit")
public abstract void save(#Valid #RequestBody Serializable car)
}
And then an actual controller could look something like this:
#RestController
public class AudiController extends CarController {
private AudiService audiService;
#Autowired
public AudiController(AudiService audiService){
super("audi");
this.audiService = audiService;
}
#Override
public void save(#Valid #RequestBody Serializable car) {
audiService.save((Audi) car);
}
.
.
.
}
The problem is that spring does not allow me to have the value for request mappings with a final variable if it is initialized through the constructor (if the CAR_MAKE is initialized right on the field declaration eg. protected final String CAR_MAKE = "s" it works). So is there any way to work around this so that the paths can come from each subclass?
Not near a compiler but something like this.
Implement a CarService interface:
public interface CarService {
String getBrand();
void save(Car car);
// ...
}
Implement AudiCarService, BmwCarService (etc) types that implement CarService.
Implement a CarService repository something like:
public class CarServiceRepository {
private Map<String, CarService> carServicesByBrand;
public Optional<CarService> findFor(String brand) {
return Optional.ofNullable(carServicesByBrand.get(brand));
}
#Autowired
public void setCarServicesByBrand(List<CarService> carServices) {
this.carServicesByBrand = carServices.stream().collect(Collectors.toMap(CarService::getBrand, Function.identity()));
}
}
Implement a single controller "CarController":
#RequestMapping(value="/api/cars")
#Component
public class CarController {
#Autowired
private CarServiceRepository carServiceRepository;
#PostMapping(value = "/{brand}/save")
public void save(#Valid #RequestBody Serializable car, #PathParam String brand) {
carServiceRepository.findFor(brand).ifPresent(carService -> carService.save(car));
}
// ...
}
Consider also favoring HTTP verbs over explicit verbs in URLs e.g. Why does including an action verb in the URI in a REST implementation violate the protocol?
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);
}
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.
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) {
}
}