Spring data. Sort using snake case parameter - spring

Could you tell me how to pass sort param to rest controller in snake_case format?
My entity class
#Entity
public class MyEntity extends BaseEntity{
#Id
private Long id;
#Column
private Long parentId;
}
Controller method
#GetMapping("list")
List<MyEntity> getEntityList(
#PageableDefault(sort = {"id"}, direction = Sort.Direction.DESC) Pageable pageable);
}
In request i want to use ?sort=parent_id
I've created Jackson2ObjectMapperBuilder and it builds correct snake_case json
But spring send me Caused by: org.springframework.data.mapping.PropertyReferenceException: No property parent found for type MyEntity! Did you mean 'parentId'? I think this is issue about parsing because spring cut everything after '_' symbol.
I've tried:
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
#Column(name = "parent_id") #JsonProperty("parent_id")
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)

I solved the problem. For correct working with snake_case parameter values we need to create custom OncePerRequestFilter. This filter prepare params before passing to controller.
User nullptr used same way here. By the way, using filter you can prepare any other params/values but this solution may produce mistakes. All requests will go through the filter.
#Configuration
public class SnakeCaseFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final Map<String, String[]> formattedParams = new ConcurrentHashMap<>();
//Convert values for each parameter
for (String param : request.getParameterMap().keySet()) {
String[] values = request.getParameterValues(param);
String formattedParam = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, param);
formattedParams.put(formattedParam, values);
}
filterChain.doFilter(new CustomResponseWrapper(request, formattedParams), response);
}
/**
* Wrapper return new formatted parameters
*/
class CustomResponseWrapper extends HttpServletRequestWrapper {
/**
* Constructs a request object wrapping the given request.
*
* #param request the {#link HttpServletRequest} to be wrapped.
* #throws IllegalArgumentException if the request is null
*/
private Map<String, String[]> params;
public CustomResponseWrapper(HttpServletRequest request, Map<String, String[]> params) {
super(request);
this.params = params;
}
#Override
public Map<String, String[]> getParameterMap() {
return params;
}
#Override
public String[] getParameterValues(String name) {
return params.get(name);
}
}
}

Related

Get Request/Response Body&Header in Spring AOP

I want to get request/response body and header within my aspect before and after if it's available or how to get those .
I mean i think with before annotation should be work for request,
with after annotation should be work for response. Can be ?
What I've tried so far :
I tried logbook library it's very complicated for me i could'nt figured it out how to work with that.So i gave up.
The actuator can do trick but I am doing extra work like how many times the endpoints called etc.So therefore i can't use actuator.
Also i tried to get request headers like below at least but i think this headers coming same all the time.I couldn't get httpservletresponse like how httpservetrequest does.
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
then
request.getHeader("date") but what about requestbody ?
how to get requestbody ? responsebody ? repsonseheader ?
My aspect file :
#Aspect
#Component
public class AppAspect implements ResponseInfo{
#Before("execution(#(#org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
public void loggingStartPointRequests(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
}
#After("execution(#(#org.springframework.web.bind.annotation.RequestMapping *) * *(..))")
public void loggingEndPointRequests(JoinPoint joinPoint) throws IOException {
}
}
My Controller Class:
#RestController
public class MainController {
#GetMapping("/people") //
public ResponseEntity<Poeple> getAllPeople(#RequestParam(name = "page", required = false) Integer page,
#RequestParam(name = "size", required = false) Integer size,
#RequestParam(name = "sortBy", required = false) Boolean sortByNameOrEpCount) {
doSomething();
}
}
I had the same problem and if you have your #Aspect annotated with #Component (or any #Autowired candidate) you can simply get the HttpServletRequest like this:
#Aspect
#Component
public class SomeAspect {
#Autowired
HttpServletRequest request;
#Before("...")
public void beforeAdvice(JoinPoint jp){
/* You will have the current request on the request property */
System.out.println(request.getRequestURL());
}
}
I know this is an old question but I hope it'll be helpful.
I think what you need is to implement the interface HandlerInterceptor, it would help you being able to inspect the request and the response. For example:
public class ApiMonitor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// when the client access to your endpoint
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// when you finished your process
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// after you already returned an answer to the client
}
}
If you want to operate with the object that you're returning just before you send it to the client, then you need AOP, yes. That's an example of how I do it to modify an object on certain endpoints just before it's parsed to json.
#Component
#Aspect
public class MyCustomAOPInterceptor {
/**
* These poincuts check the execution of a method in any (*)
* class of my.package.controller and that start with
* get/list/find plus any other word (*) . For example
* my.package.controller.UserController.getUserById()
*/
#Pointcut("execution(* my.package.controller.*.get*(..))")
public void petitionsStartWithGet() { }
#Pointcut("execution(* my.package.controller.*.list*(..))")
public void petitionsStartWithList() { }
#Pointcut("execution(* my.package.controller.*.find*(..))")
public void petitionsStartWithFind() { }
#AfterReturning(pointcut = "petitionsStartWithGet() || petitionsStartWithList() || petitionsStartWithFind()", returning = "result")
public void translateEntities(JoinPoint joinPoint, Object result) {
// do your stuff; result is the object that you need
}
}

