1

I have the same problem and tried Zero3's solution (Required @QueryParam in JAX-RS (and what to do in their absence)), but in my case parameter.isAnnotationPresent(Required.class) always return false.

This is my Required annotation:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Required {
    // This is just a marker annotation, so nothing in here.
}

I also tried it with a BeanParam annotation and modified the filter accordingly, but same result - always get null for isAnnotionPresen.

I'm using WildFly 9 (RESTeasy) which automatically registers the request filter.

My REST resource looks like this:

@GET
@Path("/{type}/{id}")
public Response getAllByTypeAndId(@Required @BeanParam RequiredQueryParams requiredQueryParams,
                                  @Required @QueryParam("mandant") String mandant,
                                  @PathParam("type") String type,
                                  @PathParam("id") Long id) {
...doSomething...
}

Running the debugger shows for parameter.declaredAnnotations two entries in the HashMap for BeanParam:

0 interface my.annotations.Required -> @my.annotations.Required()
1 interface javax.ws.rs.BeanParam -> @javax.ws.rs.BeanParam()

and for QueryParam:

0 interface my.annotations.Required -> @my.annotations.Required()
1 interface javax.ws.rs.QueryParam -> @javax.ws.rs.QueryParam(value=mandant)

Any hints welcome - Thank you!

Community
  • 1
  • 1
raho
  • 129
  • 4
  • 18
  • Seems like bean validation would be more appropriate for this use case. Check out the RESTEasy (Wildfly's jax-rs implementation) documentation. There's a section on bean validation – Paul Samsotha Jul 28 '16 at 15:56
  • 1
    That would be the last option, because I'm working on a multi-tenant application where we have lots of REST services and each of them needs the mandatory "mandant" parameter. I would prefer a filter-solution like described in Zero3's solution (http://stackoverflow.com/questions/13968261/required-queryparam-in-jax-rs-and-what-to-do-in-their-absence/38639372#38639372) – raho Jul 29 '16 at 04:58

2 Answers2

1

Based on the information in your question and the additional information in the comments, I think I have a rough idea of what is going on here. Let's start at the original issue:

parameter.isAnnotationPresent(Required.class) always return false

Now let's take a look at the implementation of AnnotatedElement#isAnnotationpresent(Class<? extends Annotation>) in Java 8:

default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
    return getAnnotation(annotationClass) != null;
}

This leads us to the implementation of Parameter#getAnnotation(Class<T>):

public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
    Objects.requireNonNull(annotationClass);
    return annotationClass.cast(declaredAnnotations().get(annotationClass));
}

The documentation of Object#cast(Object) states that:

@return the object after casting, or null if obj is null

So it appears like declaredAnnotations().get(annotationClass) returns null. Parameter#declaredAnnotations() returns a Map<Class<? extends Annotation>, Annotation>, which is actually a HashMap, containing a mapping of annotation classes to annotation instances for the parameter in question. The documentation of Map#get(Object) states that:

@return the value to which the specified key is mapped, or null if this map contains no mapping for the key

This is consistent with your comment that states that your Required.class object does not equal (nor have the same hashcode as) the class object of the Required annotation present on the parameter in question.

This leads us to the core of the issue: Why is your annotation class not equal to itself? After all, Object#equals(object) is implemented like this:

public boolean equals(Object obj) {
    return (this == obj);
}

For this implementation of equals(Object) to work for Class<T> objects (which inherits the implementation), the assumption is clearly that there is only one such class object per class. To my knowledge, this is also the case when comparing class objects loaded by the same class loader. This is probably specified somewhere in the Java specification or maybe in some class loading documentation somewhere. I don't know, but let's assume that this is how things work.

However, in more advanced applications, classes might be loaded by different class loaders at the various layers of your application, invalidating the singleton assumption and in turn causing the issue you are experiencing. So my guess is that the WildFly application server loaded your Required annotation using a different class loader than the one you later use to obtain the reference to the Required.class object.

I don't know much about WildFly, but I do know that Java applications servers usually rely on some quite bureaucratic class loader hierarchies for loading classes (for security, and other reasons). I suggest looking into the WildFly documentation which hopefully has some more information about this.

TL;DR: I think your Required.class object represents a different copy of your Required annotation class than the one present on the parameter. Make sure that the same class loader is used to load both.

Zero3
  • 594
  • 2
  • 11
  • 18
  • 1
    Thanks for your very detailed explanation. Regarding your hint to guarantee that the same class loader should be used, I refactored my application and well, `isAnnotationPresent()` works as expected! I'll mark your comment as answer to this question! – raho Aug 11 '16 at 05:21
0

As a work-around for non-working parameter.isAnnotationPresent(Required.class) in ContainerRequestFilter I'm using now this method:

private boolean isRequired(Parameter parameter) {
    for (Annotation annotation : parameter.getDeclaredAnnotations()) {
        if (Required.class.getName().equals(annotation.annotationType().getName())) {
            return true;
        }
    }
    return false;
}

eitherway i'm wondering why isAnnotionaPresent() does not work ?!

raho
  • 129
  • 4
  • 18
  • 1
    Interesting. Are you doing something funny in your annotation type? Like overriding `equals()` or `hashCode()`? Since you are comparing by annotation class name (which is not a very robust way of doing this, of course), I assume you are having problems comparing directly with `equals()`? Debugging hint: Try printing the result of `equals()` and `hashcode()` next to the names you are comparing right now and see if things look right. – Zero3 Aug 01 '16 at 19:03
  • Hi Zero3, my annotation looks the same like yours: `@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Required { // This is just a marker annotation, so nothing in here. }` The `hashCode()` of the compared names are equal, so things should be right in my work-around ;-) – raho Aug 02 '16 at 05:51
  • Hmm. Actually, what I meant was that you could try printing `Required.class.hashcode()`, `annotation.annotationType().hashcode()` and `Required.class.equals(annotation.annotationType())`. If these are not identical/true as expected, that might be the cause of your troubles. Another cause could of course be if you imported a wrong Required annotation by mistake in your endpoint Java file (you did not post the full code sample in your original post, so it is hard to get the full picture here). – Zero3 Aug 02 '16 at 12:45
  • I added my implementation of `Required`to the original post. – raho Aug 03 '16 at 05:41
  • 1
    And here my debug output: `Required.class=interface at.luxbau.mis2.commons.annotation.Required` `annotation.annotationType()=interface at.luxbau.mis2.commons.annotation.Required` `Required.class.equals(annotation.annotationType())=false // false, because hashCode() returns different values?!` `Required.class.hashCode()=145058991` `annotation.annotationType().hashCode()=446212579` `Required.class.getName()=at.luxbau.mis2.commons.annotation.Required` `annotation.annotationType().getName()=at.luxbau.mis2.commons.annotation.Required` – raho Aug 03 '16 at 05:44