JAX-RS: map method entity based on method annotation - java

I am using JAX-RS 2.0 with Jersey 2.6. I was wondering if it was possible to have something like this:
#GET
#Path("/get/{id}")
#MapTo(type = MyObjectDTO.class)
public MyObject getMyObject(#PathParam("id") String id){
MyObject o = ...
return o;
}
In the method above I am returning an instance of MyObject. However, I have defined the MapTo annotation to indicate that I want to map this object to MyObjectDTO. The way I was thinking this could work is to process the response early in a ContainerResponseFilter, detect the annotation MapTo and, assuming no error occurred, replace the entity in the response with an instance of MyObjectDTO created appropriately from the existing entity (of type MyObject).
However, I couldn't find a way to get the Method in the resource that was just called after the request came in, i.e., the getMyObject method, so that I can scan for the MapTo annotation.
Is there a way to achieve this in a JAX-RS-y kind of way?

Is this some serious reason you cannot return dto object? Sounds very strange...You can probably use AOP but I guess it would be bad practive
Here the Spring AOP example
http://docs.spring.io/spring/docs/2.5.4/reference/aop.html

I think I found a solution by reading this SO. I created a class that looks like this:
#Provider // or register in the configuration...
public class DTOMapperFeature implements DynamicFeature {
#Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
for (Annotation annotation : resourceInfo.getResourceMethod().getAnnotations()) {
if (annotation instanceof MapTo) {
MapTo mapTo = (MapTo) annotation;
// Note: additional validation (return type shouldn't be void,
// collections are out etc.) is required before creating this,
// or should be pushed in the DTOMapperFilter.
// You get the gist: this filter will map the entity to an instance
// of the specified class (using a constructor in this case).
context.register(new DTOMapperFilter(
resourceInfo.getResourceMethod().getReturnType(),
mapTo.getResponseType());
}
}
}
#Priority(/* appropriate priority here! */)
public final static class DTOMapperFilter implements ContainerResponseFilter {
public DTOMapperFilter(Class<?> declaredReturnType, Class<?> responseType) {
// implementation omitted: find DTO constructor etc.
// throw if responseType does NOT have a constructor that takes an instance
// of declaredReturnType: catch errors at application bootstrap!
}
#Override
public void filter(
ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
// implementation omitted: create instance of DTO class using constructor
}
}
}
Given sensible exceptions will be thrown from either the constructor of DTOMapperFilter or the configure method above, this should be pretty robust and errors detectable at test time.

Related

How to Inject all subclasses of a specific class with no common interface?

Question:
In order to Inject all subclasses of a superclass with no common interface, I created an interface tightly-coupled to said superclass, that every "properly" written subclass is supposed to implement.
This works, but seems insane. Was there a better way?
A simple cast do not work, as the Instance holds only a proxy that do not resolves to any real subclass of the interface when called. This results in a ClassCastException.
Some context:
I was recently tasked to provide framework code for an application. In this application, several data transfer objects are mapping from and to service level POJOs, but their mappings are not always trivial. Dozer is used to do most of the work and to avoid boilerplate code.
In the specific cases requiring explicit mapping instructions, the current recommendation with Dozer is to use the API driven mapping. All the BeanMappingBuilder subclasses, defining the mappings, should be added to the Dozer mapper upon initialisation.
In order to keep all the work needed to add a new BeanMappingBuilder in one place, I came with a convoluted use of dependancy injection that will automatically add it to the Dozer mapper, despite it having no common interface, only a common superclass with the others.
Some code:
The interface:
#Local
public interface DtoBeanMappingBuilder {
BeanMappingBuilder get();
}
Subclass example:
#Stateless
public class SomeDtoMappingBuilder extends BeanMappingBuilder implements DtoBeanMappingBuilder {
#Override
public BeanMappingBuilder get() {
return this;
}
#Override
protected void configure() {
mapping(
// Some mapping...
);
}
}
Mapper with injection point:
#Singleton
#Startup
public class DtoBeanMapper {
private DozerBeanMapper innerMapper;
#Inject
#Any
private Instance<DtoBeanMappingBuilder> mappingBuilders;
public <D> D map(Object source, Class<D> destinationClass) {
return innerMapper.map(source, destinationClass);
}
#PostConstruct
private void init() {
innerMapper = new DozerBeanMapper();
mappingBuilders.forEach(mb -> innerMapper.addMapping(mb.get()));
}
}

Method invocation based on custom annotation in Spring?

I have a custom annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Controller {
EventType[] events() default EventType.MESSAGE;
}
And there are methods in class B using them like below:
#Controller(events = {EventType.MESSAGE, EventType.DIRECT_MESSAGE})
public void onMessage(Message msg) { }
#Controller(events = {EventType.STAR_ADDED})
public void onStarAdded(Message msg) { }
Now, I want to invoke the above methods based on the annotation events value from another class A. In other words, when class A receives an event of type STAR_ADDED, I want to invoke all methods in class B with annotation #Controller(events = {EventType.STAR_ADDED}).
I know how to do this in Java but does Spring provide any API to do this? If yes, a code snippet would be helpful too.
Solution 1:
You could also do something like this:
enum EventType {
MESSAGE {
#Override
public void handleMessage(Service service, Message message) {
service.onMessage(message);
}
},
STAR_ADDED {
#Override
public void handleMessage(Service service, Message message) {
service.onStarAdded(message);
}
public abstract void handleMessage(Service service, Message message);
}
}
In your other class, where you know what is the "active" event:
yourEvent.handleMessage(service, message);
Solution 2:
I don't know if spring has anything precisely for that, otherwise you could also use reflection. Here's an example using reflection (I much prefer the solution above => enum without reflection):
for(Method method: Service.class.getDeclaredMethods()){
Controller annotation = m.getAnnotation(Controller.class);
for(EventType event: annotation.events()){
if(event.equals(yourActiveEventType)){
method.invoke(service, message);
}
return ...
}
}
Hint (not a solution) 3:
I really don't think the following applies for your scenario, but I thought I'd mention it... Spring AOP lets you trigger some code when an annotated method is called (it's kind of the opposite of your scenario), check this answer, but it may be worth the read for you: aspectj-pointcut-for-all-methods-of-a-class-with-specific-annotation
#Around("execution(#Controller * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
throws Throwable {
// perform actions before
return pjp.proceed();
// perform actions after
}
Solution 4: (added after comments)
Using org.reflections
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
example:
Service service = ...;
Message message = ...;
Set<Method> methods =
ReflectionUtils.getMethods(Service.class, ReflectionUtils.withAnnotation(Controller.class),ReflectionUtils.withParametersAssignableTo(Message.class));
for(Method m: methods){
Controller controller = m.getAnnotation(Controller.class);
for(EventType eventType: controller.value()){
if(EventType.MESSAGE.equals(eventType)){
m.invoke(service, message);
}
}
}
This assumes that you already hold the reference to the Service object (where your methods are).
Since you are using Spring, if your 'Services' are spring managed, you may get the instance from spring's context, you'll have to try it out for yourself, as this is somewhat bound to your design:
#Autowired
private ApplicationContext appContext;
Reflections r = new Reflections(new MethodAnnotationsScanner(), "com.your.package");
Set<Method> methods = r.getMethodsAnnotatedWith(Controller.class);
for(Method m: methods){
Controller controller = m.getAnnotation(Controller.class);
for(EventType eventType: controller.value()){
if(EventType.MESSAGE.equals(eventType)){
String className = m.getDeclaringClass().getSimpleName();
className = className.replaceFirst(className.substring(0,1), className.substring(0,1).toLowerCase());
Object service = appContext.getBean(className);
m.invoke(service, message);
}
}
}
This works if your Class is spring managed and is added to the context using its default camelcase name.
You may simplify the logic, but I believe the principal elements are there.

Java Spring #Valid on a method call [duplicate]

Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
Applicable methods have JSR-303 constraint annotations on their
parameters and/or on their return value (in the latter case specified
at the method level, typically as inline annotation).
Validation groups can be specified through Spring's Validated
annotation at the type level of the containing target class, applying
to all public service methods of that class. By default, JSR-303 will
validate against its default group only.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}

Writing into ServletContext in Jersey using annotation.

