I am using Spring Boot (1.5.3) to create a Spring REST Web Service. I have added spring-boot-starter-web as the only dependency (as per spring guide). Next I have created UserManagementService interface for my service class.
#RequestMapping("/usermanagement/v1")
public interface UserManagementService {
#RequestMapping(value = "/user/{id}/", method=RequestMethod.GET)
public UserTo getUserById(#PathVariable("id") long id);
#RequestMapping(value = "/users/", method=RequestMethod.GET)
public List<UserTo> getAllUsers();
}
And its implementation UserManagementServiceImpl
#RestController
public class UserManagementServiceImpl implements UserManagementService {
private Map<Integer, UserTo> users;
public UserManagementServiceImpl() {
users = new HashMap<>();
users.put(1, new UserTo(1, "Smantha Barnes"));
users.put(2, new UserTo(2, "Adam Bukowski"));
users.put(3, new UserTo(3, "Meera Nair"));
}
public UserTo getUserById(long id) {
return users.get(id);
}
public List<UserTo> getAllUsers() {
List<UserTo> usersList = new ArrayList<UserTo>(users.values());
return usersList;
}
}
I wanted to created a REST Web Service using Spring Boot with minimum configuration and thought this would work. But on accessing my the Web Service I am getting No Response. What I am missing?
Also, I have seen many projects where annotations are added to the interface rather than the implementation class. Which I think is better than annotating classes. It should work here, right?
As mentioned in the comments, not all annotations are supported on interfaces. The #PathVariable annotation for example won't work, so you'll have to put that on the implementation itself:
public UserTo getUserById(#PathVariable("id") long id) {
return users.get(id);
}
Additionally to that, you have a Map<Integer, UserTo>, but you're retrieving the users using a #PathVariable of type long. This won't work either, so either change the key of users to Long or the id parameter to int:
public UserTo getUserById(#PathVariable("id") int id) {
return users.get(id);
}
The reason for this is that 1L (long) is not the same as 1 (int). So retrieving a map entry wouldn't return any result for a long value.
Related
I am beginner with Java and Spring Boot, I use Pagination on Spring Boot, with this code I return the list of users, which I must if I want to also return the number of pages?
I know that with getTotalPages() I can get the page count but how to return it?
#Service
public class UserService{
#Autowired
UserRepository userRepository;
public List<UserDto> findAll(PageRequest pageRequest){
Page<User> userPage = userRepository.findAll(pageRequest);
List<UserDTO> dtos = new ArrayList<UserDTO>();
//return userPage.getContent();
for (User u : userPage.toList() ) {
dtos.add(new UserDTO(u));
}
return dtos;
}
}
The most common implementation of the Page interface is provided by the PageImpl class, you can use like this:
import org.springframework.data.domain.PageImpl;
...
Page<UserDTO> pageResult = new PageImpl<>(dtos,
userPage.getPageable(),
userPage.getTotalElements());
return pageResult;
If you want, you can also use the .map() function of page result, it can be preferred according to the approach. https://stackoverflow.com/a/39037297/2039546
I finally found a way to override methods of Spring Data REST with a custom implementation. Unfortunately this disables the default handling.
My Repository should contain findAll and findById exposed over the GET: /games and GET: /games/{id} respectively and save should not be exported because it is overriden by the controller.
#RepositoryRestResource(path = "games", exported = true)
public interface GameRepository extends Repository<Game, UUID> {
Collection<Game> findAll();
Game findById(UUID id);
#RestResource(exported = false)
Game save(Game game);
}
My controller should handle POST: /games, generate the game on the server and return the saved Game.
#RepositoryRestController
#ExposesResourceFor(Game.class)
#RequestMapping("games")
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces = "application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
However when I try to GET: /games it returns 405: Method Not Allowed but POST: /games works as intended. When I change the value of the generateNewGame mapping to "new" all three requests work. But POST: /games/new is no RESTful URL Layout and I would rather avoid it. I don't understand why I get this behaviour and how I may solve it. Does anybody have a clue?
Use #BasePathAwareControllerannotation above your controller to preserve default spring data rest paths and add new custom path base on your need. Although overwrite default spring data rest path.
#BasePathAwareController
public class CustomGameController {
private final GameService gameService;
public CustomGameController(GameService gameService) {
this.gameService = gameService;
}
#ResponseBody
#RequestMapping(value = "", method = RequestMethod.POST, produces =
"application/hal+json")
public PersistentEntityResource generateNewGame(#RequestBody CreateGameDTO
createGameDTO, PersistentEntityResourceAssembler assembler) {
Game game = gameService.generateNewGame(createGameDTO);
return assembler.toFullResource(game);
}
}
Maybe you can do something we usually do in Linux. Set a fake path and link to it.
POST /games ==> [filter] request.uri.euqal("/games") && request.method==POST
==> Redirect /new/games
What you see also is /games.
Don't use /games/new, it may be conflict with things inner Spring.
As we all know, there is a big problem with a partial update of the entity. Since the automatic conversion from json strings to the entity, all fields that have not been transferred will be marked null. And as a result, the fields that we did not want to reset will be reset.
I will show the classical scheme:
#RestController
#RequestMapping(EmployeeController.PATH)
public class EmployeeController {
public final static String PATH = "/employees";
#Autowired
private Service service;
#PatchMapping("/{id}")
public Employee update(#RequestBody Employee employee, #PathVariable Long id) {
return service.update(id, employee);
}
}
#Service
public class Service {
#Autowired
private EmployeeRepository repository;
#Override
public Employee update(Long id, Employee entity) {
Optional<T> optionalEntityFromDB = repository.findById(id);
return optionalEntityFromDB
.map(e -> saveAndReturnSavedEntity(entity, e))
.orElseThrow(RuntimeException::new);
}
private T saveAndReturnSavedEntity(Employee entity, Employee entityFromDB) {
entity.setId(entityFromDB.getId());
return repository.save(entity);
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
and as I have already said that in the current implementation we will not be able to perform a partial update in any way. That is, it is impossible to send an update of only one field in a json line; all fields will be updated, and in null (excepted passed).
The solution to this problem is that you need to perform the conversion from string json to the entity in manual. That is, do not use all the magic from Spring Boot (which is very sad).
I will also give an example of how this can be implemented using merge at the json level:
#RestController
#RequestMapping(EmployeeRawJsonController.PATH)
public class EmployeeRawJsonController {
public final static String PATH = "/raw-json-employees";
#Autowired
private EmployeeRawJsonService service;
#PatchMapping("/{id}")
public Employee update(#RequestBody String json, #PathVariable Long id) {
return service.update(id, json);
}
}
#Service
public class EmployeeRawJsonService {
#Autowired
private EmployeeRepository employeeRepository;
public Employee update(Long id, String json) {
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
return optionalEmployee
.map(e -> getUpdatedFromJson(e, json))
.orElseThrow(RuntimeException::new);
}
private Employee getUpdatedFromJson(Employee employee, String json) {
Long id = employee.getId();
updateFromJson(employee, json);
employee.setId(id);
return employeeRepository.save(employee);
}
private void updateFromJson(Employee employee, String json) {
try {
new ObjectMapper().readerForUpdating(employee).readValue(json);
} catch (IOException e) {
throw new RuntimeException("Cannot update from json", e);
}
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
With this solution, we eliminate the problem associated with the partial update.
But here another problem arises, that we are losing the automatic addition of validation of beans.
That is, in the first case, validation is enough to add one annotation #Valid:
#PatchMapping("/{id}")
public Employee update(#RequestBody #Valid Employee employee, #PathVariable Long id) {
return service.update(id, employee);
}
But we can't do the same when we perform manual deserialization.
My question is, is there any way to enable automatic validation for the second case?
Or maybe there are other solutions that allow you to use Spring Boot magic for Bean Validation.
What you need is not the normal validation , which can achieved through manual validator call.Let’s now go the manual route and set things up programmatically:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(object);
for (ConstraintViolation<User> violation : violations) {
log.error(violation.getMessage());
}
To validate a bean, we must first have a Validator object, which is constructed using a ValidatorFactory.
Normal validations on Spring Controllers specified with #Valid annotations are triggered automatically during the DataBinding phase when a request is served.All validators registered with the DataBinder will be executed at that stage. We can't do that for your case, so you can manually trigger the validation like above.
In spring doc, I can get following explanations for the difference of the spring mvc and spring rest.
Spring REST architecture is also based on Spring MVC, slightly making the difference on the View part. Traditional Spring MVC relies on the View technology to render the model data, the Spring REST architecture also does the same, except that the model object is set directly into the HTTP response, which the #ResponseBody converts into JSON/XML automatically. The output of a RESTful web service has to be a JSON or an XML, a standard format that could be easily handled across different consumer application platforms.
But in https://en.wikipedia.org/wiki/Representational_state_transfer.
It has a couple of feactures except for the json response like the rest will use the HTTP PUT/DELETE/POST method to manipulate resource.
I was wondering if below spring controller can be treated as a restful service. I have used #RestController to return json response, but did not use any other rest features.
#RestController
#RequestMapping(value = "/employee")
public class EmployeeController {
#RequestMapping(value = RequestAction.LOADLIST, method = RequestMethod.POST)
public List<Employee> list(#RequestBody Employee bo) {
System.out.println(bo);
return employeeList;
}
#RequestMapping(value = RequestAction.LOAD, method = RequestMethod.POST)
public Employee getEmployee(
#RequestBody Employee input) {
for (Employee employee : employeeList) {
if (employee.getId().equals(input.getId())) {
return employee;
}
}
return input;
}
#RequestMapping(value = RequestAction.ADD, method = RequestMethod.POST)
public Employee addEmployee(#RequestBody Employee bo) {
System.out.println(bo);
return bo;
}
#RequestMapping(value = RequestAction.UPDATE, method = RequestMethod.POST)
public Employee updateEmployee(#RequestBody Employee bo) {
System.out.println(bo);
for (Employee employee : employeeList) {
if (employee.getId().equals(bo.getId())) {
employee.setName(bo.getName());
return employee;
}
}
return bo;
}
}
Your example script is not REST because it change the url for each task, and use always POST verb. Spring REST use different HTTP verbs (GET, POST, DELETE) to differentiate the action. A few times sharing the same url.
Example:
#RestController
#RequestMapping("/users")
public class UsersController {
#GetMapping
public List<User> index() {...}
#GetMapping("{id}")
public User show(...) {...}
#PostMapping
public User create(...) {...}
#PutMapping("{id}")
public User update(...) {...}
#DeleteMapping("{id}")
public void delete(...) {...}
}
Your example is not following the conventionals of a REST API (e.g. GET for retrieval, POST for create, PUT for full update, PATCH for partial update, etc.), but it does not mean, that you can't. As others stated above, you might just got confused with the term. REST is a protocol and it has lots of conventionals for service usages, which if you follow, you can say that your service is REST or RESTful.
This page is the simple best source of tutoring you in this area:
https://restfulapi.net
More importantly this, when we are considering your example: https://restfulapi.net/http-methods/
I also check it sometimes.
I'm trying to create a generic controller using spring mvc 3.2.3 and spring security 3.1.3.
What i'm trying to achieve is something like this:
public abstract class DataController<E extends PersistentEntity> {
protected abstract E getEntity(String id);
#RequestMapping(value="/view/{id}", method=RequestMethod.GET)
public String view(#PathVariable("id") String id, ModelMap map) {
E ent = getEntity(id);
map.put("entity", entity);
return "showEntity";
}
}
My extended class will have a specific controller mapping in the class name so that i can access the url by using the controller name:
#Controller
#RequestMapping("/company**")
#Secured("ROLE_ADMIN")
public class CompaniesController extends DataController<Company> {
#Autowired
private AppService appService;
#Override
protected Company getEntity(String id) {
return appService.getCompany(id);
}
}
My problem is that the url /company/view is not secured by ROLE_ADMIN and can be accessed by anyone, (i think) because the /view is not defined in the controller where the #Secured is being used.
This can be fixed by just overriding the view method and define the mapping in my company class:
. . .
#Override
#RequestMapping(value = "/view/{id}", method = RequestMethod.GET)
public String view(String id, ModelMap map) {
return super.view(id, map);
}
. . .
In this case the security works correctly, but i want to know if there is another method. Since i have a lot of methods in my abstract class, this will create a problem and a mess to override all methods just to call the super.
Is there a way to fix this issue?
thanks all for the help :)
I know it's a year later, but I had the same problem and figured out a possible solution for this. It is not 100% annotation based, but works and is somewhat elegant
The abstract superclass:
#PreAuthorize("hasAnyRole(this.roles)")
public abstract class DataController<E extends PersistentEntity>
{
protected abstract E getEntity(String id);
protected abstract String[] getRoles();
#RequestMapping(value="/view/{id}", method=RequestMethod.GET)
public String view(#PathVariable("id") String id, ModelMap map) {
E ent = getEntity(id);
map.put("entity", entity);
return "showEntity";
}
}
On the subclass you simply implement getRoles() to return an array of roles that are required to access this class.
#PreAuthorize is another way to check authentication, that allows you to use SpEL expression. this.roles refers to he getRoles() property on the annotated object.