Jersey Custom Exception not caught by ExceptionMapper - exception

I am building a REST API and created some custom parameter types that apply some validation on QueryParameters and not only. Problem is I am throwing a "IllegalQueryArgumentException" but it isn't caught by a custom ExceptionMapper.
If i throw the exception from inside a controller method the exception is caught.
SOLUTION
I made the custom exception extend WebApplicationException instead of RuntimeException
#Provider
public class CustomHandler implements ExceptionMapper<IllegalQueryArgumentException> {
Override
public Response toResponse(IllegalQueryArgumentException e) {
return Response.accepted("HELLO").build();
}
}
config
<servlet>
<servlet-name>rest</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.epaas.eshop.api.v1;com.epaas.eshop.api.v1.jersey;org.codehaus.jackson.jaxrs</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>com.epaas.eshop.api.v1.jersey.CustomHandler</param-value>
</init-param>
Abstract param
public abstract class AbstractQueryParameter<V> {
private final V value;
private final String originalParameter;
public AbstractQueryParameter(String param) throws InvalidQueryArgumentException {
this.originalParameter = param;
this.value = parse(param);
}
public V getValue() {
return value;
}
public String getOriginalParameter() {
return originalParameter;
}
#Override
public String toString() {
return value.toString();
}
protected abstract V parse(String param) throws InvalidQueryArgumentException;
}
Custom parameter class
public class IdParam extends AbstractQueryParameter<Long> {
public IdParam(String param) {
super(param);
}
#Override
protected Long parse(String param) {
try {
Long id = Long.parseLong(param);
if(id < 1) throw new Throwable("Id is under 1");
return Long.parseLong(param);
} catch (Throwable throwable) {
throw new InvalidQueryArgumentException("parameter should be an integer", throwable);
}
}
}
The parse method is automaticly called when Jersey tries to bind the String parameter to the IdParam type.
Route method..
#SuppressWarnings("unchecked")
#GET
public CollectionResource getAll(
#Context UriInfo info,
#QueryParam("expand") #DefaultValue("false") BooleanParam expand,
#QueryParam("city") IdParam city,
#QueryParam("account") IdParam account,
#QueryParam("zipCode") ZipCodeParam zipCode,
#QueryParam("limit") #DefaultValue(Pagination.DEFAULT_LIMIT) LimitParam limit,
#QueryParam("offset") #DefaultValue(Pagination.DEFAULT_OFFSET) OffsetParam offset) {

Related

A callback method to execute post JSON to object conversion in Jersey JAX RS

I am using Jersey rest services with Jackson API for conversion of JSON String to POJOs. All the member variables of the POJO class need to be validated. I already have a framework in place for validation.
What I want to know is if there is any callback method or mechanism which can call my validation API post the JSON to POJO conversion itself. Doing this would make my job easier as I will not have to call the API at all the places in my Rest service class.
public class MyPojoClass{
private int interestRateCode;
private String name;
//just edited
private List<TestDTO> testObjs;
//Psuedo code
//#PostDeserialization
public String callbackMethod(Object obj){
if(!ValidationAPI.validate(obj))
return "false";
}
}
The TestDTO:
public class TestDTO {
private int var1;
private String stringVar;
public TestDTO() {
System.out.println("This constructor does get called every time");
}
}
Is there any annotation like PostDeserialization to achieve this. This will help me to make every POJO class having only one callback method for validation.
The JSON I am passing is
{"interestRateCode": 101,"name": "T",
"testObjs": [
{"var1" :10, "stringVar": "Arunabh"},
{"var1" :15, "stringVar": "Hejib"}
]}
Anyone who can help me on this problem? Thanks for any help.
One thing you can do is use a Request Filter. In the filter you would:
Get the resource Method using the injected ResourceInfo
Get the entity class by traversing the Method Parameters and checking for the method parameter without any annotations. Unless you're using bean validation where #Valid is used next to the parameter, then the entity parameter always is the parameter with no annotations. This is how we determine the entity parameter. From the parameter, we get the class.
Get the entity objects from the request.
From the entity class, using from reflection, find the Method with the #PostDeserialization annotation.
Call the method using reflection.
Below is a complete test. The ValidationFilter is the class with previous mentioned steps.
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import static org.junit.Assert.assertEquals;
/**
* Run like any other JUnit test. A couple required dependencies.
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
* <version>${jersey2.version}</version>
* <scope>test</scope>
* </dependency>
* <dependency>
* <groupId>org.glassfish.jersey.media</groupId>
* <artifactId>jersey-media-json-jackson</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*/
public class PostDeserializationTest extends JerseyTest {
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface PostDeserialization {}
public static class ValidationError extends RuntimeException {
public ValidationError(String message) {
super(message);
}
}
public static class ValidationErrorMapper
implements ExceptionMapper<ValidationError> {
#Override
public Response toResponse(ValidationError ex) {
return Response.status(Response.Status.BAD_REQUEST)
.entity(ex.getMessage())
.build();
}
}
public static class Bean {
public String value;
#PostDeserialization
public void validate() {
if (!"expected".equals(value)) {
throw new ValidationError("value must be 'expected'");
}
}
}
public static class ValidationFilter implements ContainerRequestFilter {
#Context
private ResourceInfo info;
#Override
public void filter(ContainerRequestContext request) throws IOException {
Class<?> entityClass = getEntityClass();
if (entityClass != null) {
final ContainerRequest cr = (ContainerRequest) request;
cr.bufferEntity();
final Object entity = cr.readEntity(entityClass);
findMethodAndValidate(entity);
}
}
private Class<?> getEntityClass() {
final Method rm = info.getResourceMethod();
final Annotation[][] paramAnnotations = rm.getParameterAnnotations();
for (int i = 0; i < paramAnnotations.length; i++) {
// entity parameters have no annotations.
if (paramAnnotations[i].length == 0) {
return rm.getParameterTypes()[i];
}
}
return null;
}
private void findMethodAndValidate(Object entity) {
final Method[] methods = entity.getClass().getMethods();
for (Method method: methods) {
if (method.isAnnotationPresent(PostDeserialization.class)) {
// validation method should take no parameters.
if (method.getParameterCount() != 0) {
throw new RuntimeException(
"Validation method must not have parameters.");
}
try {
method.invoke(entity);
} catch (InvocationTargetException ex) {
// if an exception happens during invocation,
// an InvocationException is thrown. We want the cause,
// expecting it to be a ValidationError.
Throwable cause = ex.getCause();
if (cause instanceof ValidationError) {
throw (ValidationError) cause;
} else {
throw new RuntimeException(ex);
}
} catch (Exception ex) {
throw new RuntimeException(
"Error calling validation method.", ex);
}
}
}
}
}
#Path("test")
public static class TestResource {
#POST
#Consumes("application/json")
public String post(Bean bean) {
return bean.value;
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(ValidationErrorMapper.class)
.register(ValidationFilter.class)
.register(new ExceptionMapper<Throwable>() {
#Override
public Response toResponse(Throwable t) {
t.printStackTrace();
return Response.serverError()
.entity(t.getMessage()).build();
}
});
}
#Test
public void testValidationError() {
final Bean bean = new Bean();
bean.value = "not expected";
final Response response = target("test")
.request()
.post(Entity.json(bean));
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus());
assertEquals("value must be 'expected'", response.readEntity(String.class));
}
#Test
public void testNoValidationError() {
final Bean bean = new Bean();
bean.value = "expected";
final Response response = target("test")
.request()
.post(Entity.json(bean));
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
assertEquals("expected", response.readEntity(String.class));
}
}

How to produce error response in json

Am writing a Restful Webservice Impl, where i consume and produce response in JSON format by annotating #Produces("application/json"). Am producing JSON response as well. Here am handling exception with a class where it has error code and error message. When am getting exception it is not produced in application/json format. I used ExceptionMapper to find a solution but it is `text/plain format.
snippet
public Class Confiuration{
#Path("getData")
#Consumes("application/json")
#Produces("application/json")
public JSONGetDataResponseVo getData(GetDataRequestVo datarequestVO)
throws FaultResponse {
JSONGetDataResponseVo response=new JSONGetDataResponseVo ();
DataServiceValidator.validateGetConfigurationAndDataRequest(datarequestVO);
....
....
}catch(ApplicationException applicationException){
throw new FaultResponse(applicationException,locale);
}
}
FaultResponseMapper
#Provider
public class FaultResponseMapper implements ExceptionMapper<FaultResponse> {
#Context
private HttpHeaders headers;
public Response toResponse(FaultResponse faultResponse) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(faultResponse).type(MediaType.APPLICATION_JSON).build();
}
}
Application Exception
public abstract class ApplicationException extends Exception{
private java.lang.String errorCode;
public ApplicationException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public ApplicationException(String message) {
super(message);
}
public java.lang.String getErrorCode() {
return this.errorCode;
}
public abstract String getLocaleMessage(Locale locale);
}
FaultResponse
public class FaultResponse extends WebApplicationException {
private String errorCode;
private String errorMessage;
private String localErrorMessage;
public FaultResponse(String errorCode, String errorMessage,
String localErrorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.localErrorMessage = localErrorMessage;
}
public FaultResponse(ApplicationException applicationException,
Locale locale) {
this.errorCode = applicationException.getErrorCode();
this.errorMessage = applicationException.getMessage();
if (locale != null
&& applicationException.getLocaleMessage(locale) != null) {
this.localErrorMessage = applicationException
.getLocaleMessage(locale);
} else {
this.localErrorMessage = applicationException.getMessage();
}
}
}
So here how can i produce my faultResponse in JSON format.
This has to do with the fact that you are returning an exception as a response. I would
Make an exception mapper for ApplicationException.
Refactor FaultResponse to not extend and exception. Just create it in the mapper.
In order to see the response, you will need to send a status other than No Content. You can't have a body in it. Send somethng like Bad Request.
You can just declare the resource method as throws ApplicationException. You don't need to catch it and rethrow.
I've made these changes, and it works fine.
UPDATE: with complete test
Added getters (required for marshalling) to FaultResponse and remove the exception extension
public class FaultResponse {
...
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public String getLocalErrorMessage() { return localErrorMessage; }
...
}
Created a Service for testing and ApplicationException implementation
public class ApplicationExceptionImpl extends ApplicationException {
public ApplicationExceptionImpl(){
this("400", "Bad Request");
}
public ApplicationExceptionImpl(String errorCode, String message) {
super(errorCode, message);
}
#Override
public String getLocaleMessage(Locale locale) {
return "Bad Request";
}
}
public class FaultService {
public void doSomething() throws ApplicationException {
throw new ApplicationExceptionImpl();
}
}
Resource class
#Path("fault")
public class FaultResource {
FaultService service = new FaultService();
#GET
public Response getException() throws ApplicationException {
service.doSomething();
return Response.ok("Cool").build();
}
}
ExceptionMapper
#Provider
public class ApplicationExceptionMapper implements ExceptionMapper<ApplicationException> {
#Override
public Response toResponse(ApplicationException exception) {
FaultResponse response = new FaultResponse(exception, Locale.ENGLISH);
return Response.status(Response.Status.BAD_REQUEST)
.entity(response).type(MediaType.APPLICATION_JSON).build();
}
}
ApplicationException class is left the same
curl -v http://localhost:8080/api/fault
{"errorCode":"400","errorMessage":"Bad Request","localErrorMessage":"Bad Request"}
If after this you are still not seeing JSON, it's possible you do not have a provider configured. If this is the case, please show your application configuration, along with your project dependencies.

