Issue
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());
}
}
Solution
maybe custom annotation with AbstractNamedValueMethodArgumentResolver would be the best solution. Read P.S of the question for more details.
Answered By - Toshimichi
Answer Checked By - Timothy Miller (JavaFixing Admin)