DevOps Zone is brought to you in partnership with:

Nicolas Frankel is an IT consultant with 10 years experience in Java / JEE environments. He likes his job so much he writes technical articles on his blog and reviews technical books in his spare time. He also tries to find other geeks like him in universities, as a part-time lecturer. Nicolas is a DZone MVB and is not an employee of DZone and has posted 228 posts at DZone. You can read more from them at their website. View Full User Profile

getCaller() hack

10.15.2013
| 3746 views |
  • submit to reddit

Disclaimer: the following is a devious hack, it should only be used if you know what you’re doing.

As developers, we should only call public APIs. However, the Java language cannot differentiate between public API and private stuff: as soon as a class and one of its method is public, we can reference the former and call the later. Therefore, we are exposed to the Dark Side of the Force, and sometimes tempted to use it.

A good example of this terrible temptation is the sun.reflect.Reflection.getCaller(int) method. As its name implies, this evil piece returns which class called your current code, letting you tailor your code behavior depending on the calling class. The Dark Side can be seductive indeed!

This way, we can check whether some caller has some property and let it invoke our stuff or not accordingly. For example, let’s design a code piece that can be invoked only from “privileged” code. Such privileges include: be a specific class, come from a specific package, be annotated with a specific annotation, etc. The following code uses the third option.

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
 
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
@Retention(RUNTIME)
@Target(TYPE)
public @interface Privileged {}

Now, checking for this annotation’s existence in the calling code is achieved like this:

public class Restricted {
 
    public Restricted() {
 
    int i = 2;
 
    while (true) {
 
        Class<?> caller = Reflection.getCallerClass(i++);
 
        if (caller == null) {
 
            throw new SecurityException();
        }
 
        if (caller.getAnnotation(Privileged.class) != null) {
 
            break;
        }
    }
}

  1. The int parameter in the getCallerClass() method refers to the number of frames up the calling stack. Index 0 is Reflection.getCallerClass() itself, while index 1 is our restricted constructor, so that real work should begin at 2.
  2. The algorithm is to search up the stack until either the whole stack has been browsed and no privileged code has been found or stop when the matching annotation is found. In the former case, throw an exception, in the latter, do the job as expected.
  3. Of course, this algorithm shouldn’t be hard-coded but factorized in a aspect and injected at some determined pointcut (perhaps a @Restricted annotation?)

Ok, but what use-case does this solve? Well, I’ve always thought about what requirements Hibernate (or JPA) put on your design. One of such requirement is to provide a no-arg constructor, for the framework to instantiate. After that, it can use setter or inject attributes directly. Unfortunately, this conflicts with my desire to provide immutable objects that have all mandatory parameters in the constructor. A basic solution would be to provide both constructors but document them accordingly (/** Do not use: this one is for Hibernate. */). However, to really enforce this rule, you’d probably need to use getCallerClass() with a check on an org.hibernate package.

Beware that Oracle intended to remove this method in the next JDK. Again, do not use this at home, but it opens some interesting perspectives, doesn’t it? [...evil laugh...]

Download the above code in IntelliJ/Maven format here.



Published at DZone with permission of Nicolas Frankel, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)