Serialize only mentioned fields in Spring MVC to JSON response

I am writing a rest service using spring MVC which produces JSON response. It should allow client to select only the given fields in response, means client can mention the fields he is interested in as url parameter like ?fields=field1,field2.
Using Jackson annotations does not provide what I am looking for as it is not dynamic also the filters in Jackson doesnt seem to be promising enough.
So far I am thinking to implement a custom message converter which can take care of this.
Is there any other better way to achieve this? I would like if this logic is not coupled with my services or controllers.
From Spring 4.2, #JsonFilter is supported in MappingJacksonValue
Issue : SPR-12586 : Support Jackson #JsonFilter
Commit : ca06582
You can directly inject PropertyFilter to MappingJacksonValue in a controller.
#RestController
public class BookController {
private static final String INCLUSION_FILTER = "inclusion";
#RequestMapping("/novels")
public MappingJacksonValue novel(String[] include) {
#JsonFilter(INCLUSION_FILTER)
class Novel extends Book {}
Novel novel = new Novel();
novel.setId(3);
novel.setTitle("Last summer");
novel.setAuthor("M.K");
MappingJacksonValue res = new MappingJacksonValue(novel);
PropertyFilter filter = SimpleBeanPropertyFilter.filterOutAllExcept(include);
FilterProvider provider = new SimpleFilterProvider().addFilter(INCLUSION_FILTER, filter);
res.setFilters(provider);
return res;
}
or you can declare global policy by ResponseBodyAdvice. The following example implements filtering policy by "exclude" parameter.
#ControllerAdvice
public class DynamicJsonResponseAdvice extends AbstractMappingJacksonResponseBodyAdvice {
public static final String EXCLUDE_FILTER_ID = "dynamicExclude";
private static final String WEB_PARAM_NAME = "exclude";
private static final String DELI = ",";
private static final String[] EMPTY = new String[]{};
#Override
protected void beforeBodyWriteInternal(MappingJacksonValue container, MediaType contentType,
MethodParameter returnType, ServerHttpRequest req, ServerHttpResponse res) {
if (container.getFilters() != null ) {
// It will be better to merge FilterProvider
// If 'SimpleFilterProvider.addAll(FilterProvider)' is provided in Jackson, it will be easier.
// But it isn't supported yet.
return;
}
HttpServletRequest baseReq = ((ServletServerHttpRequest) req).getServletRequest();
String exclusion = baseReq.getParameter(WEB_PARAM_NAME);
String[] attrs = StringUtils.split(exclusion, DELI);
container.setFilters(configFilters(attrs));
}
private FilterProvider configFilters(String[] attrs) {
String[] ignored = (attrs == null) ? EMPTY : attrs;
PropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(ignored);
return new SimpleFilterProvider().addFilter(EXCLUDE_FILTER_ID, filter);
}
}
IMHO, the simplest way to do that would be to use introspection to dynamically generate a hash containing selected fields and then serialize that hash using Json. You simply have to decide what is the list of usable fields (see below).
Here are two example functions able to do that, first gets all public fields and public getters, the second gets all declared fields (including private ones) in current class and all its parent classes :
public Map<String, Object> getPublicMap(Object obj, List<String> names)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
List<String> gettedFields = new ArrayList<String>();
Map<String, Object> values = new HashMap<String, Object>();
for (Method getter: obj.getClass().getMethods()) {
if (getter.getName().startsWith("get") && (getter.getName().length > 3)) {
String name0 = getter.getName().substring(3);
String name = name0.substring(0, 1).toLowerCase().concat(name0.substring(1));
gettedFields.add(name);
if ((names == null) || names.isEmpty() || names.contains(name)) {
values.put(name, getter.invoke(obj));
}
}
}
for (Field field: obj.getClass().getFields()) {
String name = field.getName();
if ((! gettedFields.contains(name)) && ((names == null) || names.isEmpty() || names.contains(name))) {
values.put(name, field.get(obj));
}
}
return values;
}
public Map<String, Object> getFieldMap(Object obj, List<String> names)
throws IllegalArgumentException, IllegalAccessException {
Map<String, Object> values = new HashMap<String, Object>();
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
for (Field field : clazz.getDeclaredFields()) {
String name = field.getName();
if ((names == null) || names.isEmpty() || names.contains(name)) {
field.setAccessible(true);
values.put(name, field.get(obj));
}
}
}
return values;
}
Then you only have to get the result of one of this function (or of one you could adapt to your requirements) and serialize it with Jackson.
If you have custom encoding of you domain objects, you would have to maintain the serialization rules in two different places : hash generation and Jackson serialization. In that case, you could simply generate the full class serialization with Jackson and filter the generated string afterwards. Here is an example of such a filter function :
public String jsonSub(String json, List<String> names) throws IOException {
if ((names == null) || names.isEmpty()) {
return json;
}
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.readValue(json, HashMap.class);
for (String name: map.keySet()) {
if (! names.contains(name)) {
map.remove(name);
}
}
return mapper.writeValueAsString(map);
}
Edit : integration in Spring MVC
As you are speaking of a web service and of Jackson, I assume that you use Spring RestController or ResponseBody annotations and (under the hood) a MappingJackson2HttpMessageConverter. If you use Jackson 1 instead, it should be a MappingJacksonHttpMessageConverter.
What I propose is simply to add a new HttpMessageConverter that could make use of one of the above filtering functions, and delegate actual work (and also ancilliary methods) to a true MappingJackson2HttpMessageConverter. In the write method of that new converter, it is possible to have access to the eventual fields request parameter with no need for an explicit ThreadLocal variable thanks to Spring RequestContextHolder. That way :
you keep a clear separation of roles with no modification on existing controllers
you have no modification in Jackson2 configuration
you need no new ThreadLocal variable and simply use a Spring class in a class already tied to Spring since it implements HttpMessageConverter
Here is an example of such a message converter :
public class JsonConverter implements HttpMessageConverter<Object> {
private static final Logger logger = LoggerFactory.getLogger(JsonConverter.class);
// a real message converter that will respond to ancilliary methods and do the actual work
private HttpMessageConverter<Object> delegate =
new MappingJackson2HttpMessageConverter();
// allow configuration of the fields name
private String fieldsParam = "fields";
public void setFieldsParam(String fieldsParam) {
this.fieldsParam = fieldsParam;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegate.canRead(clazz, mediaType);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegate.canWrite(clazz, mediaType);
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return delegate.getSupportedMediaTypes();
}
#Override
public Object read(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return delegate.read(clazz, inputMessage);
}
#Override
public void write(Object t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// is there a fields parameter in request
String[] fields = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest().getParameterValues(fieldsParam);
if (fields != null && fields.length != 0) {
// get required field names
List<String> names = new ArrayList<String>();
for (String field : fields) {
String[] f_names = field.split("\\s*,\\s*");
names.addAll(Arrays.asList(f_names));
}
// special management for Map ...
if (t instanceof Map) {
Map<?, ?> tmap = (Map<?, ?>) t;
Map<String, Object> map = new LinkedHashMap<String, Object>();
for (Entry entry : tmap.entrySet()) {
String name = entry.getKey().toString();
if (names.contains(name)) {
map.put(name, entry.getValue());
}
}
t = map;
} else {
try {
Map<String, Object> map = getMap(t, names);
t = map;
} catch (Exception ex) {
throw new HttpMessageNotWritableException("Error in field extraction", ex);
}
}
}
delegate.write(t, contentType, outputMessage);
}
/**
* Create a Map by keeping only some fields of an object
* #param obj the Object
* #param names names of the fields to keep in result Map
* #return a map containing only requires fields and their value
* #throws IllegalArgumentException
* #throws IllegalAccessException
*/
public static Map<String, Object> getMap(Object obj, List<String> names)
throws IllegalArgumentException, IllegalAccessException {
Map<String, Object> values = new HashMap<String, Object>();
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
for (Field field : clazz.getDeclaredFields()) {
String name = field.getName();
if (names.contains(name)) {
field.setAccessible(true);
values.put(name, field.get(obj));
}
}
}
return values;
}
}
If you want the converter to be more versatile, you could define an interface
public interface FieldsFilter {
Map<String, Object> getMap(Object obj, List<String> names)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}
and inject it with an implementation of that.
Now you must ask Spring MVC to use that custom message controller.
If you use XML config, you simply declare it in the <mvc:annotation-driven> element :
<mvc:annotation-driven >
<mvc:message-converters>
<bean id="jsonConverter" class="org.example.JsonConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
And if you use Java configuration, it is almost as simple :
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Autowired JsonConverter jsonConv;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jsonConv);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
converters.add(new ByteArrayHttpMessageConverter());
converters.add(stringConverter);
converters.add(new ResourceHttpMessageConverter());
converters.add(new SourceHttpMessageConverter<Source>());
converters.add(new AllEncompassingFormHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
}
}
but here you have to explicitely add all the default message converters that you need.
I've never done this but after looking at this page http://wiki.fasterxml.com/JacksonFeatureJsonFilter it seems that it would be possible to do what you want this way:
1) Create a custom JacksonAnnotationIntrospector implementation (by extending default one) that will use a ThreadLocal variable to choose a filter for current request and also create a custom FilterProvider that would provide that filter.
2) Configure the message converter's ObjectMapper to use the custom introspector and filter provider
3) Create an MVC interceptor for REST service that detects fields request parameter and configures a new filter for current request via your custom filter provider (this should be a thread local filter). ObjectMapper should pick it up through your custom JacksonAnnotationIntrospector.
I'm not 100% certain that this solution would be thread safe (it depends on how ObjectMapper uses annotation introspector and filter provider internally).
- EDIT -
Ok I did a test implementation and found out that step 1) wouldn't work because Jackson caches the result of AnnotationInterceptor per class. I modified idea to apply dynamic filtering only on annotated controller methods and only if the object doesn't have anoter JsonFilter already defined.
Here's the solution (it's quite lengthy):
DynamicRequestJsonFilterSupport class manages the per-request fields to be filtered out:
public class DynamicRequestJsonFilterSupport {
public static final String DYNAMIC_FILTER_ID = "___DYNAMIC_FILTER";
private ThreadLocal<Set<String>> filterFields;
private DynamicIntrospector dynamicIntrospector;
private DynamicFilterProvider dynamicFilterProvider;
public DynamicRequestJsonFilterSupport() {
filterFields = new ThreadLocal<Set<String>>();
dynamicFilterProvider = new DynamicFilterProvider(filterFields);
dynamicIntrospector = new DynamicIntrospector();
}
public FilterProvider getFilterProvider() {
return dynamicFilterProvider;
}
public AnnotationIntrospector getAnnotationIntrospector() {
return dynamicIntrospector;
}
public void setFilterFields(Set<String> fieldsToFilter) {
filterFields.set(Collections.unmodifiableSet(new HashSet<String>(fieldsToFilter)));
}
public void setFilterFields(String... fieldsToFilter) {
filterFields.set(Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(fieldsToFilter))));
}
public void clear() {
filterFields.remove();
}
public static class DynamicIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findFilterId(Annotated annotated) {
Object result = super.findFilterId(annotated);
if (result != null) {
return result;
} else {
return DYNAMIC_FILTER_ID;
}
}
}
public static class DynamicFilterProvider extends FilterProvider {
private ThreadLocal<Set<String>> filterFields;
public DynamicFilterProvider(ThreadLocal<Set<String>> filterFields) {
this.filterFields = filterFields;
}
#Override
public BeanPropertyFilter findFilter(Object filterId) {
return null;
}
#Override
public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {
if (filterId.equals(DYNAMIC_FILTER_ID) && filterFields.get() != null) {
return SimpleBeanPropertyFilter.filterOutAllExcept(filterFields.get());
}
return super.findPropertyFilter(filterId, valueToFilter);
}
}
}
JsonFilterInterceptor intercepts controller methods annotated with custom #ResponseFilter annotation.
public class JsonFilterInterceptor implements HandlerInterceptor {
#Autowired
private DynamicRequestJsonFilterSupport filterSupport;
private ThreadLocal<Boolean> requiresReset = new ThreadLocal<Boolean>();
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
ResponseFilter filter = method.getMethodAnnotation(ResponseFilter.class);
String[] value = filter.value();
String param = filter.param();
if (value != null && value.length > 0) {
filterSupport.setFilterFields(value);
requiresReset.set(true);
} else if (param != null && param.length() > 0) {
String filterParamValue = request.getParameter(param);
if (filterParamValue != null) {
filterSupport.setFilterFields(filterParamValue.split(","));
}
}
}
requiresReset.remove();
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
Boolean reset = requiresReset.get();
if (reset != null && reset) {
filterSupport.clear();
}
}
}
Here's the custom #ResponseFilter annotation. You can either define a static filter (via annotation's value property) or a filter based on request param (via annotation's param property):
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ResponseFilter {
String[] value() default {};
String param() default "";
}
You will need to setup the message converter and the interceptor in the config class:
...
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(converter());
}
#Bean
JsonFilterInterceptor jsonFilterInterceptor() {
return new JsonFilterInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jsonFilterInterceptor);
}
#Bean
DynamicRequestJsonFilterSupport filterSupport() {
return new DynamicRequestJsonFilterSupport();
}
#Bean
MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(filterSupport.getAnnotationIntrospector());
mapper.setFilters(filterSupport.getFilterProvider());
converter.setObjectMapper(mapper);
return converter;
}
...
And finally, you can use the filter like this:
#RequestMapping("/{id}")
#ResponseFilter(param = "fields")
public Invoice getInvoice(#PathVariable("id") Long id) { ... }
When request is made to /invoices/1?fields=id,number response will be
filtered and only id and number properties will be returned.
Please note I haven't tested this thoroughly but it should get you started.
Would populating a HashMap from the object not suite the requirements? You could then just parse the HashMap. I have done something similar with GSON in the past where I had to provide a simple entity and ended up just populating a HashMap and then serializing it, it was far more maintainable than over engineering a whole new system.

Spring Jersey REST: arrays with ONE element issue

I am using spring and jersey. Problem with this combo is when trying to create a json where an objects has List and there is only one element. If there are many, json is fine. Jersey "forgot" to add brackets [] on single elems.
As you can see, even tried to force it to use jackson (as I read in many tutorials), but ... same result:
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>org.codehaus.jackson.jaxrs;au.com.bikesquare.core.rest</param-value>
</init-param>
</servlet>
Then found this, and still nothing.
#Provider
#Component
#Produces ("application/json")
public class JsonJaxbResolver implements ContextResolver<JAXBContext> {
#Override
public JAXBContext getContext(Class<?> type) {
JSONConfiguration.MappedBuilder b = JSONConfiguration.mapped();
b.arrays("to");
try {
return new JSONJAXBContext(b.build(), EmailMessageTransferObject.class);
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
Server where I am trying to send my object (that contains list) captures this:
....
"to": {
"email": "aaa#bbb.ccc",
"name": "aaa"
}
....
Supposed to be:
"to": [
{
"email": "aaa#bbb.ccc",
"name": "aaa"
}
],
My class (parts of it):
#XmlRootElement
public class EmailMessageTransferObject {
...
#XmlElement
private List<EmailRecipientTransferObject> to;
...
}
Any ideas? Tnx
Found the answer later:
#Provider
#Produces ("application/json")
public class JAXBContextResolver implements ContextResolver<JAXBContext> {
private JAXBContext context;
private Class<?>[] types = {SendEmailMessageTransferObject.class};
public JAXBContextResolver() throws Exception {
this.context = new JSONJAXBContext(JSONConfiguration.mapped().arrays("to").build(), types);
}
#Override
public JAXBContext getContext(Class objectType) {
for (Class type : types) {
if (type == objectType) {
return context;
}
}
return null;
}
}
Client:
public MandrillRestMailImpl() {
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getClasses().add(JAXBContextResolver.class);
client = Client.create(clientConfig);
client.addFilter(new LoggingFilter(System.out)); //todo: remove this for prod
}

How can I process JSON and modify the request inside Spring Interceptor?

I have a REST API using Spring. I've created an Interceptor:
#Component
public class CSRFInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// code here
return true;
}
}
Every request made is using JSON with the following corresponding Java class:
public class CSRFTokenContainer<T> {
private T data;
private String csrf;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getCsrf() {
return csrf;
}
public void setCsrf(String csrf) {
this.csrf = csrf;
}
}
In my Controller it all works well using for example:
#Controller
#RequestMapping("/persons")
public class PersonController {
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody String create(#RequestBody CSRFTokenContainer<Person> account, HttpServletResponse response) {
// do something
return "test";
}
}
Now I'd like to do the following: The Controller should just receive the Person object without the CSRF Token. The CSRF Token should get processed inside the Interceptor. How can I do this? I think the main problem is, that I don't know how to get my CSRFTokenContainer object inside the Interceptor. Afterwards I'd like to modify the request to only contain the "data" part.
Some code example would be nice.
Thank you!
I've solved the CSRF problem this way:
I create the token on server side and place it inside the GWT host page via JSP. The token also gets stored in the Session:
myPage.jsp:
<%#taglib prefix="t" uri="myTags" %>
<!doctype html>
<html>
<head>
...
<script>
<t:csrfToken />
</script>
...
</head>
...
</html>
myTags.tld:
<?xml version="1.0" encoding="UTF-8"?>
<taglib xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>t</short-name>
<uri>myTags</uri>
<tag>
<name>csrfToken</name>
<tag-class>myapp.server.jsp.CSRFTokenTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
CSRFTokenTag:
public class CSRFTokenTag extends TagSupport {
private final SecureRandom random = new SecureRandom();
private String generateToken() {
final byte[] bytes = new byte[32];
random.nextBytes(bytes);
return Base64.encode(bytes);
}
#Override
public int doStartTag() throws JspException {
String token = generateToken();
try {
pageContext.getOut().write("var " + "myCSRFVarName" + " = \"" + token + "\";");
} catch (IOException e) {}
pageContext.getSession().setAttribute("csrfTokenSessionAttributeName", token);
return SKIP_BODY;
}
#Override
public int doEndTag() throws JspException {
return EVAL_PAGE;
}
}
GWT reads the token via JSNI:
public class CSRFToken {
private native static String get()/*-{
return $wnd["myCSRFVarName"];
}-*/;
}
And with every request the web application sends the token inside a custom HTTP header, for example like this:
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, "/rest/persons");
rb.setHeader("myCSRFTokenHeader", CSRFToken.get());
rb.setRequestData("someData");
rb.setCallback(new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
// ...
}
#Override
public void onError(Request request, Throwable exception) {
// ...
}
});
rb.send();
Within Spring I've created an Interceptor, that for every request reads the token from the submitted header and checks it:
#Component
public class CSRFInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String sessionCSRFToken = (String) request.getSession().getAttribute("csrfTokenSessionAttributeName");
if(sessionCSRFToken != null && sessionCSRFToken.equals(request.getHeader("myCSRFTokenHeader"))) {
return true;
} else {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
return false;
}
}
}
It's maybe not perfect, but it seems to work pretty well!