Java tutorial
// 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(); } }