In my spring rest application I need to read the headers information for PUT and POST calls and set those information in the bean passed as #RequestBody. currently what am doing is like follows.
#RequestMapping(value = "/create", method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
#ResponseBody
public ReportRepresentation createDailyReport(#RequestBody ReportEntity reportEntity,
#RequestHeader(value= "FIRST_HEAD1", required = false) boolean isHeaderSet,
#RequestHeader(value= "SECOND_HEAD2", required = false) Long scondHead) {
// Setting the header values into bean properties .
}
So am extracting the headers in all methods(POST and PUT) and setting values in different entities.
My question is is there any way to parse/ override the #RequestBody in method param in global level and set those headers?
You can use RequestBodyAdvice:
For example:
Bean:
#Data
public class MyBean {
private String property;
}
Controller:
#RestController
public class MyController {
#RequestMapping("/")
public MyBean get(#RequestBody MyBean myBean) {
return myBean;
}
}
Advisor:
#ControllerAdvice(annotations = RestController.class)
public class MyRequestBodyAdvisor extends RequestBodyAdviceAdapter {
#Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.getParameterType() == MyBean.class;
}
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
MyBean myBean = (MyBean)body;
List<String> strings = inputMessage.getHeaders().get("X-Property");
myBean.setProperty(strings.get(0));
return myBean;
}
}
Testing:
$ curl localhost:8080 -d '{}' -X POST -H 'X-Property: myProp' -H 'Content-Type: application/json' -s
Output:
{"property":"myProp"}
I have implemented the same as #caco3 mentioned here is my implementation with set the values to bean.
#ControllerAdvice
public class RequestBodyAdviceChain implements RequestBodyAdvice {
.. Other methods
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
HttpHeaders headers = inputMessage.getHeaders();
List<String> emulated = headers.get("FIRST_HEAD1");
Boolean isEmulated = false;
Long emulatedUserId = null;
if (!CollectionUtils.isEmpty(emulated)) {
isEmulated = Boolean.valueOf(emulated.get(0));
}
if (isEmulated) {
List<String> users = headers.get("SECOND_HEAD2");
if (!CollectionUtils.isEmpty(users)) {
emulatedUserId = Long.valueOf(users.get(0));
}
}
if (isEmulated) {
if (setField(body, Is_Emulated_Field, isEmulated)) {
setField(body, EmulatedUserId_FIELD, emulatedUserId);
}
}
return body;
}
/**
* <p>
* Method to set the field value for the emulated user and it's id wven
* though if the fields are defined in the super class.
*/
private static boolean setField(Object targetObject, String fieldName, Object fieldValue) {
Field field;
try {
field = targetObject.getClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
field = null;
}
Class superClass = targetObject.getClass().getSuperclass();
while (field == null && superClass != null) {
try {
field = superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
superClass = superClass.getSuperclass();
}
}
if (field == null) {
return false;
}
field.setAccessible(true);
try {
field.set(targetObject, fieldValue);
return true;
} catch (IllegalAccessException e) {
return false;
}
}
}
Related
A class containing
package com.data.job.controller;
#Controller
public class SomeController implements SomeUtility {
#RequestMapping(value = { "/v3/jobs/upload" }, method = RequestMethod.POST)
#ResponseBody
public ServerResponse upload(UploadItem uploadItem, BindingResult result, HttpServletResponse servletResponse) throws IOException {
// SomeHandler method used in between
return response;
}
An interface with default method:
package com.data.upload.util;
public interface SomeUtility {
default String SomeHandler(String value) {
try {
if (null != value) {
long keyName = Double.valueOf((value.trim())).longValue();
return String.valueOf(keyName);
}
} catch (NumberFormatException nfe) {
}
return value;
}
}
but while calling controller its giving 404 handler not found. If I removed implementation of SomeUtility its working fine.
I have the following #RestController
#RequestMapping(...)
public ResponseEntity(#RequestBody #Valid SomeDTO, BindingResult errors) {
//do something with errors if validation error occur
}
public class SomeDTO {
public SomeEnum someEnum;
}
If the JSON request is { "someEnum": "valid value" }, everything works fine. However, if the request is { "someEnum": "invalid value" }, it only return error code 400.
How can I trap this error so I can provide a custom error message, such as "someEnum must be of value A/B/C".
The answer provided by #Amit is good and works. You can go ahead with that if you want to deserialize an enum in a specific way. But that solution is not scalable. Because every enum which needs validation must be annotated with #JsonCreator.
Other answers won't help you beautify the error message.
So here's my solution generic to all the enums in spring web environment.
#RestControllerAdvice
public class ControllerErrorHandler extends ResponseEntityExceptionHandler {
public static final String BAD_REQUEST = "BAD_REQUEST";
#Override
public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException exception,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String genericMessage = "Unacceptable JSON " + exception.getMessage();
String errorDetails = genericMessage;
if (exception.getCause() instanceof InvalidFormatException) {
InvalidFormatException ifx = (InvalidFormatException) exception.getCause();
if (ifx.getTargetType()!=null && ifx.getTargetType().isEnum()) {
errorDetails = String.format("Invalid enum value: '%s' for the field: '%s'. The value must be one of: %s.",
ifx.getValue(), ifx.getPath().get(ifx.getPath().size()-1).getFieldName(), Arrays.toString(ifx.getTargetType().getEnumConstants()));
}
}
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setTitle(BAD_REQUEST);
errorResponse.setDetail(errorDetails);
return handleExceptionInternal(exception, errorResponse, headers, HttpStatus.BAD_REQUEST, request);
}
}
This will handle all the invalid enum values of all types and provides a better error message for the end user.
Sample output:
{
"title": "BAD_REQUEST",
"detail": "Invalid enum value: 'INTERNET_BANKING' for the field: 'paymentType'. The value must be one of: [DEBIT, CREDIT]."
}
#ControllerAdvice
public static class GenericExceptionHandlers extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(new ErrorDTO().setError(e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
I created a fully functional Spring boot Application with a Test on Bitbucket
You do not need #Valid for enum validation, you can achieve the required response using below code:
Controller Code, StackDTO has an enum PaymentType in it:
#RequestMapping(value = "/reviews", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> add(#RequestBody StackDTO review) {
return new ResponseEntity<String>(HttpStatus.ACCEPTED);
}
Create an exception class, as EnumValidationException
public class EnumValidationException extends Exception {
private String enumValue = null;
private String enumName = null;
public String getEnumValue() {
return enumValue;
}
public void setEnumValue(String enumValue) {
this.enumValue = enumValue;
}
public String getEnumName() {
return enumName;
}
public void setEnumName(String enumName) {
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName) {
super(enumValue);
this.enumValue = enumValue;
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName, Throwable cause) {
super(enumValue, cause);
this.enumValue = enumValue;
this.enumName = enumName;
}
}
I have enum as below, with a special annotation #JsonCreator on a method create
public enum PaymentType {
CREDIT("Credit"), DEBIT("Debit");
private final String type;
PaymentType(String type) {
this.type = type;
}
String getType() {
return type;
}
#Override
public String toString() {
return type;
}
#JsonCreator
public static PaymentType create (String value) throws EnumValidationException {
if(value == null) {
throw new EnumValidationException(value, "PaymentType");
}
for(PaymentType v : values()) {
if(value.equals(v.getType())) {
return v;
}
}
throw new EnumValidationException(value, "PaymentType");
}
}
Finally RestErrorHandler class,
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
}
ValidationErrorDTO is the dto with setters/getters of enumValue, enumName and errorMessage. Now when you send POST call to controller endpoint /reviews with below request
{"paymentType":"Credit2"}
Then code returns response as 400 with below response body -
{
"enumValue": "Credit2",
"enumName": "PaymentType",
"errorMessage": "Credit2 is an invalid PaymentType"
}
Let me know if it resolves your issue.
Yon can achieve this using #ControllerAdvice as follows
#org.springframework.web.bind.annotation.ExceptionHandler(value = {InvalidFormatException.class})
public ResponseEntity handleIllegalArgumentException(InvalidFormatException exception) {
return ResponseEntity.badRequest().body(exception.getMessage());
}
Basically , the idea is to catch com.fasterxml.jackson.databind.exc.InvalidFormatException and handle it as per your requirement.
#Valid has to do with Hibernate bean validation. Currently enum type is not supported out of the box. I found this answer to be the closet, https://funofprograming.wordpress.com/2016/09/29/java-enum-validator/, the drawback however is that you have to make the enum field of type String instead.
I have a spring REST controller which returns the following JSON payload:
[
{
"id": 5920,
"title": "a title"
},
{
"id": 5926,
"title": "another title",
}
]
The REST controller with its corresponding get request method:
#RequestMapping(value = "example")
public Iterable<Souvenir> souvenirs(#PathVariable("user") String user) {
return new souvenirRepository.findByUserUsernameOrderById(user);
}
Now the Souvenir class is a pojo:
#Entity
#Data
public class Souvenir {
#Id
#GeneratedValue
private long id;
private String title;
private Date date;
}
Regarding https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside and http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ I would like to wrap the response within an object so that it is not vulnerable to attacks. Of course I could do something like this:
#RequestMapping(value = "example")
public SouvenirWrapper souvenirs(#PathVariable("user") String user) {
return new SouvenirWrapper(souvenirRepository.findByUserUsernameOrderById(user));
}
#Data
class SouvenirWrapper {
private final List<Souvenir> souvenirs;
public SouvenirWrapper(List<Souvenir> souvenirs) {
this.souvenirs = souvenirs;
}
}
This results in the following JSON payload:
{
"souvenirs": [
{
"id": 5920,
"title": "a title"
},
{
"id": 5926,
"title": "another title",
}
]
}
This helps in preventing some JSON/Javascript attacks but I don't like the verbosity of the Wrapper class. I could of course generalize the above approach with generics. Is there another way to achieve the same result in the Spring ecosystem (with an annotation or something similar)? An idea would be that the behaviour is done by Spring automatically, so whenever there is a REST controller that returns a list of objects, it could wrap those objects within an object wrapper so that no direct list of objects get serialized?
I ended up with the following solution (thanks to #vadim-kirilchuk):
My controller still looks exactly as before:
#RequestMapping(value = "example")
public Iterable<Souvenir> souvenirs(#PathVariable("user") String user) {
return new souvenirRepository.findByUserUsernameOrderById(user);
}
I added the following implementation of ResponseBodyAdvice which basically gets executed when a controller in the referenced package tries to respond to a client call (to my understanding):
#ControllerAdvice(basePackages = "package.where.all.my.controllers.are")
public class JSONResponseWrapper implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
#SuppressWarnings("unchecked")
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof List) {
return new Wrapper<>((List<Object>) body);
}
return body;
}
#Data // just the lombok annotation which provides getter and setter
private class Wrapper<T> {
private final List<T> list;
public Wrapper(List<T> list) {
this.list = list;
}
}
}
So with this approach I can keep my existing method signature in my controller (public Iterable<Souvenir> souvenirs(#PathVariable("user") String user)) and future controllers don't have to worry about wrapping its Iterables within such a wrapper because the framework does this part of the work.
Based in your solution I ended up with a more flexible option. First I created an annotation to activate the behaviour whenever I want and with a customizable wrapper attribute name:
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface JsonListWrapper {
String name() default "list";
}
This annotation can be used on the entity class so it's applied to all controllers responses of List<MyEntity> or can be used for specifics controller methods.
The ControllerAdvice will look like this (note that I return a Map<Object> to dynamically set the wrapper name as a map key).
public class WebResponseModifierAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(final MethodParameter returnType, final Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(final Object body,
final MethodParameter returnType,
final MediaType selectedContentType,
final Class<? extends HttpMessageConverter<?>> selectedConverterType,
final ServerHttpRequest request,
final ServerHttpResponse response) {
if (body instanceof List && selectedContentType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
return checkListWrapper(body, returnType);
} else {
return body;
}
}
/**
* Detects use of {#link JsonListWrapper} in a response like <tt>List<T></tt>
* in case it's necesary to wrap the answer.
*
* #param body body to be written in the response
* #param returnType controller method return type
* #return
*/
private Object checkListWrapper(final Object body,
final MethodParameter returnType) {
String wrapperName = null;
try {
// Checks class level annotation (List<C>).
String typeName = "";
String where = "";
String whereName = "";
// Gets generic type List<T>
Type[] actualTypeArgs = ((ParameterizedType) returnType.getGenericParameterType()).getActualTypeArguments();
if (actualTypeArgs.length > 0) {
Type listType = ((ParameterizedType) returnType.getGenericParameterType()).getActualTypeArguments()[0];
if (listType instanceof ParameterizedType) {
Type elementType = ((ParameterizedType) listType).getActualTypeArguments()[0];
elementType.getClass();
try {
typeName = elementType.getTypeName();
Class<?> clz = Class.forName(typeName);
JsonListWrapper classListWrapper = AnnotationUtils.findAnnotation(clz, JsonListWrapper.class);
if (classListWrapper != null) {
where = "clase";
whereName = typeName;
wrapperName = classListWrapper.name();
}
} catch (ClassNotFoundException e) {
log.error("Class not found" + elementType.getTypeName(), e);
}
}
}
// Checks method level annotations (prevails over class level)
JsonListWrapper methodListWrapper = AnnotationUtils.findAnnotation(returnType.getMethod(), JsonListWrapper.class);
if (methodListWrapper != null) {
where = "método";
whereName = returnType.getMethod().getDeclaringClass() + "." + returnType.getMethod().getName() + "()";
wrapperName = methodListWrapper.name();
}
if (wrapperName != null) {
if (log.isTraceEnabled()) {
log.trace("#JsonListWrapper detected {} {}. Wrapping List<{}> in \"{}\"", where, whereName, typeName, wrapperName);
}
final Map<String, Object> map = new HashMap<>(1);
map.put(wrapperName, body);
return map;
}
} catch(Exception ex) {
log.error("Error getting type of List in the response", ex);
}
return body;
}
}
This way you can use either:
#JsonListWrapper(name = "souvenirs")
public class Souvenir {
//...class members
}
...or
#JsonListWrapper(name = "souvenirs")
#RequestMapping(value = "example")
public ResponseEntity<List<Souvenir>> souvenirs(#PathVariable("user") String user) {
return new souvenirRepository.findByUserUsernameOrderById(user);
}
I am trying to implement pagination to my Spring Data JPA repository in Spring Boot but I am stuck with the following exception when running uni tests:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.domain.Pageable]: Specified class is an interface
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
...
Could someone point out to me what am I missing here? This is my repository:
#Repository
public interface VenueRepository extends PagingAndSortingRepository<Venue, Long> {
public Page<Venue> findAll(Pageable pageable);
}
and controller:
#RestController
#RequestMapping("/venues")
public class VenueController {
#Autowired
private VenueRepository venueRepo;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Page<Venue>> getVenues(Pageable pageable) {
return new ResponseEntity<>(venueRepo.findAll(pageable), HttpStatus.OK);
}
}
and finally my test:
#Test
public void responseOkVenuesTest() throws Exception {
mvc.perform(get("/venues").accept(MediaType.APPLICATION_JSON_VALUE)).andExpect(status().isOk());
}
I spent couple of hours trying to make this work and am running out of ideas. Thank you for any tips!
Change your method getVenues in the way that you can pass the parameters to instantiate a PageRequest instead of passing Pageable :
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<Venue>> getVenues(int from,int to) {
return new ResponseEntity<>(
venueRepo.findAll((new PageRequest(from, to)), HttpStatus.OK).getContent();
}
In addition to #SEY_91's answer you might also like to use the following solution inspired with How to remove redundant Spring MVC method by providing POST-only #Valid? and used in my Spring Boot-driven application for long time.
In short, here is an annotation to annotate controller method parameters:
#Target(PARAMETER)
#Retention(RUNTIME)
public #interface PlainModelAttribute {
}
Now, just a method processor that would scan for parameters annotated with #PlainModelAttribute:
public final class PlainModelAttributeMethodProcessor
extends ModelAttributeMethodProcessor {
private final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index;
private PlainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
super(true);
this.index = index;
}
public static HandlerMethodArgumentResolver plainModelAttributeMethodProcessor(final Map<TypeToken<?>, Converter<? super NativeWebRequest, ?>> index) {
return new PlainModelAttributeMethodProcessor(index);
}
#Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(PlainModelAttribute.class) || super.supportsParameter(parameter);
}
#Override
protected Object createAttribute(final String attributeName, final MethodParameter parameter, final WebDataBinderFactory binderFactory,
final NativeWebRequest request) {
final TypeToken<?> typeToken = TypeToken.of(parameter.getGenericParameterType());
final Converter<? super NativeWebRequest, ?> converter = index.get(typeToken);
if ( converter == null ) {
throw new IllegalArgumentException("Cannot find a converter for " + typeToken.getType());
}
return converter.convert(request);
}
#Override
protected void bindRequestParameters(final WebDataBinder binder, final NativeWebRequest request) {
final HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if ( !isSafe(resolve(servletRequest.getMethod())) ) {
((ServletRequestDataBinder) binder).bind(servletRequest);
}
}
private static HttpMethod resolve(final String name) {
return HttpMethod.valueOf(name.toUpperCase());
}
private static boolean isSafe(final HttpMethod method)
throws UnsupportedOperationException {
switch ( method ) {
case GET:
case HEAD:
case OPTIONS:
return true;
case POST:
case PUT:
case PATCH:
case DELETE:
return false;
case TRACE:
throw new UnsupportedOperationException();
default:
throw new AssertionError(method);
}
}
}
I don't really remember, but a resolve() method equivalent should be present in Spring Framework somewhere. Note that I use Google Guava TypeToken in order to let the processor be compatible with generic types (since I use models like IQuery<Foo> and IQuery<Bar> in controllers). Now just register the processor:
#Configuration
#EnableWebMvc
public class MvcConfiguration
extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(createModelAttributeMethodProcessor());
}
private static HandlerMethodArgumentResolver createModelAttributeMethodProcessor() {
return plainModelAttributeMethodProcessor(ImmutableMap.of(pageableTypeToken, MvcConfiguration::toPageable));
}
private static final TypeToken<Pageable> pageableTypeToken = new TypeToken<Pageable>() {
};
private static Pageable toPageable(final WebRequest request) {
return new PageRequest(
ofNullable(request.getParameter("page")).map(Integer::parseInt).orElse(0),
ofNullable(request.getParameter("size")).map(Integer::parseInt).orElse(1)
);
}
}
Here is a web request to a Pageable DTO conversion, and the converter must be registered as an argument resolver. So now it's ready to use:
#RestController
#RequestMapping("/")
public class Controller {
#RequestMapping(method = GET)
public String get(#PlainModelAttribute final Pageable pageable) {
return toStringHelper(pageable)
.add("offset", pageable.getOffset())
.add("pageNumber", pageable.getPageNumber())
.add("pageSize", pageable.getPageSize())
.add("sort", pageable.getSort())
.toString();
}
}
A few examples:
/ ⇒ PageRequest{offset=0, pageNumber=0, pageSize=1, sort=null}
/?page=43 ⇒ PageRequest{offset=43, pageNumber=43, pageSize=1, sort=null}
/?size=32 ⇒ PageRequest{offset=0, pageNumber=0, pageSize=32, sort=null}
/?page=22&size=32 ⇒ PageRequest{offset=704, pageNumber=22, pageSize=32, sort=null}
Edit I added more detail to help others and left the original question for history
Background I have prototyped a REST call that returns JSON in a Spring Controller that works with my client software. The client software has a specific way it queries for data. That query is not compatible with the my Spring code, so I had a few lines that did the conversion. I refactored the conversion code into its own object. Instead of creating each time in my REST methods that require it, I would like to have it pre-populated before it gets to my method.
Question In a Spring Controller can I have Spring pre-populate an object from the values in the URL and the header, similar to how Spring populates and object from a form?
Current code
#RequestMapping(value="", headers = "Accept=application/json", method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<String> searchUserProjects(
#RequestParam(required = false) String projectName,
#RequestParam(required = false) String sortBy,
#RequestHeader(value = "Range") String range) {
Original Question I know in Spring you can take the properties of a form and map them to an object. In addition, I know you can map a field to property converter object, I cannot remember the exact name, but I have done it. My question, is it possible to have Spring populate an object from values in the URL and the header and then pass that into the method instead of declaring them at the method signature of the controller?
Edit:
The registration method in the applicationContext.xml
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="app.util.dojo.DojoQueryProcessorHandlerMethodArgumentResolver"/>
</mvc:argument-resolvers>
</mvc:annotation-driven>
And the handler method with parameter
public ResponseEntity<String> searchUserProjects(#RequestParam(required = false) String projectName, #ProcessDojoQuery DojoRestQueryProcessor dojoQueryResults) {
DojoRestQueryProcessor.java
package app.util.dojo;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
public class DojoRestQueryProcessor {
protected String[] rangeArray;
protected String range;
protected String sortBy;
protected int startIndex;
protected int endIndex;
public DojoRestQueryProcessor() {
}
public DojoRestQueryProcessor(String range, String sortBy) {
if (range== null && sortBy == null)
return;
if (range.length() <= 3 || !range.contains("-"))
throw new DojoRestQueryProcessorException("Range value does not meet spec. " + range);
this.rangeArray = range.substring(6).split("-");
this.range = range;
this.sortBy = sortBy;
}
public PageRequest createPageRequest() {
startIndex = Integer.parseInt(rangeArray[0]);
endIndex = Integer.parseInt(rangeArray[1]);
if (startIndex >= endIndex)
throw new IllegalArgumentException("The starting index for a range needs to be less than the end index.");
Sort.Order[] sortOrders = null;
if (sortBy != null && sortBy.length() > 2)
sortOrders = convertDojoSortValuesToSpringSorts(sortBy.split(","));
int pageSize = endIndex-startIndex+1;
int pageNum = ((endIndex+1)/pageSize)-1;
PageRequest pageRequest = null;
if (sortOrders != null)
pageRequest = new PageRequest(pageNum, pageSize, new Sort(sortOrders));
else
pageRequest = new PageRequest(pageNum, pageSize);
return pageRequest;
}
public static Sort.Order[] convertDojoSortValuesToSpringSorts(String[] sortStrings) {
if (sortStrings == null)
return null;
Sort.Order[] sortOrders = new Sort.Order[sortStrings.length];
for (int i = 0; i < sortStrings.length; i++) {
String sortString = sortStrings[i];
if (sortString.startsWith("-")) {
sortOrders[i] = new Sort.Order(Direction.DESC, sortString.substring(1));
} else {
sortOrders[i] = new Sort.Order(Direction.ASC, sortString.substring(1));
}
}
return sortOrders;
}
public int getStartIndex() {
return startIndex;
}
public int getEndIndex() {
return endIndex;
}
public String getRange() {
return range;
}
public String getSortBy() {
return sortBy;
}
}
My Method Handler:
package app.util.dojo;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
public class DojoQueryProcessorHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(ProcessDojoQuery.class) && parameter.getParameterType().equals(DojoRestQueryProcessor.class) ;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
String rangeField = parameter.getParameterAnnotation(ProcessDojoQuery.class).rangeField();
String sortByField = parameter.getParameterAnnotation(ProcessDojoQuery.class).sortByField();
String range = getRangeValue(rangeField, webRequest);
String sortBy = getSortByValue(sortByField, webRequest);
return new DojoRestQueryProcessor(range, sortBy);
}
private String getSortByValue(String rangeField, NativeWebRequest webRequest) {
Map<String, String> pathVariables = getPathVariables(webRequest);
return pathVariables.get(rangeField);
}
private Map<String, String> getPathVariables(NativeWebRequest webRequest) {
HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return (Map<String, String>) httpServletRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
}
private String getHeaderValue(String headerName, NativeWebRequest webRequest) {
HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return httpServletRequest.getHeader(headerName);
}
private String getRangeValue(String rangeField, NativeWebRequest webRequest) {
return getHeaderValue(rangeField, webRequest);
}
}
It is possible, but you would have to do it yourself (once).
The interface for this is HandlerMethodArgumentResolver. The way I see it is you would create an annotation, like #FromUrlAndHeaders and use that to annotate the parameter in the method:
#RequestMapping(value = "/someRequest/path")
public String doBusiness(#FromUrlAndHeaders CustomObject customObject) {
// do business with customObject
}
Then the fun part is creating your own HandlerMethodArgumentResolver.
public class FromUrlAndHeadersHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(FromUrlAndHeaders.class);
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// use the various objects here
// request to get parameters and headers
// mavContainer for model attributes (if you need)
// parameter for class type and annotation attributes
// etc.
// note that the parameter class type matters, are your creating a CustomObject, a String, a DifferentClassObject, etc...
}
}
You can then register this HandlerMethodArgumentResolver and let it do work.
The DispatcherServlet stack uses a list of HandlerMethodArgumentResolver implementation instances to decide what argument to pass to your method. There's one for #ModelAttribute, for #PathVariable, for #RequestParam, for #RequestBody, for ModelMap, for HttpServletRequest, for HttpServletResponse, basically for each parameter type supported by default. You can see all of them in the javadoc.
Related:
Spring MVC controller with multiple #RequestBody
Controller handler method supported return types
maybe i didn't get your question and this is not what your looking for, but if you want all parameters to be injected in action method,just declare it as :
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity doSomethingCool(#RequestParam Map<String, String> parameters) {
...
}