Java SE Context and Dependency Injection with Java EE support

5 Minutes reading time

Java CDI(Context and Dependency Injection) JSR-299 is available on Java EE application servers, but CDI(Weld is the reference implementation) is also embeddable in Java SE standard edition. Unfortunately Java EE functionalities like security or transaction support are not available here, for instance the following code just does not work as expected when running on CDI SE:

@Singleton
public class WeldBean {

    @Resource
    Principal principal;

    @RolesAllowed("securedrole")
    public void run() {
        System.out.println("Hello World : "+principal);
    }
}

The @Resource and @RolesAllowed annotations are not evaluated on Weld SE by default. Now, how can we enable these Java EE features?

The @Resource annotation is a problem, but the biggest problem is the @RolesAllowed annotation. This security check can by done by using an CDI interceptor, but the @RolesAllowed annotation does not support @InterceptorBinding. How can be enable security check without introducing new annotations in our code?

The answer is quite simple. We use the CDI Portable Extension API. This API allows us to manipulate the CDI container meta data before it is processed by the CDI runtime. But how does this help?

Well, with a CDI extension we can rewrite the given annotations from out code, and change it, for instance replace the @Resource annotation with @Inject, and this can be processed by the CDI SE runtime and will inject managed beans created from classes or producer methods for instance.

The tricky part is the @RolesAllowed annotation. A custom annotation is added to such annotated methods, with @InterceptorBinding. Now, a security interceptor checks for permissions and will continue with execution. In our case, the @MySecurityAnnotation is added to the method, and the following interceptor will check the permissions:

@Interceptor
@MySecurityAnnotation
public class MySecurityInterceptor {

    @AroundInvoke
    public Object aroundCheck(InvocationContext aContext) throws Exception {
        RolesAllowed theRolesAllowed = aContext.getMethod().getAnnotation(RolesAllowed.class);
        System.out.println("Secured Method check : Allowed roles : " + theRolesAllowed.value()[0]);
        return aContext.proceed();
    }
}

The tricky thing is we have to extract the original @RolesAllowed annotation from the method signature. It is still there as we use the Java reflection API to extract it. We just modified the CDI container meta data, not the Bytecode.

Simple but efficient. Using the same pattern, we can also implement transaction support with CDI running on Java SE. But how does the CDI extension work? The following code should show how this can be done. It can also be simplified using Weld Extensions API or Seam Solder, but here is everything without third party dependencies:

public class MyExtension implements Extension {

    static class ResourceInjectAlias implements Annotation, Inject {
        public Class<? extends Annotation> annotationType() {
            return Inject.class;
        }
    }

    static final ResourceInjectAlias INJECT_ALIAS = new ResourceInjectAlias();

    static class SecurityAlias implements Annotation, MySecurityAnnotation {

        public Class<? extends Annotation> annotationType() {
            return MySecurityAnnotation.class;
        }
    }

    static final SecurityAlias SECURITY_ALIAS = new SecurityAlias();

    <X> void processBean(@Observes ProcessAnnotatedType<X> aEvent, BeanManager aBeanManager) {

        final AnnotatedType<X> theType = aEvent.getAnnotatedType();

        AnnotatedType<X> theWrapped = new AnnotatedType<X>() {
            public Class<X> getJavaClass() {
                return theType.getJavaClass();
            }

            public Set<AnnotatedConstructor<X>> getConstructors() {
                return theType.getConstructors();
            }

            public Set<AnnotatedMethod<? super X>> getMethods() {
                Set<AnnotatedMethod<? super X>> theResult = new HashSet<AnnotatedMethod<? super X>>();
                for (AnnotatedMethod theMethod : theType.getMethods()) {
                    if (theMethod.isAnnotationPresent(RolesAllowed.class)) {
                        final AnnotatedMethod theOriginal = theMethod;

                        theResult.add(new AnnotatedMethod<X>() {
                            public Method getJavaMember() {
                                return theOriginal.getJavaMember();
                            }

                            public List<AnnotatedParameter<X>> getParameters() {
                                return theOriginal.getParameters();
                            }

                            public boolean isStatic() {
                                return theOriginal.isStatic();
                            }

                            public AnnotatedType<X> getDeclaringType() {
                                return theOriginal.getDeclaringType();
                            }

                            public Type getBaseType() {
                                return theOriginal.getBaseType();
                            }

                            public Set<Type> getTypeClosure() {
                                return theOriginal.getTypeClosure();
                            }

                            public <T extends Annotation> T getAnnotation(Clas<T> annotationType) {
                                return theOriginal.getAnnotation(annotationType);
                            }

                            public Set<Annotation> getAnnotations() {
                                Set<Annotation> theResult = new HashSet<Annotation>();
                                theResult.addAll(theOriginal.getAnnotations());
                                theResult.add(SECURITY_ALIAS);
                                return theResult;
                            }

                            public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
                                return theOriginal.isAnnotationPresent(annotationType);
                            }
                        });
                    } else {
                        theResult.add(theMethod);
                    }
                }
                return theResult;
            }

            public Set<AnnotatedField<? super X>> getFields() {
                Set<AnnotatedField<? super X>> theResult = new HashSet<AnnotatedField<? super X>>();
                for (AnnotatedField theField : theType.getFields()) {
                    if (theField.isAnnotationPresent(Resource.class)) {

                        final AnnotatedField theOriginal = theField;

                        theResult.add(new AnnotatedField<X>() {
                            public Field getJavaMember() {
                                return theOriginal.getJavaMember();
                            }

                            public boolean isStatic() {
                                return theOriginal.isStatic();
                            }

                            public AnnotatedType<X> getDeclaringType() {
                                return theOriginal.getDeclaringType();
                            }

                            public Type getBaseType() {
                                return theOriginal.getBaseType();
                            }

                            public Set<Type> getTypeClosure() {
                                return theOriginal.getTypeClosure();
                            }

                            public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
                                return theOriginal.getAnnotation(annotationType);
                            }

                            public Set<Annotation> getAnnotations() {
                                Set<Annotation> theResult = new HashSet<Annotation>();
                                theResult.addAll(theOriginal.getAnnotations());
                                theResult.add(INJECT_ALIAS);
                                return theResult;
                            }

                            public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
                                return theOriginal.isAnnotationPresent(annotationType);
                            }
                        });
                    } else {
                        theResult.add(theField);
                    }
                }
                return theResult;
            }

            public Type getBaseType() {
                return theType.getBaseType();
            }

            public Set<Type> getTypeClosure() {
                return theType.getTypeClosure();
            }

            public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
                return theType.getAnnotation(annotationType);
            }

            public Set<Annotation> getAnnotations() {
                return theType.getAnnotations();
            }

            public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
                return theType.isAnnotationPresent(annotationType);
            }
        };
        aEvent.setAnnotatedType(theWrapped);
    }
}

This does all the job, replace @Resource with @Inject and add the @MySecurityAnnotation to @RolesAllowed annotated method so their execution is intercepted by the security interceptor. Here we just rewrite the field or method level annotations, but to get full CDI support, we would also have to rewrite constructor annotations. But i hope you get how this can be done by looking at the code. If you want to read more about Weld, CDI and the Portable Extension API, i suggest to read the Weld Portable Extension Documentation.

Weld offers of course a SPI which can do the same thing. But the CDI Extension API is a container independent way to implement Java EE security and transaction support with Weld CDI running on Java SE standard edition. I really love it :-)

Git revision: 63a36b0

Loading comments...