I have a class like this:
public class MyResource(){
public MyResource(#Context ServletContext context){
context.setAttribute("someAttribute","someVal");
}
#PUT
public void someMethod(){
...
}
}
and I would like to this using annotations (i.e. JAX-RS/Jersey reads the value of the annotation and writes it into ServletContext so that I can access this value somewhere else where I inject the ServletContext in the request scope.)
#MyCustomAnnotation(name="someVal")
public class MyResource(){
}
Annotation needs to be treated by some code.
You need to create a filter, that processes your custom annotation, before your method is called.
see : https://jersey.java.net/documentation/latest/filters-and-interceptors.html
Creating a filter, should be fairly easy, but it is not enough. It will get called, but won't know in what context it will be called. By context, I mean which class / method will be called right after the filter is executed. In this example I assumed your annotation (called MyCustomAnnotation) can be applied to class / method.
For this, you need to create a "Dynamic Feature" that will bind a different instance of the filter, for each possible context.
In details :
For a given JAX-RS class :
#MyCustomAnnotation(name="someVal")
class MyClass{
#GET
#MyCustomAnnotation(name="someConfig")
public Object myMethod(){
...
}
#GET
#MyCustomAnnotation(name="otherConfig")
public Object myOtherMethod(){
...
}
}
First, create your annotation (I guess you know, but just to be clear) :
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation {
String name() default "";
}
Then, create a filter.
Notice the special constructor. A different instance of the filter will be created for each possible context. The right instance of the filter will be used in a specific context. This way it will know in what context (Class / Method) is is called. This way, using intro-spectation, your filter can behave however you like, based of the annotation you used on your target class and/or method :
#Priority(Priorities.AUTHORIZATION - 1)
public class MyFilter implements ContainerRequestFilter {
private final Class<?> _class;
private final Method method;
private MyCustomAnnotation classAnnotation;
private MyCustomAnnotation methodAnnotation;
public MyFilter(Class<?> _class, Method method) {
this._class = _class;
this.method = method;
this.classAnnotation = _class.getAnnotation(MyCustomAnnotation.class);
this.methodAnnotation = method.getAnnotation(MyCustomAnnotation.class);
}
#Override
public void filter(ContainerRequestContext requestContext) {
// your code goes here!
// based on classAnnotation and/or methodAnnotation,
// add whatever you want to the requestContext
}
}
Ok, so now we have an annotation, a filter that process this annotation, now we need to bind dynamically to class / methods that are annotated
public class MyFilterDynamicFeature implements DynamicFeature {
#Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext configuration) {
//if the class or the method is annotated, bind a new instance of our filter to this method
if(resourceInfo.getResourceClass().getAnnotation(MyCustomAnnotation.class)!=null || resourceInfo.getResourceMethod().getAnnotation(MyCustomAnnotation.class)!=null){
configuration.register(new MyFilter(resourceInfo.getResourceClass(), resourceInfo.getResourceMethod()));
}
}
}
In your JAX-RS configuration... register your new DynamicFeature
public class MyRestConfig extends ResourceConfig {
public RestConfig() {
// your configs...
packages("com.yourpackage.rest");
// ...
// handle #MyCustomAnnotation annotations
register(MyFilterDynamicFeature.class);
// ...
}
}
I hope this is clear. Recap of what you need to do
create your annotation
annotate your JAX-RS class / method with your annotation
create a filter that will process your annotation
create a dynamic feature, that will bind a different instance of the filter for each different context (method / class combination, where at least one or the other is annotated with your annotation)
register the dynamic feature in your rest config
----Update--------
Rather than using the Dynamic Feature, you should be able to inject the ressource info at runtime
#Context
private ResourceInfo resourceInfo;

Configure interceptor (proxy) on intercepted method

As far as I know, in Spring AOP when we want some method call to be intercepted, we configure an Aspect having such a pointcut configuration that matches wanted method invocation. In other words, we configure interception on the Aspect side.
Is there a way to configure it entirely from the opposite side, that is, on method which invocation is to be intercepted? I expect that something like this is possible:
#Component
class MyClass {
#Intercept(interctptor="myInterceptor", method="invoke")
Object methodThatWillBeIntercepted(Object arg) {
// ..
}
}
#Component(value="myInterceptor")
class MyInterceptor {
Object invoke(MethodInvocation mi) {
// ...
if (someCondition) {
return mi.proceed();
} else {
return someOtherValue;
}
}
}
You can, at least if you're using it with AspectJ. You can use the syntax #annotation(com.mycompany.MyAnnotation) in your pointcut declaration to target elements that are annotated with your annotation. You can read more about it in section 9.2.3 of the Spring reference documentation
If you're not using AspectJ, but a generic proxy based interceptor, a "brute force" approact would be to proxy all the object you want to check and then check the method invocation argument to see if the method is annotated with your annotation, something like this:
class MyInterceptor {
public Object invoke(MethodInvocation mi) {
if(mi.getMethod().getAnnotation(MyAnnotationClass.class) != null) {
// Do the interception
}
else {
return mi.proceed();
}
}
}
Don't remember the exact API for MethodInvocation, but something like that.

Categories