001 // Copyright 2007, 2008, 2009, 2010, 2011 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.internal.spring; 016 017 import org.apache.tapestry5.internal.AbstractContributionDef; 018 import org.apache.tapestry5.ioc.*; 019 import org.apache.tapestry5.ioc.annotations.Primary; 020 import org.apache.tapestry5.ioc.def.ContributionDef; 021 import org.apache.tapestry5.ioc.def.DecoratorDef; 022 import org.apache.tapestry5.ioc.def.ModuleDef; 023 import org.apache.tapestry5.ioc.def.ServiceDef; 024 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 025 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 026 import org.apache.tapestry5.ioc.services.ClassFabUtils; 027 import org.apache.tapestry5.ioc.services.RegistryShutdownHub; 028 import org.apache.tapestry5.spring.ApplicationContextCustomizer; 029 import org.apache.tapestry5.spring.SpringConstants; 030 import org.springframework.beans.factory.BeanFactoryUtils; 031 import org.springframework.context.ApplicationContext; 032 import org.springframework.core.SpringVersion; 033 import org.springframework.web.context.ConfigurableWebApplicationContext; 034 import org.springframework.web.context.WebApplicationContext; 035 import org.springframework.web.context.support.WebApplicationContextUtils; 036 037 import javax.servlet.ServletContext; 038 import java.util.Collections; 039 import java.util.Map; 040 import java.util.Set; 041 import java.util.concurrent.atomic.AtomicBoolean; 042 043 /** 044 * A wrapper that converts a Spring {@link ApplicationContext} into a set of service definitions, 045 * compatible with 046 * Tapestry 5 IoC, for the beans defined in the context, as well as the context itself. 047 */ 048 public class SpringModuleDef implements ModuleDef 049 { 050 static final String SERVICE_ID = "ApplicationContext"; 051 052 private final Map<String, ServiceDef> services = CollectionFactory.newMap(); 053 054 private final boolean compatibilityMode; 055 056 private final AtomicBoolean applicationContextCreated = new AtomicBoolean(false); 057 058 private final ServletContext servletContext; 059 060 private ApplicationContext locateExternalContext() 061 { 062 ApplicationContext context = locateApplicationContext(servletContext); 063 064 applicationContextCreated.set(true); 065 066 return context; 067 } 068 069 /** 070 * Invoked to obtain the Spring ApplicationContext, presumably stored in the ServletContext. 071 * This method is only used in Tapestry 5.0 compatibility mode (in Tapestry 5.1 and above, 072 * the default is for Tapestry to <em>create</em> the ApplicationContext). 073 * 074 * @param servletContext used to locate the ApplicationContext 075 * @return the ApplicationContext itself 076 * @throws RuntimeException if the ApplicationContext could not be located or is otherwise invalid 077 * @since 5.2.0 078 */ 079 protected ApplicationContext locateApplicationContext(ServletContext servletContext) 080 { 081 ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext); 082 083 if (context == null) 084 { 085 throw new NullPointerException( 086 String 087 .format( 088 "No Spring ApplicationContext stored in the ServletContext as attribute '%s'. " 089 + "You should either re-enable Tapestry as the creator of the ApplicationContext, or " 090 + "add a Spring ContextLoaderListener to web.xml.", 091 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)); 092 } 093 094 return context; 095 } 096 097 public SpringModuleDef(ServletContext servletContext) 098 { 099 this.servletContext = servletContext; 100 101 compatibilityMode = Boolean.parseBoolean(servletContext 102 .getInitParameter(SpringConstants.USE_EXTERNAL_SPRING_CONTEXT)); 103 104 final ApplicationContext externalContext = compatibilityMode ? locateExternalContext() 105 : null; 106 107 if (compatibilityMode) 108 addServiceDefsForSpringBeans(externalContext); 109 110 ServiceDef applicationContextServiceDef = new ServiceDef() 111 { 112 public ObjectCreator createServiceCreator(final ServiceBuilderResources resources) 113 { 114 if (compatibilityMode) 115 return new StaticObjectCreator(externalContext, 116 "externally configured Spring ApplicationContext"); 117 118 ApplicationContextCustomizer customizer = resources.getService( 119 "ApplicationContextCustomizer", ApplicationContextCustomizer.class); 120 121 return constructObjectCreatorForApplicationContext(resources, customizer); 122 } 123 124 public String getServiceId() 125 { 126 return SERVICE_ID; 127 } 128 129 public Set<Class> getMarkers() 130 { 131 return Collections.emptySet(); 132 } 133 134 public Class getServiceInterface() 135 { 136 return compatibilityMode ? externalContext.getClass() 137 : ConfigurableWebApplicationContext.class; 138 } 139 140 public String getServiceScope() 141 { 142 return ScopeConstants.DEFAULT; 143 } 144 145 public boolean isEagerLoad() 146 { 147 return false; 148 } 149 }; 150 151 services.put(SERVICE_ID, applicationContextServiceDef); 152 } 153 154 private void addServiceDefsForSpringBeans(ApplicationContext context) 155 { 156 for (final String beanName : context.getBeanDefinitionNames()) 157 { 158 String trueName = beanName.startsWith("&") ? beanName.substring(1) : beanName; 159 160 services.put(trueName, new SpringBeanServiceDef(trueName, context)); 161 } 162 } 163 164 private ObjectCreator constructObjectCreatorForApplicationContext( 165 final ServiceBuilderResources resources, @Primary 166 ApplicationContextCustomizer customizer) 167 { 168 final CustomizingContextLoader loader = new CustomizingContextLoader(customizer); 169 170 final Runnable shutdownListener = new Runnable() 171 { 172 public void run() 173 { 174 loader.closeWebApplicationContext(servletContext); 175 } 176 }; 177 178 final RegistryShutdownHub shutdownHub = resources.getService(RegistryShutdownHub.class); 179 180 return new ObjectCreator() 181 { 182 public Object createObject() 183 { 184 return resources.getTracker().invoke( 185 "Creating Spring ApplicationContext via ContextLoader", 186 new Invokable<Object>() 187 { 188 public Object invoke() 189 { 190 resources.getLogger().info( 191 String.format("Starting Spring (version %s)", SpringVersion 192 .getVersion())); 193 194 WebApplicationContext context = loader 195 .initWebApplicationContext(servletContext); 196 197 shutdownHub.addRegistryShutdownListener(shutdownListener); 198 199 applicationContextCreated.set(true); 200 201 return context; 202 } 203 }); 204 } 205 206 @Override 207 public String toString() 208 { 209 return "ObjectCreator for Spring ApplicationContext"; 210 } 211 }; 212 } 213 214 public Class getBuilderClass() 215 { 216 return null; 217 } 218 219 /** 220 * Returns a contribution, "SpringBean", to the MasterObjectProvider service. It is ordered 221 * after the built-in 222 * contributions. 223 */ 224 public Set<ContributionDef> getContributionDefs() 225 { 226 ContributionDef def = createContributionToMasterObjectProvider(); 227 228 return CollectionFactory.newSet(def); 229 } 230 231 private ContributionDef createContributionToMasterObjectProvider() 232 { 233 234 ContributionDef def = new AbstractContributionDef() 235 { 236 public String getServiceId() 237 { 238 return "MasterObjectProvider"; 239 } 240 241 @Override 242 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 243 OrderedConfiguration configuration) 244 { 245 final OperationTracker tracker = resources.getTracker(); 246 247 final ApplicationContext context = resources.getService(SERVICE_ID, 248 ApplicationContext.class); 249 250 final ObjectProvider springBeanProvider = new ObjectProvider() 251 { 252 public <T> T provide(Class<T> objectType, 253 AnnotationProvider annotationProvider, ObjectLocator locator) 254 { 255 256 Map beanMap = context.getBeansOfType(objectType); 257 258 switch (beanMap.size()) 259 { 260 case 0: 261 return null; 262 263 case 1: 264 265 Object bean = beanMap.values().iterator().next(); 266 267 return objectType.cast(bean); 268 269 default: 270 271 String message = String 272 .format( 273 "Spring context contains %d beans assignable to type %s: %s.", 274 beanMap.size(), ClassFabUtils 275 .toJavaClassName(objectType), InternalUtils 276 .joinSorted(beanMap.keySet())); 277 278 throw new IllegalArgumentException(message); 279 } 280 } 281 }; 282 283 final ObjectProvider springBeanProviderInvoker = new ObjectProvider() 284 { 285 public <T> T provide(final Class<T> objectType, 286 final AnnotationProvider annotationProvider, final ObjectLocator locator) 287 { 288 return tracker.invoke( 289 "Resolving dependency by searching Spring ApplicationContext", 290 new Invokable<T>() 291 { 292 public T invoke() 293 { 294 return springBeanProvider.provide(objectType, 295 annotationProvider, locator); 296 } 297 }); 298 } 299 }; 300 301 ObjectProvider outerCheck = new ObjectProvider() 302 { 303 public <T> T provide(Class<T> objectType, 304 AnnotationProvider annotationProvider, ObjectLocator locator) 305 { 306 // I think the following line is the only reason we put the 307 // SpringBeanProvider here, 308 // rather than in SpringModule. 309 310 if (!applicationContextCreated.get()) 311 return null; 312 313 return springBeanProviderInvoker.provide(objectType, annotationProvider, 314 locator); 315 } 316 }; 317 318 319 configuration.add("SpringBean", outerCheck, "after:AnnotationBasedContributions", "after:ServiceOverride"); 320 } 321 }; 322 323 return def; 324 } 325 326 /** 327 * Returns an empty set. 328 */ 329 public Set<DecoratorDef> getDecoratorDefs() 330 { 331 return Collections.emptySet(); 332 } 333 334 public String getLoggerName() 335 { 336 return SpringModuleDef.class.getName(); 337 } 338 339 public ServiceDef getServiceDef(String serviceId) 340 { 341 return services.get(serviceId); 342 } 343 344 public Set<String> getServiceIds() 345 { 346 return services.keySet(); 347 } 348 }