I have the following interfaces
#Path("/v1")
public interface IV1Api {
}
#Path("/Accounts/{AccountId}")
public interface IAccountsInstanceApi extends IV1Api {
}
#Path("/Users")
public interface IUsersListApi extends IAccountsInstanceApi {
#GET
Json listUsers();
#POST
Json createUser();
}
public UsersListResource implements IUsersListApi {
// ...
}
I was expecting my user list resource path to be /v1/Accounts/123/Users, but it is /Users. What am I doing wrong?
Sorry, but it doesn't work like this. You can do the following:
#Path(IAccountsInstanceApi.PATH)
public interface IAccountsInstanceApi extends IV1Api {
String PATH = "/Accounts/{AccountId}";
}
#Path(IUsersListApi.PATH)
public interface IUsersListApi extends IAccountsInstanceApi {
String PATH = IAccountsInstanceApi.PATH + "/Users";
#GET
Json listUsers();
#POST
Json createUser();
}
Related
I have defined an class for some specific handling:
public abstract class ListProvider {
...
public abstract ResponseObject getResponse(RequestObject request) {}
}
I will create several derived class that I will define as services.
The purpose is to use it to create a json API using Jackson to deserialize the RequestObject and to serialize the ResponseObject. For example:
#Service
public class ClientListProvider extends ListProvider {
public ResponseObject getResponse(RequestObject request) {
return ...
}
I can use it in a controller like that:
#RestController
#RequestMapping("/client")
public class ClientController {
#AutoWired
ClientListProvider provider;
#PostMapping("/list")
public ResponseObject ResponseObject list(#RequestBody RequestObject request) {
return provider.getResponse(request);
}
}
But I would like to use it without the boiler plate, like:
#RestController
#RequestMapping("/client")
public class ClientController {
#PostMapping("/list")
#Provider(ClientListProvider.class)
public list() {}
}
Or maybe:
#RestController
#RequestMapping("/client")
#Provider(ClientListProvider.class,path="/list")
public class ClientController {
}
Or something like that.
Do you know if there any way? If necessary I could replace the Request/ResponseObject by HttpServletRequest/Response or something else in the ListProvider interface.
Dynamic Parent Controller
#CrossOrigin("*")
public abstract class RealtimeController<T> {
public abstract RealtimeService<T> getService();
#PostMapping(value = "/find")
public ResponseEntity<Iterable<T>> find(#RequestBody T entity) {
return ResponseEntity.ok(getService().findAllByKey(entity));
}
}
Dynamic Service
public abstract class RealtimeService<T> {
public abstract RealtimeRepository<T> getRepository();
public Iterable<T> findAllByKey(T entity) {
return getRepository().findAll(
Example.of(
entity,
ExampleMatcher.matching().withIgnorePaths("_class"))
);
}
}
Child Controller1
#Api(tags = "Child1")
#RestController
#RequestMapping("/api/v1/child1")
#Slf4j
public class Child1Controller extends RealtimeController<Child1> {
#Autowired
private Child1Service child1Service;
#Override
public RealtimeService<Child1> getService(){
return child1Service;
}
}
Child Controller2
#Api(tags = "Child2")
#RestController
#RequestMapping("/api/v1/child2")
#Slf4j
public class Child2Controller extends RealtimeController<Child2> {
#Autowired
private Child2Service child2Service;
#Override
public RealtimeService<Child2> getService(){
return child2Service;
}
}
Question1:
I have 10 of these child controllers. Is there a way to dynamically create them from some config? The only difference between every child controller is the Model, tag and endpoint. My idea was to read these config from a property file and generate these controllers dynamically somehow. is it possible?
application.yaml
controllers:
- name: Child1
endpoint: /api/v1/child1
model: Child1
- name: Child2
endpoint: /api/v1/child2
model: Child2
Question2:
Is there another better way to achieve similar results without code duplication ?
You can do
#CrossOrigin("*")
#RestController
#RequestMapping("/api/v1/{childtype}")
public class RealtimeController {
public RealtimeService getService(String childtype){
return map.get(childtype);
}
#PostMapping(value = "/find")
public ResponseEntity<Iterable<Object>> find(#PathVariable("childtype") String childtype, #RequestBody Object entity) {
return ResponseEntity.ok(getServiceByType(childtype).findAllByKey(entity));
}
}
All you need now is to fill map with instances of service.
I want autowire a class which implements in a Component. Here is a part of the interface:
#Service
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class GenericResourceHandlerService<T extends ResourceRequest, A extends ResultType, B extends ResourceService<T, A>> {
private final B service;
public Response get(String x) {
various checks(x, service.getType());
B res = service.get(x);
if (res!= null) {
return Response.status(Response.Status.OK).entity(res).build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
}
}
}
Then, a class which implements ResourceService would look like this:
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class TypeOneService implements EntityService<CityRequest, City> {
private final Repository repository;
#Override
public ResultType getType() {
return ResultType.TYPE_ONE;
}
#Timed
public TYPE_ONE get(String resource) {
return repository.get(resource);
}
}
And the interface itself, looks like this:
public interface EntityService<T extends EntityRequest, A extends ReturnableEntity> {
ResourceType getResourceType();
A get(String resource);
}
Now, I have a set of controllers which tries to autowire GenericResourceHandlerService and call it's get method. Which looks like this:
public class TypeOneController {
private final TypeOneService typeOneService;
private final GenericResourceHandlerService<TypeOneRequest, TypeOne, TypeOneService> genericResourceHandlerService;
public Response getListItemByResource(
String resource
) {
return genericResourceHandlerService.get(resource);
}
}
Or this:
public class TypTwoController {
private final TypeTwoService typeTwoService;
private final GenericResourceHandlerService<TypeTwoRequest, TypeTwo, TypeTwoService> genericResourceHandlerService;
public Response getListItemByResource(
String resource
) {
return genericResourceHandlerService.get(resource);
}
}
This compiles but when the app starts then, then I get the following error message:
Parameter 0 of constructor in path.GenericResourceHandlerService required a single bean, but 2 were found:
- typeOneSerivce: defined in file [C:\Path\TypeOneService.class]
- typeTwoService: defined in file [C:\Path\TypeTwoService.class]
I think this is because, Spring Boot can't work out which one to service to autowire with. Is it possible what I am trying to do?
Spring tries to inject a bean to resolve the GenericResourceHandlerService.service but service has type B and B extends ResourceService. And spring found 2 beans implementing this interface so doesn't know which implementation to autowire..
You can put #Qualifier on field service but I imagine you will lost the genericity of this type GenericResourceHandlerService
Maybe the best way is to let the controller pass through the implementation in the GenericResourceHandlerService and let this last as a simple pojo..not a spring bean (so remove #Service on GenericResourceHandlerService
Like this
public class TypeOneController {
#Autowired
private final TypeOneService typeOneService;
private final GenericResourceHandlerService<TypeOneRequest, TypeOne, TypeOneService> genericResourceHandlerService = new GenericResourceHandlerService(typeOneService);
public Response getListItemByResource(
String resource
) {
return genericResourceHandlerService.get(resource);
}
}
I am not sure of using inheritance / interface implementation in particular situation.
In my simple Spring MVC application I have #Entity class TennisPlayer, which is inherited from abstract class Player (TennisPlayer adds some attributes).
Also I have class TennisPlayerForm, which is inherited from abstract class PlayerForm (TennisPlayerForm adds some attributes again).
User fills the form about tennis player in .jsp page and TennisPlayerForm object is used to represent filled values and then on the basis of this object is created TennisPlayer object and saved into database.
Creation of TennisPlayer object is responsibility of class TennisPlayerDbService. This class is implementation of interface PlayerService.
I have following #Controller, which handles requests:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayerForm") #Valid TennisPlayerForm tennisPlayerForm,
BindingResult result, RedirectAttributes redirectAttributes) {
playerService.createPlayer(tennisPlayerForm);
return "redirect:/allplayers";
}
}
Part of my source code looks like this:
public interface PlayerService {
public void createPlayer(PlayerForm playerForm);
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(PlayerForm playerForm) {
TennisPlayerForm tennisPlayerForm = null;
if (playerForm instanceof TennisPlayerForm) {
tennisPlayerForm = (TennisPlayerForm) playerForm;
}
else {
throw new IllegalArgumentException("Must be of type TennisPlayerForm.");
}
TennisPlayer player = new TennisPlayer();
player.setName(tennisPlayerForm.getName());
player.setSurname(tennisPlayerForm.getSurname());
player.setAge(tennisPlayerForm.getAge());
player.setRacket(tennisPlayerForm.getRacket());
player.setRanking(tennisPlayerForm.getRanking());
player.setSponsor(tennisPlayerForm.getSponsor());
player.setCoach(tennisPlayerForm.getCoach());
player.setClub(tennisPlayerForm.getClub());
dao.saveAndFlush(player);
}
}
Is it justified to use inheritance and interface implementations like this in this situation, when concrete implementation of PlayerService (TennisPlayerDbService) expects instance of particular class, although these potential classes have common parent?
Finally I solved my problem according to your comments and answers.
I deleted PlayerForm abstract class, TennisPlayerForm and mixed javax.validation and javax.persistence annotations in #Entity classes Player and Tennis Player.
Previously mentioned code now looks like this:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService<TennisPlayer> playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayer") #Valid TennisPlayer tennisPlayer,
BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
return "newplayer";
}
playerService.createPlayer(tennisPlayer);
MessageUtil.flash(locale, redirectAttributes, "success", "signup.success");
return "redirect:/allplayers";
}
}
public interface PlayerService<T extends Player> {
public void createPlayer(T player);
public List<T> getAllPlayers();
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService<TennisPlayer> {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(TennisPlayer player) {
dao.saveAndFlush(player);
}
#Override
public List<TennisPlayer> getAllPlayers() {
return dao.findAll();
}
}
Normally your service does not need to know you are working with a form. Your form is purely created to be the model in the model-view-controller architecture of your webpage. (your jsp being the view and your controller being the c-part)
Are you also planning on using other types of players than a TennisPlayer? If not it all seems like premature optimisation and you should keep it as simple as possible.
I have this resource which is located perfectly:
#Path("/adoptable")
public class AdoptableAnimalsResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String get()
{
return "dogs";
}
}
Now, how can I turn this class into a nested inner class?
For example,
public class Grouper
{
#Path("/adoptable")
public class AdoptableAnimalsResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String get()
{
return "dogs";
}
}
}
When I try it, I get a 404 Not Found error, suggesting that Jersey is not considering the inner class as a resource.
You need to use Sub-Resource Locators. Basically, you will have a method in Grouper class, which will instantiate the AdoptableAnimalsResource class. The AdoptableAnimalsResource should not have a #Path annotation. It could but it will be ignored. It's methods can have sub-resource #Paths. The method in the Grouper class should have #Path that identities the AdoptableAnimalsResource sub-resource.
So it might look something like
#Path("/groups")
public class Grouper {
#Path("/adoptable")
public AdoptableAnimalsResource animalSubResource() {
return new AdoptableAnimalsResource();
}
public class AdoptableAnimalsResource {
#GET
#Produces(MediaType.TEXT_PLAIN)
public String get() {
return "dogs";
}
}
}