org.os890.ds.addon.spring.impl.SpringBridgeExtension.java Source code

Java tutorial

Introduction

Here is the source code for org.os890.ds.addon.spring.impl.SpringBridgeExtension.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.os890.ds.addon.spring.impl;

import org.apache.deltaspike.core.api.provider.BeanProvider;
import org.apache.deltaspike.core.util.ClassUtils;
import org.apache.deltaspike.core.util.ExceptionUtils;
import org.apache.deltaspike.core.util.ServiceUtils;
import org.apache.deltaspike.core.util.bean.BeanBuilder;
import org.apache.deltaspike.core.util.bean.ImmutablePassivationCapableBean;
import org.apache.deltaspike.core.util.metadata.AnnotationInstanceProvider;
import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
import org.apache.deltaspike.core.util.metadata.builder.ContextualLifecycle;
import org.os890.ds.addon.spring.impl.bidirectional.ManualSpringContainerManager;
import org.os890.ds.addon.spring.spi.BeanFilter;
import org.os890.ds.addon.spring.spi.SpringContainerManager;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

import javax.enterprise.context.Dependent;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.*;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;

//other scopes than singleton and prototype require a proper proxy-config (ScopedProxyMode.TARGET_CLASS) for spring
public class SpringBridgeExtension implements Extension {
    private List<Bean<?>> cdiBeansForSpring = new ArrayList<Bean<?>>();
    private static ThreadLocal<BeanFactoryPostProcessor> currentBeanFactoryPostProcessor = new ThreadLocal<BeanFactoryPostProcessor>();

    private EditableConfigurableApplicationContextProxy springContext;

    private List<BeanFilter> beanFilterList = new ArrayList<BeanFilter>();

    public void init(@Observes BeforeBeanDiscovery beforeBeanDiscovery) {
        beanFilterList.addAll(ServiceUtils.loadServiceImplementations(BeanFilter.class));
    }

    //for supporting producers ProcessBean would be needed
    //however later on Bean#getBeanClass is used which returns the producer-class and not the return-type of the producer (like #getTypes)
    public void recordBeans(@Observes ProcessBean pb) {
        Bean bean = pb.getBean();

        if (!isSpringAdapterBean(bean) && !isFilteredCdiBean(bean.getBeanClass())) {
            this.cdiBeansForSpring.add(bean);
        }
    }

    private boolean isFilteredCdiBean(Class beanClass) {
        for (BeanFilter beanFilter : this.beanFilterList) {
            if (!beanFilter.exposeCdiBeanToSpring(beanClass)) {
                return true;
            }
        }
        return false;
    }

    //TODO improve ds -> simplify this method
    private boolean isSpringAdapterBean(Bean bean) //don't add spring-bean adapters back to spring
    {
        if (bean instanceof ImmutablePassivationCapableBean) {
            for (Field field : bean.getClass().getSuperclass().getDeclaredFields()) {
                if (ContextualLifecycle.class.isAssignableFrom(field.getType())) {
                    field.setAccessible(true);

                    try {
                        if (field.get(bean) instanceof SpringAwareBeanLifecycle) {
                            return true;
                        }
                    } catch (IllegalAccessException e) {
                        throw ExceptionUtils.throwAsRuntimeException(e);
                    }
                }
            }
        }
        return false;
    }

    public void initContainerBridge(@Observes AfterBeanDiscovery abd, BeanManager beanManager) {
        this.springContext = new EditableConfigurableApplicationContextProxy(
                resolveSpringContext(abd, beanManager));

        if (this.springContext == null) {
            abd.addDefinitionError(new IllegalStateException("no spring-context found/created"));
            return;
        }

        for (String beanName : this.springContext.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = this.springContext.getBeanFactory().getBeanDefinition(beanName);

            String name = beanDefinition.getBeanClassName();

            if (name == null) //can be null in case of config-files as spring bean
            {
                continue;
            }

            Class<?> beanClass = ClassUtils.tryToLoadClassForName(name);

            if (CdiSpringScope.class.getName().equals(beanDefinition.getScope())
                    || isFilteredSpringBean(beanClass)) {
                continue; //don't add cdi-beans registered in spring back to cdi
            }

            abd.addBean(
                    createBeanAdapter(beanClass, beanName, beanDefinition, this.springContext, beanManager, abd));
        }

        this.beanFilterList.clear();
        this.cdiBeansForSpring.clear();
    }

    private boolean isFilteredSpringBean(Class<?> beanClass) {
        for (BeanFilter beanFilter : this.beanFilterList) {
            if (!beanFilter.exposeSpringBeanToCdi(beanClass)) {
                return true;
            }
        }
        return false;
    }

    private ConfigurableApplicationContext resolveSpringContext(AfterBeanDiscovery abd, BeanManager beanManager) {
        List<SpringContainerManager> scmList = ServiceUtils
                .loadServiceImplementations(SpringContainerManager.class);

        //TODO cleanup
        Map<String, Bean<?>> cdiBeansForSpringMap = new HashMap<String, Bean<?>>();
        for (Bean<?> bean : this.cdiBeansForSpring) {
            if (bean.getName() != null) {
                cdiBeansForSpringMap.put(bean.getName(), bean);
            }

            Set<Type> beanTypes = new HashSet<Type>(bean.getTypes());
            beanTypes.remove(Object.class);
            beanTypes.remove(Serializable.class);

            Type beanType = beanTypes.size() == 1 ? beanTypes.iterator().next() : null;

            if (beanType instanceof Class) { //to support producers
                cdiBeansForSpringMap.put(((Class) beanType).getName(), bean);
            } else { //fallback since spring doesn't support multiple types
                cdiBeansForSpringMap.put(bean.getBeanClass().getName(), bean);
            }
        }

        BeanFactoryPostProcessor beanFactoryPostProcessor = new CdiAwareBeanFactoryPostProcessor(beanManager,
                cdiBeansForSpringMap);

        try {
            currentBeanFactoryPostProcessor.set(beanFactoryPostProcessor);

            if (scmList.isEmpty()) {
                return null;
            }
            if (scmList.size() == 1) {
                return scmList.iterator().next().bootContainer(beanFactoryPostProcessor);
            }
            if (scmList.size() > 2) {
                abd.addDefinitionError(
                        new IllegalStateException(scmList.size() + " spring-context-resolvers found"));
            } else //2 are found -> use the custom one
            {
                for (SpringContainerManager containerManager : scmList) {
                    if (containerManager instanceof ManualSpringContainerManager) {
                        continue;
                    }

                    if (containerManager.isContainerStarted()) {
                        return containerManager.getStartedContainer();
                    }
                    return containerManager.bootContainer(beanFactoryPostProcessor);
                }
            }
            return null;
        } finally {
            currentBeanFactoryPostProcessor.set(null);
            currentBeanFactoryPostProcessor.remove();
        }
    }

    private <T> Bean<T> createBeanAdapter(Class<T> beanClass, String beanName, BeanDefinition beanDefinition,
            ConfigurableApplicationContext applicationContext, BeanManager bm, AfterBeanDiscovery abd) {
        String beanScope = beanDefinition.getScope();
        ContextualLifecycle lifecycleAdapter = new SpringAwareBeanLifecycle(applicationContext, beanName,
                beanScope);

        List<Annotation> cdiQualifiers = tryToMapToCdiQualifier(beanName, beanDefinition, abd);

        //we don't need to handle (remove) interceptor annotations, because BeanBuilder >won't< add them (not supported)
        BeanBuilder<T> beanBuilder = new BeanBuilder<T>(bm)
                .readFromType(new AnnotatedTypeBuilder<T>().readFromType(beanClass).create()).name(beanName)
                .beanLifecycle(lifecycleAdapter).injectionPoints(Collections.<InjectionPoint>emptySet())
                .scope(Dependent.class) //the instance (or proxy) returned by spring shouldn't bootContainer proxied
                .passivationCapable(true).alternative(false).nullable(true);

        if (!cdiQualifiers.isEmpty()) {
            beanBuilder.addQualifiers(cdiQualifiers);
        }

        boolean typeObjectFound = false;
        for (Type type : beanBuilder.getTypes()) {
            if (Object.class.equals(type)) {
                typeObjectFound = true;
            }
        }

        if (!typeObjectFound) {
            beanBuilder.addType(Object.class); //java.lang.Object needs to be present (as type) in any case
        }

        return beanBuilder.create();
    }

    //TODO test it
    private List<Annotation> tryToMapToCdiQualifier(String beanName, BeanDefinition beanDefinition,
            AfterBeanDiscovery abd) {
        List<Annotation> cdiQualifiers = new ArrayList<Annotation>();
        if (beanDefinition instanceof AbstractBeanDefinition) {
            boolean unsupportedQualifierFound = false;
            for (AutowireCandidateQualifier springQualifier : ((AbstractBeanDefinition) beanDefinition)
                    .getQualifiers()) {
                Class qualifierClass = ClassUtils.tryToLoadClassForName(springQualifier.getTypeName());

                if (qualifierClass == null) {
                    unsupportedQualifierFound = true;
                    break;
                }

                if (Annotation.class.isAssignableFrom(qualifierClass)) {
                    Map<String, Object> qualifierValues = new HashMap<String, Object>();
                    String methodName;
                    Object methodValue;
                    for (Method annotationMethod : qualifierClass.getDeclaredMethods()) {
                        methodName = annotationMethod.getName();
                        methodValue = springQualifier.getAttribute(methodName);

                        if (methodValue != null) {
                            qualifierValues.put(methodName, methodValue);
                        }

                    }

                    cdiQualifiers.add(AnnotationInstanceProvider.of(qualifierClass, qualifierValues));
                } else {
                    unsupportedQualifierFound = true;
                    break;
                }
            }
            if (unsupportedQualifierFound) {
                abd.addDefinitionError(new IllegalStateException(beanName + " can't be added"));
            }
        }
        return cdiQualifiers;
    }

    ApplicationContext getApplicationContext() {
        return springContext;
    }

    //only allowed after the bootstrapping process
    //needed by other bridges which have to merge different context instances
    public static void updateSpringContext(ConfigurableApplicationContext springContext) {
        ApplicationContext context = BeanProvider.getContextualReference(SpringBridgeExtension.class)
                .getApplicationContext();

        if (context instanceof EditableConfigurableApplicationContextProxy) {
            ((EditableConfigurableApplicationContextProxy) context).setWrapped(springContext);
        }
    }

    public static BeanFactoryPostProcessor getBeanFactoryPostProcessor() {
        return currentBeanFactoryPostProcessor.get();
    }
}