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 }