All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oneandone.ejbcdiunit.internal.EjbExtensionExtended Maven / Gradle / Ivy

Go to download

A module that can be used together with cdiunit to build en ejb-test-environment.

The newest version!
/*
 * Copyright 2014 Bryn Cooke Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in
 * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing permissions and limitations under the License.
 */
package com.oneandone.ejbcdiunit.internal;


import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.ejb.Asynchronous;
import javax.ejb.EJB;
import javax.ejb.MessageDriven;
import javax.ejb.Schedule;
import javax.ejb.Schedules;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.ejb.Stateful;
import javax.ejb.Stateless;
import javax.ejb.Timeout;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.AnnotatedField;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessInjectionTarget;
import javax.enterprise.inject.spi.ProcessManagedBean;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
import javax.persistence.Entity;
import javax.persistence.PersistenceContext;

import org.apache.deltaspike.core.util.metadata.AnnotationInstanceProvider;
import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
import org.jboss.weld.bean.proxy.InterceptionDecorationContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.oneandone.ejbcdiunit.ResourceQualifier;
import com.oneandone.ejbcdiunit.SupportEjbExtended;
import com.oneandone.ejbcdiunit.cdiunit.EjbName;
import com.oneandone.ejbcdiunit.persistence.SimulatedTransactionManager;
import com.oneandone.ejbcdiunit.resourcesimulators.SessionContextSimulation;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;

/**
 * CDI-Extension used to handle @Resource, @PersistenceContext...
 * normally it just adds @Inject to the declarations.
 * This was originally checked in at cdi-unit and has been adapted.
 */
@SupportEjbExtended
@ApplicationScoped
public class EjbExtensionExtended implements Extension {

    Logger logger = LoggerFactory.getLogger("CDI-Unit EJB-ExtensionExtended");

    private List> timerClasses = new ArrayList<>();

    private List> entityClasses = new ArrayList<>();
    private List> startupSingletons = new ArrayList<>();

    private static AnnotationLiteral createDefaultAnnotation() {
        return new AnnotationLiteral() {
            private static final long serialVersionUID = 1L;
        };
    }

    private static AnnotationLiteral createDependentAnnotation() {
        return new AnnotationLiteral() {
            private static final long serialVersionUID = 1L;
        };
    }

    private static AnnotationLiteral createApplicationScopedAnnotation() {
        return new AnnotationLiteral() {
            private static final long serialVersionUID = 1L;
        };
    }

    public List> getEntityClasses() {
        return entityClasses;
    }

    public List> getTimerClasses() {
        return timerClasses;
    }

    public List> getStartupSingletons() {
        return startupSingletons;
    }

