Skip to content

Instantly share code, notes, and snippets.

@horte
Created May 13, 2012 11:22
Show Gist options
  • Save horte/2687913 to your computer and use it in GitHub Desktop.
Save horte/2687913 to your computer and use it in GitHub Desktop.
Partial Update in Spring 3.1 Controller using @RequestBody with Validation
public class ContentPatternConverter implements Converter<String, ContentPattern> {
private PatternService patternService;
@Autowired
public ContentPatternConverter(PatternService patternService) {
this.patternService = patternService;
}
@Override
public ContentPattern convert(String source) {
return patternService.findOneByPatternId(Long.valueOf(source));
}
}
public static void merge(Object obj, Object update) {
if (!obj.getClass().isAssignableFrom(update.getClass())) {
return;
}
Method[] methods = obj.getClass().getMethods();
for (Method fromMethod : methods) {
if (fromMethod.getDeclaringClass().equals(obj.getClass()) && fromMethod.getName().startsWith("get")) {
String fromName = fromMethod.getName();
String toName = fromName.replace("get", "set");
try {
Method toMetod = obj.getClass().getMethod(toName, fromMethod.getReturnType());
Object value = fromMethod.invoke(update, (Object[]) null);
if (value != null) {
toMetod.invoke(obj, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* Annotation to indicate a method parameter should be bound to a URL template, and retrieved as a domain entity.
* Used for annotated methods.
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBodyPathVariable {
/**
* The URI template variable to bind to.
* @return
*/
String value();
/**
* If validation should be made on the final entity.
* @return
*/
String validation();
}
/**
* Annotation that binds a method parameter to a URI template variable and loads the
* entity based on the path attribute value using a custom {@link Converter} and
* merges the loaded entity with the provided request body. Finally does
* validation on the merged entity. Not compatible with {@link Valid} annotation,
* for validation of the final merged entity set
* {@link RequestBodyPathVariable#validation()} to true.
*
*/
public class RequestBodyPathVariableMethodArgumentResolver implements HandlerMethodArgumentResolver {
public RequestBodyPathVariableMethodArgumentResolver() {
}
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null;
private RequestResponseBodyMethodProcessor getRequestResponseBodyMethodProcessor() {
if (requestResponseBodyMethodProcessor == null) {
List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters();
requestResponseBodyMethodProcessor = new RequestResponseBodyMethodProcessor(messageConverters);
}
return requestResponseBodyMethodProcessor;
}
/**
* A conversion service to convert primary key values.
*/
@Autowired
private ConversionService conversionService;
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBodyPathVariable.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
// Get attribute name from URL
String attribute = parameter.getParameterAnnotation(RequestBodyPathVariable.class).value();
// Get value for the URL attribute
String attributeValue = getRequestValueForAttribute(attribute, webRequest);
// Get existing resource
Object target = conversionService.convert(attributeValue, parameter.getParameterType());
// Get the request body
Object arg = getRequestResponseBodyMethodProcessor().resolveArgument(parameter, mavContainer, webRequest, binderFactory);
// Merge the request body into the existing resource
Util.merge(target, arg);
// Run validation if validation=true (not compatible with @Valid
// annotation)
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getSimpleName().equalsIgnoreCase("RequestBodyPathVariable") && getValidationValue(parameter).equalsIgnoreCase("true")) {
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, target, name);
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] { hints });
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
}
return target;
}
protected String getValidationValue(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestBodyPathVariable.class).validation();
}
public boolean supportsReturnType(MethodParameter returnType) {
return false;
}
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
throws Exception {
}
/**
* Copied from {@see org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor#getRequestValueForAttribute)
* @param attributeName
* @param request
* @return
*/
private String getRequestValueForAttribute(String attributeName, NativeWebRequest request) {
Map<String, String> variables = getUriTemplateVariables(request);
if (StringUtils.hasText(variables.get(attributeName))) {
return variables.get(attributeName);
} else if (StringUtils.hasText(request.getParameter(attributeName))) {
return request.getParameter(attributeName);
} else {
return null;
}
}
/**
* Copied from {@see org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor#getRequestValueForAttribute)
* @param attributeName
* @param request
* @return
*/
@SuppressWarnings("unchecked")
private final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
Map<String, String> variables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE,
RequestAttributes.SCOPE_REQUEST);
return (variables != null) ? variables : Collections.<String, String> emptyMap();
}
}
//Register Custom Converter
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="my.package.spring.ContentPatternConverter" />
</list>
</property>
</bean>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment