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 015package org.apache.tapestry5.internal.spring; 016 017import org.apache.tapestry5.internal.AbstractContributionDef; 018import org.apache.tapestry5.ioc.*; 019import org.apache.tapestry5.ioc.annotations.Primary; 020import org.apache.tapestry5.ioc.def.ContributionDef; 021import org.apache.tapestry5.ioc.def.DecoratorDef; 022import org.apache.tapestry5.ioc.def.ModuleDef; 023import org.apache.tapestry5.ioc.def.ServiceDef; 024import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 025import org.apache.tapestry5.ioc.internal.util.InternalUtils; 026import org.apache.tapestry5.ioc.services.RegistryShutdownHub; 027import org.apache.tapestry5.plastic.PlasticUtils; 028import org.apache.tapestry5.spring.ApplicationContextCustomizer; 029import org.apache.tapestry5.spring.SpringConstants; 030import org.springframework.beans.factory.BeanFactoryUtils; 031import org.springframework.context.ApplicationContext; 032import org.springframework.core.SpringVersion; 033import org.springframework.web.context.ConfigurableWebApplicationContext; 034import org.springframework.web.context.WebApplicationContext; 035import org.springframework.web.context.support.WebApplicationContextUtils; 036 037import javax.servlet.ServletContext; 038import java.util.Collections; 039import java.util.Map; 040import java.util.Set; 041import 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 */ 048public 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 @Override 113 public ObjectCreator createServiceCreator(final ServiceBuilderResources resources) 114 { 115 if (compatibilityMode) 116 return new StaticObjectCreator(externalContext, 117 "externally configured Spring ApplicationContext"); 118 119 ApplicationContextCustomizer customizer = resources.getService( 120 "ApplicationContextCustomizer", ApplicationContextCustomizer.class); 121 122 return constructObjectCreatorForApplicationContext(resources, customizer); 123 } 124 125 @Override 126 public String getServiceId() 127 { 128 return SERVICE_ID; 129 } 130 131 @Override 132 public Set<Class> getMarkers() 133 { 134 return Collections.emptySet(); 135 } 136 137 @Override 138 public Class getServiceInterface() 139 { 140 return compatibilityMode ? externalContext.getClass() 141 : ConfigurableWebApplicationContext.class; 142 } 143 144 @Override 145 public String getServiceScope() 146 { 147 return ScopeConstants.DEFAULT; 148 } 149 150 @Override 151 public boolean isEagerLoad() 152 { 153 return false; 154 } 155 }; 156 157 services.put(SERVICE_ID, applicationContextServiceDef); 158 } 159 160 private void addServiceDefsForSpringBeans(ApplicationContext context) 161 { 162 for (final String beanName : context.getBeanDefinitionNames()) 163 { 164 String trueName = beanName.startsWith("&") ? beanName.substring(1) : beanName; 165 166 services.put(trueName, new SpringBeanServiceDef(trueName, context)); 167 } 168 } 169 170 private ObjectCreator constructObjectCreatorForApplicationContext( 171 final ServiceBuilderResources resources, @Primary 172 ApplicationContextCustomizer customizer) 173 { 174 final CustomizingContextLoader loader = new CustomizingContextLoader(customizer); 175 176 final Runnable shutdownListener = new Runnable() 177 { 178 @Override 179 public void run() 180 { 181 loader.closeWebApplicationContext(servletContext); 182 } 183 }; 184 185 final RegistryShutdownHub shutdownHub = resources.getService(RegistryShutdownHub.class); 186 187 return new ObjectCreator() 188 { 189 @Override 190 public Object createObject() 191 { 192 return resources.getTracker().invoke( 193 "Creating Spring ApplicationContext via ContextLoader", 194 new Invokable<Object>() 195 { 196 @Override 197 public Object invoke() 198 { 199 resources.getLogger().info( 200 String.format("Starting Spring (version %s)", SpringVersion 201 .getVersion())); 202 203 WebApplicationContext context = loader 204 .initWebApplicationContext(servletContext); 205 206 shutdownHub.addRegistryShutdownListener(shutdownListener); 207 208 applicationContextCreated.set(true); 209 210 return context; 211 } 212 }); 213 } 214 215 @Override 216 public String toString() 217 { 218 return "ObjectCreator for Spring ApplicationContext"; 219 } 220 }; 221 } 222 223 @Override 224 public Class getBuilderClass() 225 { 226 return null; 227 } 228 229 /** 230 * Returns a contribution, "SpringBean", to the MasterObjectProvider service. It is ordered 231 * after the built-in 232 * contributions. 233 */ 234 @Override 235 public Set<ContributionDef> getContributionDefs() 236 { 237 ContributionDef def = createContributionToMasterObjectProvider(); 238 239 return CollectionFactory.newSet(def); 240 } 241 242 private ContributionDef createContributionToMasterObjectProvider() 243 { 244 245 ContributionDef def = new AbstractContributionDef() 246 { 247 @Override 248 public String getServiceId() 249 { 250 return "MasterObjectProvider"; 251 } 252 253 @Override 254 public void contribute(ModuleBuilderSource moduleSource, ServiceResources resources, 255 OrderedConfiguration configuration) 256 { 257 final OperationTracker tracker = resources.getTracker(); 258 259 final ApplicationContext context = resources.getService(SERVICE_ID, 260 ApplicationContext.class); 261 262 final ObjectProvider springBeanProvider = new ObjectProvider() 263 { 264 @Override 265 public <T> T provide(Class<T> objectType, 266 AnnotationProvider annotationProvider, ObjectLocator locator) 267 { 268 269 Map beanMap = context.getBeansOfType(objectType); 270 271 switch (beanMap.size()) 272 { 273 case 0: 274 return null; 275 276 case 1: 277 278 Object bean = beanMap.values().iterator().next(); 279 280 return objectType.cast(bean); 281 282 default: 283 284 String message = String 285 .format( 286 "Spring context contains %d beans assignable to type %s: %s.", 287 beanMap.size(), PlasticUtils.toTypeName(objectType), InternalUtils 288 .joinSorted(beanMap.keySet())); 289 290 throw new IllegalArgumentException(message); 291 } 292 } 293 }; 294 295 final ObjectProvider springBeanProviderInvoker = new ObjectProvider() 296 { 297 @Override 298 public <T> T provide(final Class<T> objectType, 299 final AnnotationProvider annotationProvider, final ObjectLocator locator) 300 { 301 return tracker.invoke( 302 "Resolving dependency by searching Spring ApplicationContext", 303 new Invokable<T>() 304 { 305 @Override 306 public T invoke() 307 { 308 return springBeanProvider.provide(objectType, 309 annotationProvider, locator); 310 } 311 }); 312 } 313 }; 314 315 ObjectProvider outerCheck = new ObjectProvider() 316 { 317 @Override 318 public <T> T provide(Class<T> objectType, 319 AnnotationProvider annotationProvider, ObjectLocator locator) 320 { 321 // I think the following line is the only reason we put the 322 // SpringBeanProvider here, 323 // rather than in SpringModule. 324 325 if (!applicationContextCreated.get()) 326 return null; 327 328 return springBeanProviderInvoker.provide(objectType, annotationProvider, 329 locator); 330 } 331 }; 332 333 334 configuration.add("SpringBean", outerCheck, "after:AnnotationBasedContributions", "after:ServiceOverride"); 335 } 336 }; 337 338 return def; 339 } 340 341 /** 342 * Returns an empty set. 343 */ 344 @Override 345 public Set<DecoratorDef> getDecoratorDefs() 346 { 347 return Collections.emptySet(); 348 } 349 350 @Override 351 public String getLoggerName() 352 { 353 return SpringModuleDef.class.getName(); 354 } 355 356 @Override 357 public ServiceDef getServiceDef(String serviceId) 358 { 359 return services.get(serviceId); 360 } 361 362 @Override 363 public Set<String> getServiceIds() 364 { 365 return services.keySet(); 366 } 367}