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?
Related
Friends,
Here are my Java objects
#Data
public class CarDto {
private String make;
private String model;
private int year;
}
#Data
public class Car {
private MakeEnum make;
private String model;
private int year;
}
For consuming, I need to do something like this
#Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
Car toModel(CarDto carDto);
CarDto toDto(Car carModel);
}
// Using mapper
Car carModel = CarMapper.INSTANCE.toModel(carDto);
But I am looking a solution, where I could do this:
Car carModel = Mapper.map(carDto, Car.class);
How do do this? Didn't find an example where I can dynamically map based on a Type. I found this method very handy in both ModelMapper & Google gson. Appreciate your help!
If I understand you correctly, you require a kind of repo.
Another option would be to look at sprint and the new MapStruct spring integration, recently developed: https://github.com/mapstruct/mapstruct-spring-extensions. It was designed as a follow up of this question.
There's an example in the example repo. It's not straightforward though: https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapper-repo
Before calling the mapping, you have to setup and interface of MapStruct like this:
#Mapper(componentModel = "spring")
public interface MapStructMapper {
ObjectDto objectToObjectDto(Object object);
}
Then an implementation of it :
#Component
public class MapStructMapperImpl implements MapStructMapper {
#Override
public ObjectDto objectToObjectDto(Object object) {
if ( object == null ) { return null; }
ObjectDto objectDto = new ObjectDto();
objectDto.setId( object.getId() );
objectDto.setName( object.getName() );
return objectDto;
}
and then, you just have to inject this interface in the controller and invoke the repository like this:
#RequestMapping("/objects")
public class ObjectController {
private MapStructMapper mapstructMapper;
private ObjectRepository objectRepository;
#Autowired
public ObjectController(
MapStructMapper mapstructMapper,
ObjectRepository objectRepository
) {
this.mapstructMapper = mapstructMapper;
this.objectRepository = objectRepository;
}
#GetMapping("/{id}")
public ResponseEntity<ObjectDto> getById(#PathVariable(value = "id") int id){
return new ResponseEntity<>(
mapstructMapper.objectToObjectDto(
objectRepository.findById(id).get()
),
HttpStatus.OK
);
}
}
Of course, you can call a service/serviceImpl instead of a direct call to the repository but it's to be as simple as possible. :)
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 ?
I have a controller which produces JSON, and from this controller, I return an entity object, which is automatically serialized by Jackson.
Now, I want to avoid returning some fields based on a parameter passed to the controller. I looked at examples where this is done using FilterProperties / Mixins etc. But all the examples I saw requires me to use ObjectMapper to serialize / de-serialize the bean manually. Is there any way to do this without manual serialization? The code I have is similar to this:
#RestController
#RequestMapping(value = "/myapi", produces = MediaType.APPLICATION_JSON_VALUE)
public class MyController {
#Autowired
private MyService myService;
#RequestMapping(value = "/test/{variable}",method=RequestMethod.GET)
public MyEntity getMyEntity(#PathVariable("variable") String variable){
return myservice.getEntity(variable);
}
}
#Service("myservice")
public class MyService {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity getEntity(String variable){
return myEntityRepository.findOne(1L);
}
}
#Entity
#Table(name="my_table")
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyEntity implements Serializable {
#Column(name="col_1")
#JsonProperty("col_1")
private String col1;
#Column(name="col_2")
#JsonProperty("col_2")
private String col2;
// getter and setters
}
Now, based on the value of "variable" passed to the controller, I want to show/hide col2 of MyEntity. And I do not want to serialize/deserialize the class manually. Is there any way to do this? Can I externally change the Mapper Jackson uses to serialize the class based on the value of "variable"?
Use JsonView in conjunction with MappingJacksonValue.
Consider following example:
class Person {
public static class Full {
}
public static class OnlyName {
}
#JsonView({OnlyName.class, Full.class})
private String name;
#JsonView(Full.class)
private int age;
// constructor, getters ...
}
and then in Spring MVC controller:
#RequestMapping("/")
MappingJacksonValue person(#RequestParam String view) {
MappingJacksonValue value = new MappingJacksonValue(new Person("John Doe", 44));
value.setSerializationView("onlyName".equals(view) ? Person.OnlyName.class : Person.Full.class);
return value;
}
Use this annotation and set the value to null, it will not be serialised:
#JsonInclude(Include.NON_NULL)
I am using spring 4 REST.
I have a base class and many other class extends the same.
For example, Employee is the base class and other classes hr, engineer, trainer etc etc extends the employee.
I have to create REST API to create the different type of employee.
The interface is one POST which accepts all the type of employees. I cannot create different interface for each sub type. From the base, I know what is the sub type.
#RequestMapping(value= "/test/{employeeType}", method = RequestMethod.POST)
public void createEmp(#RequestBody Employee employee){
//If type is HR, I want to cast to HR type
//Is there any way we can take generic object in spring rest and then manage internally ?
}
Maybe try this?
#RequestMapping(value= "/test/{employeeType}", method = RequestMethod.POST)
public void createEmp(#PathVariable String employeeType, #RequestBody EmployeeDTO employeeDTO){
transform(employeeType,employeeDTO);
}
Here EmployeeDTO will contain all possible parameters so it can construct any of the child classes then based on the employeeType you just transform into domain object(Employee)?
Edit2 as requested
Here is sample code:
public class Employee {
private String name;
}
public class Hr extends Employee {
private String department;
}
public class Hr extends Employee {
private String department;
}
Then the DTO class should look like this:
public class EmployeeDTO {
private String name;
private String course;
private String department;
}
Then when you know your type you transform to whatever your want with all the necessary values in the DTO
Edit: Now when I think about it this also may be an option but I will need to see your classes.
#RequestMapping(value= "/test/employee", method = RequestMethod.POST)
public void createEmp(#RequestBody Employee employee){
#RequestMapping(value= "/test/hr", method = RequestMethod.POST)
public void createHr(#RequestBody Hr hr){
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.