    /**
     * use this event to initialise static contents in SimulatedTransactionManager
     *
     * @param bbd not used
     * @param  not used
     */
    public  void processBeforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
        new SimulatedTransactionManager().init();
    }

    private  void processClass(AnnotatedTypeBuilder builder, String name, boolean isSingleton, boolean scopeIsPresent) {
        logger.trace("processing class: {} singleton: {} scopeIsPresent: {}", name, isSingleton, scopeIsPresent);
        if (!scopeIsPresent) {
            if (!isSingleton || builder.getJavaClass().getFields().length > 0) {
                builder.addToClass(createDependentAnnotation());
            } else {
                builder.addToClass(createApplicationScopedAnnotation());  // For Singleton normally only ApplicationScoped
            }
        }

        builder.addToClass(createDefaultAnnotation());
        if (!name.isEmpty()) {
            builder.addToClass(new EjbName.EjbNameLiteral(name));
        } else {
            builder.addToClass(DefaultLiteral.INSTANCE);
        }
    }

    String beanNameOrName(EJB ejb) {
        if (!ejb.name().isEmpty()) {
            return ejb.name();
        } else {
            return ejb.beanName();
        }
    }

     T findAnnotation(Class annotatedType,  Class annotation) {
        if (annotatedType.equals(Object.class)) {
            return null;
        }
        return annotatedType.getAnnotation(annotation);
    }

     boolean isAnnotationPresent(Class annotatedType,  Class annotation) {
        if (annotatedType.equals(Object.class)) {
            return false;
        }
        return annotatedType.isAnnotationPresent(annotation);
    }

     boolean isAnnotationPresent(ProcessAnnotatedType pat,  Class annotation) {
        return isAnnotationPresent(pat.getAnnotatedType().getJavaClass(), annotation);
    }

    /**
     * Handle Bean classes, if EJB-Annotations are recognized change, add, remove as fitting.
     *
     * @param pat the description of the beanclass
     * @param  The type
     */
    public  void processAnnotatedType(@Observes ProcessAnnotatedType pat) {
        logger.trace("processing annotated Type: " + pat.getAnnotatedType().getJavaClass().getName());

        boolean modified = false;
        AnnotatedType annotatedType = pat.getAnnotatedType();
        AnnotatedTypeBuilder builder = new AnnotatedTypeBuilder().readFromType(annotatedType);

        boolean scopeIsPresent =
                annotatedType.isAnnotationPresent(ApplicationScoped.class)
                        || annotatedType.isAnnotationPresent(Dependent.class)
                        || annotatedType.isAnnotationPresent(RequestScoped.class)
                        || annotatedType.isAnnotationPresent(SessionScoped.class);

        Entity entity = annotatedType.getAnnotation(Entity.class);
        if (entity != null) {
            entityClasses.add(annotatedType.getJavaClass());
        }

        Stateless stateless = findAnnotation(annotatedType.getJavaClass(), Stateless.class);

        // Stateless stateless = annotatedType.getJavaClass().getAnnotation(Stateless.class);

        if (stateless != null) {
            processClass(builder, stateless.name(), false, scopeIsPresent);
            modified = true;
        }

        Stateful stateful = findAnnotation(annotatedType.getJavaClass(),Stateful.class);

        if (stateful != null) {
            processClass(builder, stateful.name(), false, scopeIsPresent);
            modified = true;
        }
        try {
            Singleton singleton = findAnnotation(annotatedType.getJavaClass(),Singleton.class);
            if (singleton != null) {
                processClass(builder, singleton.name(), true, scopeIsPresent);
                modified = true;
                if (annotatedType.getAnnotation(Startup.class) != null) {
                    startupSingletons.add(annotatedType.getJavaClass());
                }
            }
        } catch (NoClassDefFoundError e) {
            // EJB 3.0
        }

        for (AnnotatedMethod method : annotatedType.getMethods()) {
            EJB ejb = method.getAnnotation(EJB.class);
            if (ejb != null) {
                builder.removeFromMethod(method, EJB.class);
                modified = true;
                if (!beanNameOrName(ejb).isEmpty()) {
                    builder.addToMethod(method, new EjbName.EjbNameLiteral(beanNameOrName(ejb)));
                } else {
                    builder.addToMethod(method, DefaultLiteral.INSTANCE);
                }
            }
        }

        for (AnnotatedField field : annotatedType.getFields()) {
            boolean addInject = false;
            Produces produces = field.getAnnotation(Produces.class);
            EJB ejb = field.getAnnotation(EJB.class);
            if (ejb != null) {
                modified = true;
                addInject = true;
                if (field.getJavaMember().getType().equals(annotatedType.getJavaClass())) {
                    logger.error("Self injection of EJB Type {} in field {} of Class {} can't get simulated by ejb-cdi-unit",
                            field.getJavaMember().getType().getName(), field.getJavaMember().getName(),
                            field.getJavaMember().getDeclaringClass().getName());
                }

                builder.removeFromField(field, EJB.class);
                if (!beanNameOrName(ejb).isEmpty()) {
                    builder.addToField(field, new EjbName.EjbNameLiteral(beanNameOrName(ejb)));
                } else {
                    builder.addToField(field, DefaultLiteral.INSTANCE);
                }
            }
            Resource resource = field.getAnnotation(Resource.class);
            if (resource != null) {  // all Resources will be set injected. The Tester must provide anything for them.
                // this means that MessageDrivenContexts, SessionContext and JMS-Resources will be expected to be injected.
                addInject = true;
            }
            if (field.getAnnotation(PersistenceContext.class) != null) {
                addInject = true;
                builder.removeFromField(field, PersistenceContext.class);
            }
            if (addInject) {
                modified = true;
                builder.addToField(field, new AnnotationLiteral() {
                    private static final long serialVersionUID = 1L;
                });
                if (produces != null) {
                    builder.removeFromField(field, Produces.class);
                }
                if (field.getBaseType().equals(String.class)) {

                    builder.addToField(field, new ResourceQualifier.ResourceQualifierLiteral(resource.name(), resource.lookup(), resource.mappedName()) {
                        private static final long serialVersionUID = 1L;

                    });

                }
            }
        }
        if (modified) {
            pat.setAnnotatedType(builder.create());
        }
    }

    /**
     * create EJB-Wrapper, Interceptors to specific annotated types if necessary.
     *
     * @param pat the description of the type
     * @param  the type
     */
    public  void processInjectionTarget(
            @Observes ProcessAnnotatedType pat) {
        if (isAnnotationPresent(pat, Stateless.class) || isAnnotationPresent(pat, Stateful.class)
                || isAnnotationPresent(pat, Singleton.class)
                || isAnnotationPresent(pat, MessageDriven.class)) {
            createEJBWrapper(pat, pat.getAnnotatedType());
        } else {
            if (possiblyAsynchronous(pat.getAnnotatedType())) {
                logger.error("Non Ejb with Asynchronous-Annotation {}", pat);
            }

        }
    }

    public  void initializeSelfInit(@Observes ProcessInjectionTarget pit) {

        boolean needToWrap = false;
        for (AnnotatedField f : pit.getAnnotatedType().getFields()) {
            if (f.getJavaMember().getType().equals(pit.getAnnotatedType().getJavaClass())) {
                needToWrap = true;
                break;
            }
        }

        if (needToWrap) {
            final InjectionTarget it = pit.getInjectionTarget();
            final Set> annotatedTypeFields = pit.getAnnotatedType().getFields();
            final Class annotatedTypeJavaClass = pit.getAnnotatedType().getJavaClass();
            InjectionTarget wrapped = new InjectionTarget() {

                @Override
                public void inject(final T instance, CreationalContext ctx) {
                    HashMap, Object> orgValues = fetchOriginalValuesOfSelfFields(instance);
                    it.inject(instance, ctx);
                    // After injection replace all fields of self-type by enhanced ones which make sure interception is handled.
                    wrapDifferingValuesOfSelfFields(instance, orgValues);
                }


                @Override
                public void postConstruct(T instance) {
                    it.postConstruct(instance);
                }

                @Override
                public void preDestroy(T instance) {
                    it.dispose(instance);
                }

                @Override
                public void dispose(T instance) {
                    it.dispose(instance);
                }

                @Override
                public Set getInjectionPoints() {
                    return it.getInjectionPoints();
                }

                @Override
                public T produce(CreationalContext ctx) {
                    return it.produce(ctx);
                }

                private void wrapDifferingValuesOfSelfFields(T instance, HashMap, Object> orgValues) {
                    for (AnnotatedField f : annotatedTypeFields) {
                        if (f.getJavaMember().getType().equals(annotatedTypeJavaClass)) {
                            try {
                                final Field javaMember = f.getJavaMember();
                                javaMember.setAccessible(true);
                                final Object currentInstance = javaMember.get(instance);
                                if (currentInstance != null && currentInstance != orgValues.get(f)) {
                                    Enhancer enhancer = new Enhancer();
                                    enhancer.setSuperclass(currentInstance.getClass());
                                    enhancer.setCallback(new InvocationHandler() {
                                        @Override
                                        public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                                            SessionContextSimulation.startInterceptionDecorationContext();
                                            try {
                                                return method.invoke(currentInstance, objects);
                                            } catch (Throwable thw) {
                                                if (thw instanceof InvocationTargetException) {
                                                    throw thw.getCause();
                                                } else {
                                                    throw thw;
                                                }
                                            } finally {
                                                InterceptionDecorationContext.endInterceptorContext();
                                            }
                                        }
                                    });
                                    javaMember.setAccessible(true);
                                    javaMember.set(instance, enhancer.create());
                                }
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }

                private HashMap, Object> fetchOriginalValuesOfSelfFields(T instance) {
                    HashMap, Object> orgValues = new HashMap<>();
                    for (AnnotatedField f : annotatedTypeFields) {
                        if (f.getJavaMember().getType().equals(annotatedTypeJavaClass)) {
                            final Field javaMember = f.getJavaMember();
                            javaMember.setAccessible(true);
                            try {
                                orgValues.put(f, javaMember.get(instance));
                            } catch (IllegalAccessException e) {
                                new RuntimeException(e);
                            }
                        }
                    }
                    return orgValues;
                }

            };
            pit.setInjectionTarget(wrapped);
        }
    }


    private  boolean possiblyAsynchronous(final AnnotatedType at) {

        boolean isTimer = false;
        boolean isAsynch = false;
        if (at.isAnnotationPresent(Asynchronous.class)) {
            return true;
        }

        for (AnnotatedMethod m: at.getMethods()) {
            if (!isTimer && (m.isAnnotationPresent(Timeout.class)
                    || m.isAnnotationPresent(Schedule.class)
                    || m.isAnnotationPresent(Schedules.class)
                    )) {
                timerClasses.add(m.getJavaMember().getDeclaringClass());
                isTimer = true;
            }
            if (!isAsynch && m.isAnnotationPresent(Asynchronous.class)) {
                isAsynch = true;
            }
        }

        return isAsynch;
    }

    private  void createEJBWrapper(ProcessAnnotatedType pat,
            final AnnotatedType at) {
        EjbAsynchronous ejbAsynchronous = AnnotationInstanceProvider.of(EjbAsynchronous.class);

        EjbTransactional transactionalAnnotation =
                AnnotationInstanceProvider.of(EjbTransactional.class);

        AnnotatedTypeBuilder builder =
                new AnnotatedTypeBuilder().readFromType(at);
        builder.addToClass(transactionalAnnotation);
        if (possiblyAsynchronous(at)) {
            builder.addToClass(ejbAsynchronous);
        }

        // by annotating let CDI set Wrapper to this Bean
        pat.setAnnotatedType(builder.create());
    }

    void processManagedBean(@Observes ProcessManagedBean event) {
        // LOGGER.fine("Handling ProcessManagedBean event for " + event.getBean().getBeanClass().getName());

        // TODO - here we should check that all the rules have been followed
        // and call addDefinitionError for each problem we encountered

        Bean bean = event.getBean();
        for (InjectionPoint injectionPoint : bean.getInjectionPoints()) {
            StringBuilder sb = new StringBuilder();
            sb.append("  Found injection point ");
            sb.append(injectionPoint.getType());
            if (injectionPoint.getMember() != null && injectionPoint.getMember().getName() != null) {

                sb.append(": ");
                sb.append(injectionPoint.getMember().getName());
            }
            for (Annotation annotation : injectionPoint.getQualifiers()) {
                sb.append(" ");
                sb.append(annotation);
            }
            logger.trace(sb.toString());
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy