org.apache.tapestry5.internal.spring.SpringModuleDef.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.tapestry5.internal.spring.SpringModuleDef.java

Source

// Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation
//
// 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 org.apache.tapestry5.internal.spring;

import com.google.common.reflect.AbstractInvocationHandler;
import org.apache.tapestry5.internal.AbstractContributionDef;
import org.apache.tapestry5.ioc.AnnotationProvider;
import org.apache.tapestry5.ioc.Invokable;
import org.apache.tapestry5.ioc.ModuleBuilderSource;
import org.apache.tapestry5.ioc.ObjectCreator;
import org.apache.tapestry5.ioc.ObjectLocator;
import org.apache.tapestry5.ioc.ObjectProvider;
import org.apache.tapestry5.ioc.OperationTracker;
import org.apache.tapestry5.ioc.OrderedConfiguration;
import org.apache.tapestry5.ioc.ScopeConstants;
import org.apache.tapestry5.ioc.ServiceBuilderResources;
import org.apache.tapestry5.ioc.ServiceResources;
import org.apache.tapestry5.ioc.annotations.Primary;
import org.apache.tapestry5.ioc.def.ContributionDef;
import org.apache.tapestry5.ioc.def.DecoratorDef;
import org.apache.tapestry5.ioc.def.ModuleDef;
import org.apache.tapestry5.ioc.def.ServiceDef;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
import org.apache.tapestry5.plastic.PlasticUtils;
import org.apache.tapestry5.spring.ApplicationContextCustomizer;
import org.apache.tapestry5.spring.SpringConstants;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.SpringVersion;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.ServletContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A wrapper that converts a Spring {@link ApplicationContext} into a set of service definitions,
 * compatible with
 * Tapestry 5 IoC, for the beans defined in the context, as well as the context itself.
 */
public class SpringModuleDef implements ModuleDef {

    static final String SERVICE_ID = "ApplicationContext";

    private final Map<String, ServiceDef> services = CollectionFactory.newMap();

    private final boolean compatibilityMode;

    private final AtomicBoolean applicationContextCreated = new AtomicBoolean(false);

    private final ServletContext servletContext;

    private ApplicationContext locateExternalContext() {
        ApplicationContext context = locateApplicationContext(servletContext);

        applicationContextCreated.set(true);

        return context;
    }

    /**
     * Invoked to obtain the Spring ApplicationContext, presumably stored in the ServletContext.
     * This method is only used in Tapestry 5.0 compatibility mode (in Tapestry 5.1 and above,
     * the default is for Tapestry to <em>create</em> the ApplicationContext).
     *
     * @param servletContext used to locate the ApplicationContext
     * @return the ApplicationContext itself
     * @throws RuntimeException if the ApplicationContext could not be located or is otherwise invalid
     * @since 5.2.0
     */
    protected ApplicationContext locateApplicationContext(ServletContext servletContext) {
        ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);

        if (context == null) {
            throw new NullPointerException(String.format(
                    "No Spring ApplicationContext stored in the ServletContext as attribute '%s'. "
                            + "You should either re-enable Tapestry as the creator of the ApplicationContext, or "
                            + "add a Spring ContextLoaderListener to web.xml.",
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE));
        }

        return context;
    }

    public SpringModuleDef(ServletContext servletContext) {
        this.servletContext = servletContext;

        compatibilityMode = Boolean
                .parseBoolean(servletContext.getInitParameter(SpringConstants.USE_EXTERNAL_SPRING_CONTEXT));

        final ApplicationContext externalContext = compatibilityMode ? locateExternalContext() : null;

        if (compatibilityMode)
            addServiceDefsForSpringBeans(externalContext);

        ServiceDef applicationContextServiceDef = new ServiceDef() {
            @Override
            public ObjectCreator createServiceCreator(final ServiceBuilderResources resources) {
                if (compatibilityMode)
                    return new StaticObjectCreator(externalContext,
                            "externally configured Spring ApplicationContext");

                ApplicationContextCustomizer customizer = resources.getService("ApplicationContextCustomizer",
                        ApplicationContextCustomizer.class);

                return constructObjectCreatorForApplicationContext(resources, customizer);
            }

            @Override
            public String getServiceId() {
                return SERVICE_ID;
            }

            @Override
            public Set<Class> getMarkers() {
                return Collections.emptySet();
            }

            @Override
            public Class getServiceInterface() {
                return compatibilityMode ? externalContext.getClass() : ConfigurableWebApplicationContext.class;
            }

            @Override
            public String getServiceScope() {
                return ScopeConstants.DEFAULT;
            }

            @Override
            public boolean isEagerLoad() {
                return false;
            }
        };

        services.put(SERVICE_ID, applicationContextServiceDef);
    }

    private void addServiceDefsForSpringBeans(ApplicationContext context) {
        ConfigurableListableBeanFactory beanFactory = null;
        if (context instanceof ConfigurableApplicationContext) {
            beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
        }

        for (final String beanName : context.getBeanDefinitionNames()) {
            boolean isAbstract = false;
            if (beanFactory != null) {
                isAbstract = beanFactory.getBeanDefinition(beanName).isAbstract();
            }

            if (!isAbstract) {
                String trueName = beanName.startsWith("&") ? beanName.substring(1) : beanName;

                services.put(trueName, new SpringBeanServiceDef(trueName, context));
            }
        }
    }

    private ObjectCreator constructObjectCreatorForApplicationContext(final ServiceBuilderResources resources,
            @Primary ApplicationContextCustomizer customizer) {
        final CustomizingContextLoader loader = new CustomizingContextLoader(customizer);

        final Runnable shutdownListener = () -> loader.closeWebApplicationContext(servletContext);

        final RegistryShutdownHub shutdownHub = resources.getService(RegistryShutdownHub.class);

        return new ObjectCreator() {
            @Override
            public Object createObject() {
                return resources.getTracker().invoke("Creating Spring ApplicationContext via ContextLoader",
                        (Invokable<Object>) () -> {
                            resources.getLogger().info(
                                    String.format("Starting Spring (version %s)", SpringVersion.getVersion()));

                            WebApplicationContext context = loader.initWebApplicationContext(servletContext);

                            shutdownHub.addRegistryShutdownListener(shutdownListener);

                            applicationContextCreated.set(true);

                            return context;
                        });
            }

            @Override
            public String toString() {
                return "ObjectCreator for Spring ApplicationContext";
            }
        };
    }

    @Override
    public Class getBuilderClass() {
        return null;
    }

    /**
     * Returns a contribution, "SpringBean", to the MasterObjectProvider service. It is ordered
     * after the built-in
     * contributions.
     */
    @Override
    public Set<ContributionDef> getContributionDefs() {
        ContributionDef def = createContributionToMasterObjectProvider();

        return CollectionFactory.newSet(def);
    }

    private ContributionDef createContributionToMasterObjectProvider() {

        return new AbstractContributionDef() {
            @Override
            public String getServiceId() {
                return "MasterObjectProvider";
            }

            @Override
            public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources,
                    OrderedConfiguration configuration) {
                final OperationTracker tracker = resources.getTracker();

                final ApplicationContext context = resources.getService(SERVICE_ID, ApplicationContext.class);

                //region CUSTOMIZATION
                final ObjectProvider springBeanProvider = new ObjectProvider() {
                    public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider,
                            ObjectLocator locator) {

                        try {
                            T bean = context.getBean(objectType);
                            if (!objectType.isInterface()) {
                                return bean;
                            }
                            // We proxify here because Tapestry calls toString method on proxy, which realizes the underlying service, with scope issues
                            return (T) Proxy.newProxyInstance(objectType.getClassLoader(),
                                    new Class<?>[] { objectType }, new AbstractInvocationHandler() {
                                        @Override
                                        protected Object handleInvocation(Object proxy, Method method,
                                                Object[] args) throws Throwable {
                                            String methodName = method.getName();
                                            if (methodName.equals("equals")) {
                                                // Only consider equal when proxies are identical.
                                                return (proxy == args[0]);
                                            }
                                            if (methodName.equals("hashCode")) {
                                                // Use hashCode of proxy.
                                                return System.identityHashCode(proxy);
                                            }
                                            if (methodName.equals("toString")) {
                                                return "Current Spring " + objectType.getSimpleName();
                                            }
                                            try {
                                                return method.invoke(bean, args);
                                            } catch (InvocationTargetException e) {
                                                throw e.getCause();
                                            }
                                        }
                                    });

                        } catch (NoUniqueBeanDefinitionException e) {
                            String message = String.format(
                                    "Spring context contains %d beans assignable to type %s.",
                                    e.getNumberOfBeansFound(), PlasticUtils.toTypeName(objectType));

                            throw new IllegalArgumentException(message, e);
                        } catch (NoSuchBeanDefinitionException e) {
                            return null;
                        }
                    }
                };
                //endregion

                final ObjectProvider springBeanProviderInvoker = new ObjectProvider() {
                    @Override
                    public <T> T provide(final Class<T> objectType, final AnnotationProvider annotationProvider,
                            final ObjectLocator locator) {
                        return tracker.invoke("Resolving dependency by searching Spring ApplicationContext",
                                () -> springBeanProvider.provide(objectType, annotationProvider, locator));
                    }
                };

                ObjectProvider outerCheck = new ObjectProvider() {
                    @Override
                    public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider,
                            ObjectLocator locator) {
                        // I think the following line is the only reason we put the
                        // SpringBeanProvider here,
                        // rather than in SpringModule.

                        if (!applicationContextCreated.get())
                            return null;

                        return springBeanProviderInvoker.provide(objectType, annotationProvider, locator);
                    }
                };

                configuration.add("SpringBean", outerCheck, "after:AnnotationBasedContributions",
                        "after:ServiceOverride");
            }
        };
    }

    /**
     * Returns an empty set.
     */
    @Override
    public Set<DecoratorDef> getDecoratorDefs() {
        return Collections.emptySet();
    }

    @Override
    public String getLoggerName() {
        return SpringModuleDef.class.getName();
    }

    @Override
    public ServiceDef getServiceDef(String serviceId) {
        return services.get(serviceId);
    }

    @Override
    public Set<String> getServiceIds() {
        return services.keySet();
    }
}