How to override BroadleafCategoryController [Broadleaf Commerce]

When i try to override the BroadleafCategoryController handleRequest method everything works fine for me. But when i try to call the same method as show below with the Device parameter to identify the user device
In my Java Class CategoryController.java
#Controller("blCategoryController")
public class CategoryController extends BroadleafCategoryController {
private static final Logger logger = LoggerFactory.getLogger(CategoryController.class);
#RequestMapping("/")
public ModelAndView handleRequest(Device currentDevice, HttpServletRequest request, HttpServletResponse response) throws Exception {
return super.handleRequest(request, response);
}
}
then when it goes inside the super method (handleRequest) i get the Category Object value as NULL.
Category category = (Category) request.getAttribute(CategoryHandlerMapping.CURRENT_CATEGORY_ATTRIBUTE_NAME);
The above value should have been set through CategoryHandlerMapping
public class CategoryHandlerMapping extends BLCAbstractHandlerMapping {
public static final String CURRENT_CATEGORY_ATTRIBUTE_NAME = "category";
protected String defaultTemplateName = "catalog/category";
private String controllerName = "blCategoryController";
#Override
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
Category category = null;
if (allowCategoryResolutionUsingIdParam()) {
category = findCategoryUsingIdParam(request);
}
if (category == null) {
category = findCategoryUsingUrl(request);
}
if (category != null) {
request.setAttribute(CURRENT_CATEGORY_ATTRIBUTE_NAME, category);
return controllerName;
}
return null;
}
The CategoryHandlerMapping (provided by Broadleaf Commerce) has been configured inside a class annotated with #Configuration annotation and object is created inside a method annotated with #Bean annotation.
#Bean
public HandlerMapping categoryHandlerMapping() {
CategoryHandlerMapping mapping = new CategoryHandlerMapping();
mapping.setOrder(5);
return mapping;
}
Note:- All the configuration related to Device (Spring-Mobile) is fine
Please let me know if I am missing any xml config like applicationContext.xml ?
Thanks in advance !!!
The category controller is a little different, and is not resolved by the RequestMappingHandlerMapping (which is what resolves your #RequestMapping piece). Instead, as you mentioned, the only way the handleRequest() method of the CategoryController is invoked is via the CategoryHandlerMapping. That is the piece that is handling the category URL.
It looks like you are just trying to figure out the current Device on the category page. Spring Mobile has a facility to do this with simply DeviceUtils.getCurrentDevice(request). So:
Remove the #RequestMapping from handleRequest()
Rewrite your method like this:
#Controller("blCategoryController")
public class CategoryController extends BroadleafCategoryController {
private static final Logger logger = LoggerFactory.getLogger(CategoryController.class);
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
Device currentDevice = DeviceUtils.getCurrentDevice(request);
return super.handleRequest(request, response);
}
}

Spring: how to pass objects from filters to controllers

I'm trying to add a Filter that creates an object that is then to be used inside a controller in a Spring Boot application.
The idea is to use the Filter as a "centralized" generator of this object - that is request-specific and useful only in a controller.
I've tried to use the HttpServletRequest request.getSession().setAttribute method: I can access my object in the controller, but then it will be (clearly) added to the session.
Are the Filters the right way to do so? If yes, where can I keep the temporary object generated by the filter to be used by the controllers?
Why Don't you use a Bean with the #Scope('request')
#Component
#Scope(value="request", proxyMode= ScopedProxyMode.TARGET_CLASS)
class UserInfo {
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String password;
}
and then you can Autowireed this bean in both filter and controller to do setting and getting of data.
lifecycle of this UserInfo bean is only exisits within the request so once the http request is done then it terminates the instance as well
you can use ServletRequest.setAttribute(String name, Object o);
for example
#RestController
#EnableAutoConfiguration
public class App {
#RequestMapping("/")
public String index(HttpServletRequest httpServletRequest) {
return (String) httpServletRequest.getAttribute(MyFilter.passKey);
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Component
public static class MyFilter implements Filter {
public static String passKey = "passKey";
private static String passValue = "hello world";
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request.setAttribute(passKey, passValue);
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
}
An addition to wcong's answer.
Since Spring 4.3 after setting the attribute by using request.setAttribute(passKey, passValue);, you can access the attribute in your controller by simply annotating it with #RequestAttribute.
ex.
#RequestMapping("/")
public String index(#RequestAttribute passKey) {
return (String) passKey;
}
I dont know actually what is the scenario but If you really want to create an object in a filter and then use it somewhere in the code then you may use ThreadLocal class to do so.
To get know how this work see the most voted answer from that question Purpose of ThreadLocal?
In general using ThreadLocal you will be able to create a class that can store objects available ONLY for the current thread.
Sometimes for optimization reasons the same thread can be used to serve subsequent request as well so it will be nice to clean the threadLocal value after the request is processed.
class MyObjectStorage {
static private ThreadLocal threadLocal = new ThreadLocal<MyObject>();
static ThreadLocal<MyObject> getThreadLocal() {
return threadLocal;
}
}
in the filter
MyObjectStorage.getThreadLocal().set(myObject);
and in the Controller
MyObjectStorage.getThreadLocal().get();
Instead of filter you can use also #ControllerAdvice and pass objects to specified Controllers by using model.
#ControllerAdvice(assignableTypes={MyController.class})
class AddMyObjectAdvice {
// if you need request parameters
private #Inject HttpServletRequest request;
#ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("myObject", myObject);
}
}
#Controller
public class MyController{
#RequestMapping(value = "/anyMethod", method = RequestMethod.POST)
public String anyMethod(Model model) {
MyObjecte myObject = model.getAttribute("myObject");
return "result";
}
}

Mapping HTTP request value to object in Sprint Boot application when field and value has different field name?

This instance of class AuthorizationRequest is created during HTTP request, params are sent in query string.
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
I would like to use this code, this is an example parameter from AuthorizationRequest class:
#NotEmpty
#JsonProperty("client_id")
private String clientId;
but new instance has a filed clientId empty, because in query string there is a value for this parameter under client_id parameter.
Is there some way how to tell Spring which parameter from HTTP request should use for one particular field of created instance? I need to solve problem with different naming clientId andclient_id`.
What you need is a setter to handle each kind of clientId. Keep in mind that if both clientId and client_id is specified that it is unknown which will take precedence.
//These methods will allow clientId or client_id
to be used as arguments setting the same field this.clientId
public void setClient_id(String client_id) {
this.clientId = client_id;
}
public void setClientId(String client_id) {
this.clientId = client_id;
}
I tested this with a post and a get
get - http://localhost:8080/authorize?clientId=2&username=someusername
get - http://localhost:8080/authorize?client_id=2&username=someusername
post - http://localhost:8080/authorize
Content-Type: application/x-www-form-urlencoded
Body: clientId=2&username=someusername
or Body: client_id=2&username=someusername
I was only able to have #JsonProperty("client_id") to be recognized when I annotated AuthorizationRequest with #RequestBody and then used application/json instead of application/x-www-form-urlencoded
I found the solution with own implementation of org.springframework.web.method.supportHandlerMethodArgumentResolver.
Resolver implementation:
public class AuthorizationRequestResolver implements HandlerMethodArgumentResolver {
private static Logger LOG = Logger.getLogger(AuthorizationRequestResolver.class);
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AuthorizationRequest.class);
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
AuthorizationRequest authRequest = mapFromServletRequest(request);
return authRequest;
}
private AuthorizationRequest mapFromServletRequest(HttpServletRequest request) {
AuthorizationRequest authorizationRequest = new AuthorizationRequest();
authorizationRequest.setClientId(request.getParameter("client_id"));
authorizationRequest.setRedirectUri(request.getParameter("request_uri"));
authorizationRequest.setResponseType(request.getParameter("response_type"));
authorizationRequest.setScope(request.getParameter("scope"));
authorizationRequest.setState(request.getParameter("state"));
return authorizationRequest;
}
}
and cofiguration class:
#Configuration
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestResolver());
}
}

Dynamic post logout redirection url based on user?

i am wondering how i could implement a post logout redirection using a custom logout handler. I have implemented a CustomLogoutSuccessHandler but i have no way off access http session data that has previous been set by the user who has logged in. The data is alway empty...
class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
private static final ThreadLocal<Authentication> AUTH_HOLDER = new ThreadLocal<Authentication>()
void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
AUTH_HOLDER.set authentication
// reading session variable...
request.session?.variable // but this is always empty
try {
super.handle(request, response, authentication)
}
finally {
AUTH_HOLDER.remove()
}
}
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = AUTH_HOLDER.get()
String url = super.determineTargetUrl(request, response)
// do something with the url based on session data..
url
}
}
I do not know if there is any easy way to do this but came up with the below solution.
All you have to do is set the setTargetUrlParameter in your LogoutSuccessHandler. For that I made use of the implementation of HttpServletRequestWrapper written by Lincoln Baxter, III here for adding a parameter to the current request. Here is the relevant code.
public class PrettyFacesWrappedRequest extends HttpServletRequestWrapper
{
private final Map<String, String[]> modifiableParameters;
private Map<String, String[]> allParameters = null;
/**
* Create a new request wrapper that will merge additional parameters into
* the request object without prematurely reading parameters from the
* original request.
*
* #param request
* #param additionalParams
*/
public PrettyFacesWrappedRequest(final HttpServletRequest request,
final Map<String, String[]> additionalParams)
{
super(request);
modifiableParameters = new TreeMap<String, String[]>();
modifiableParameters.putAll(additionalParams);
}
#Override
public String getParameter(final String name)
{
String[] strings = getParameterMap().get(name);
if (strings != null)
{
return strings[0];
}
return super.getParameter(name);
}
#Override
public Map<String, String[]> getParameterMap()
{
if (allParameters == null)
{
allParameters = new TreeMap<String, String[]>();
allParameters.putAll(super.getParameterMap());
allParameters.putAll(modifiableParameters);
}
//Return an unmodifiable collection because we need to uphold the interface contract.
return Collections.unmodifiableMap(allParameters);
}
#Override
public Enumeration<String> getParameterNames()
{
return Collections.enumeration(getParameterMap().keySet());
}
#Override
public String[] getParameterValues(final String name)
{
return getParameterMap().get(name);
}
}
and then in the CustomLogoutSuccessHandler, I add this targetUrl as the parameter like this:
#Component
public class MyCustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
#Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
HttpServletRequest wrappedRequest = request;
if (authentication != null) {
//do something with the Principal and add the corresponding url
Map<String, String[]> extraParams = new TreeMap<String, String[]>();
extraParams.put("targetUrl", new String[] {"/target.xhtml"});
wrappedRequest = new PrettyFacesWrappedRequest(request, extraParams);
setTargetUrlParameter("targetUrl");
}
setDefaultTargetUrl("/general/main.xhtml");
super.onLogoutSuccess(wrappedRequest, response, authentication);
}
}
and the relevant change to the applicationContext:
<http>
<logout logout-url="/j_spring_security_logout"
success-handler-ref="myCustomLogoutSuccessHandler"
invalidate-session="true"/>
</http>
<beans:bean id="myCustomLogoutSuccessHandler" class="com.examples.MyCustomLogoutSuccessHandler"/>

Resources