I have URL for user dashboard(/users/{userId}).
I wanted to support /users/current for current logged user, and I searched for ways to implement that, and I did that by the following code.
However, I think it’s too overwhelming and I wonder if there are better/simpler ways to do that.
@Controller @RequestMapping("/users/{target}") public class UserController { @GetMapping public String get(@PathVariable User target) { return "dashboard"; } @PostMapping public String put(@PathVariable User target, ...) { ... } @GetMapping("licenses") public String getLicenses(@PathVariable User target, ...) { ... } ... }
@Configuration @RequiredArgsConstructor public class MethodHandlersConfig { private final RequestMappingHandlerAdapter adapter; @PostConstruct public void prioritizeCustomArgumentMethodHandlers() { List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(adapter.getArgumentResolvers()); List<HandlerMethodArgumentResolver> customResolvers = adapter.getCustomArgumentResolvers(); argumentResolvers.removeAll(customResolvers); argumentResolvers.addAll(0, customResolvers); adapter.setArgumentResolvers(argumentResolvers); } }
@Component @RequiredArgsConstructor public class UserResolver extends PathVariableMethodArgumentResolver { private final UserService userService; @Override public boolean supportsParameter(MethodParameter parameter) { return User.class.isAssignableFrom(parameter.getParameterType()); } @Override @SuppressWarnings("rawtypes") protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { User user; HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class); Map path = (Map) servletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); String target = (String) path.get(name); if (target.equals("current")) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth == null) { throw new InsufficientAuthenticationException("User is not authenticated"); } user = (User) auth.getPrincipal(); } else { user = userService.loadUserByUsername(target); } request.setAttribute("username", target, RequestAttributes.SCOPE_REQUEST); request.setAttribute("user", user, RequestAttributes.SCOPE_REQUEST); return user; } }
P. S. I think I made a much better solution with custom annotation (@UserPath)
@Component @RequiredArgsConstructor public class UserResolver extends AbstractNamedValueMethodArgumentResolver implements UriComponentsContributor { private final UserService userService; @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(UserPath.class); } @Override protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { UserPath annotation = parameter.getParameterAnnotation(UserPath.class); return new NamedValueInfo(annotation.name(), true, ValueConstants.DEFAULT_NONE); } @SuppressWarnings("unchecked") protected String getValue(String name, WebRequest request) { Map<String, String> vars = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); return vars.get(name); } @Override @Nullable protected User resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { UserPath annotation = parameter.getParameterAnnotation(UserPath.class); String userObj = getValue(name, request); User current = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (userObj.equals("current")) { return current; } else { User found = userService.loadUserByUsername(userObj); if (annotation.admin() && !current.getId().equals(found.getId()) && !current.getAuthorities().contains(UserAuthority.ROLE_ADMIN)) { throw new InsufficientAuthenticationException("Admin permission is required"); } return found; } } @Override protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) { User user = (User) arg; String userObj = getValue(name, webRequest); mavContainer.addAttribute("user", user); mavContainer.addAttribute("username", userObj); } @Override public void contributeMethodArgument(MethodParameter parameter, Object value, UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) { uriVariables.put(parameter.getParameterAnnotation(UserPath.class).value(), value.toString()); } }
Advertisement
Answer
maybe custom annotation with AbstractNamedValueMethodArgumentResolver would be the best solution. Read P.S of the question for more details.