001
002 /*
003 * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004 *
005 * $Id: SpringContextApplication.java 284 2012-02-17 02:50:50Z archie.cobbs $
006 */
007
008 package org.dellroad.stuff.vaadin;
009
010 import java.io.IOException;
011 import java.io.ObjectInputStream;
012 import java.io.Serializable;
013 import java.util.concurrent.atomic.AtomicLong;
014
015 import javax.servlet.ServletContext;
016 import javax.servlet.http.HttpServletRequest;
017
018 import org.springframework.context.ApplicationContext;
019 import org.springframework.context.ApplicationListener;
020 import org.springframework.context.event.ContextRefreshedEvent;
021 import org.springframework.context.event.SourceFilteringListener;
022 import org.springframework.web.context.ConfigurableWebApplicationContext;
023 import org.springframework.web.context.ContextLoader;
024 import org.springframework.web.context.WebApplicationContext;
025 import org.springframework.web.context.support.WebApplicationContextUtils;
026 import org.springframework.web.context.support.XmlWebApplicationContext;
027
028 /**
029 * Vaadin application implementation that manages an associated Spring {@link WebApplicationContext}.
030 *
031 * <h3>Overview</h3>
032 *
033 * <p>
034 * Each Vaadin application instance is given its own Spring application context, and all such
035 * application contexts share the same parent context, which is the one associated with the overal servlet web context
036 * (i.e., the one created by Spring's {@link org.springframework.web.context.ContextLoaderListener ContextLoaderListener}).
037 * A context is created when a new Vaadin application instance is initialized, and destroyed when it is closed.
038 * </p>
039 *
040 * <p>
041 * This setup is analogous to how Spring's {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
042 * creates per-servlet application contexts that are children of the overall servlet web context.
043 * </p>
044 *
045 * <p>
046 * For each Vaadin application {@code com.example.FooApplication} that subclasses this class, there should exist an XML
047 * file named {@code FooApplication.xml} in the {@code WEB-INF/} directory that defines the per-Vaadin application Spring
048 * application context (this naming scheme {@linkplain #getApplicationName can be overriden}).
049 * </p>
050 *
051 * <h3>Application as Bean</h3>
052 *
053 * <p>
054 * This {@link SpringContextApplication} instance can itself be exposed in and configured by the associated Spring
055 * application context by using a bean definition invoking the static factory method {@link ContextApplication#get}:
056 * <blockquote><pre>
057 * <bean id="myVaadinApplication" class="org.dellroad.stuff.vaadin.ContextApplication" factory-method="get"/>
058 * </pre></blockquote>
059 * Note however that Spring will autowire this bean based on the type {@link ContextApplication} rather than it's actual type.
060 * To make Spring aware of your Vaadin application's actual type, you can add a custom factory method to your application class:
061 * <blockquote><pre>
062 * public class MyVaadinApplication extends SpringContextApplication {
063
064 * public MyApplication get() {
065 * return ContextApplication.get(MyApplication.class);
066 * }
067 * }
068 *
069 * <bean id="myVaadinApplication" class="com.example.MyApplication" factory-method="get"/>
070 * </pre></blockquote>
071 * </p>
072 *
073 * <h3><code>@VaadinConfigurable</code> Beans</h3>
074 *
075 * <p>
076 * It is also possible to configure beans outside of this application context using AOP, so that any invocation of
077 * {@code new FooBar()}, where the class {@code FooBar} is marked {@link VaadinConfigurable @VaadinConfigurable},
078 * will automagically cause the new {@code FooBar} object to be configured by the application context associated with
079 * the {@linkplain ContextApplication#get() currently running application instance}. In effect, this does for
080 * Vaadin application beans what Spring's {@link org.springframework.beans.factory.annotation.Configurable @Configurable}
081 * does for regular beans.
082 * </p>
083 *
084 * <p>
085 * Note however that Spring {@linkplain org.springframework.beans.factory.DisposableBean#destroy destroy methods}
086 * will not be invoked on application close for these beans, since their lifecycle is controlled outside of the
087 * Spring application context (this is also the case with
088 * {@link org.springframework.beans.factory.annotation.Configurable @Configurable} beans). Instead, these beans
089 * can register as a {@link ContextApplication.CloseListener} for shutdown notification.
090 * </p>
091 *
092 * <p>
093 * For the this annotation to do anything, {@link VaadinConfigurable @VaadinConfigurable} classes must be woven
094 * (either at build time or runtime) using the
095 * <a href="http://www.eclipse.org/aspectj/doc/released/faq.php#compiler">AspectJ compiler</a> with the
096 * {@code VaadinConfigurableAspect} aspect (included in the <code>dellroad-stuff</code> JAR file).
097 * </p>
098 *
099 * @see ContextApplication#get
100 * @see ContextApplicationFactoryBean
101 * @see <a href="https://github.com/archiecobbs/dellroad-stuff-vaadin-spring-demo3">Example Code on GitHub</a>
102 */
103 @SuppressWarnings("serial")
104 public abstract class SpringContextApplication extends ContextApplication {
105
106 private static final AtomicLong UNIQUE_INDEX = new AtomicLong();
107
108 private transient ConfigurableWebApplicationContext context;
109
110 /**
111 * Get this instance's associated Spring application context.
112 */
113 public ConfigurableWebApplicationContext getApplicationContext() {
114 return this.context;
115 }
116
117 /**
118 * Get the {@link SpringContextApplication} instance associated with the current thread or throw an exception if there is none.
119 *
120 * <p>
121 * Works just like {@link ContextApplication#get()} but returns this narrower type.
122 * </p>
123 *
124 * @return the {@link SpringContextApplication} associated with the current thread
125 * @throws IllegalStateException if the current thread is not servicing a Vaadin web request
126 * or the current Vaadin {@link com.vaadin.Application} is not a {@link SpringContextApplication}
127 */
128 public static SpringContextApplication get() {
129 return ContextApplication.get(SpringContextApplication.class);
130 }
131
132 /**
133 * Initializes the associated {@link ConfigurableWebApplicationContext}.
134 */
135 protected final void initApplication() {
136
137 // Load the context
138 this.loadContext();
139
140 // Initialize subclass
141 this.initSpringApplication(context);
142 }
143
144 /**
145 * Initialize the application. Sub-classes of {@link SpringContextApplication} must implement this method.
146 *
147 * @param context the associated {@link WebApplicationContext} just created and refreshed
148 * @see #destroySpringApplication
149 */
150 protected abstract void initSpringApplication(ConfigurableWebApplicationContext context);
151
152 /**
153 * Perform any application-specific shutdown work. This will be invoked at shutdown after this Vaadin application and the
154 * associated {@link WebApplicationContext} have both been closed.
155 *
156 * <p>
157 * The implementation in {@link SpringContextApplication} does nothing. Subclasses may override as necessary.
158 * </p>
159 *
160 * <p>
161 * Note that if a {@link SpringContextApplication} instance is exposed in the application context and configured
162 * with a Spring {@linkplain org.springframework.beans.factory.DisposableBean#destroy destroy method}, then that
163 * method will also be invoked when the application is closed. In such cases overriding this method is not necessary.
164 * </p>
165 *
166 * @see #initSpringApplication
167 */
168 protected void destroySpringApplication() {
169 }
170
171 /**
172 * Post-process the given {@link WebApplicationContext} after initial creation but before the initial
173 * {@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()}.
174 *
175 * <p>
176 * The implementation in {@link SpringContextApplication} does nothing. Subclasses may override as necessary.
177 * </p>
178 *
179 * @param context the associated {@link WebApplicationContext} just refreshed
180 * @see #onRefresh
181 * @see ConfigurableWebApplicationContext#refresh()
182 */
183 protected void postProcessWebApplicationContext(ConfigurableWebApplicationContext context) {
184 }
185
186 /**
187 * Perform any application-specific work after a successful application context refresh.
188 *
189 * <p>
190 * The implementation in {@link SpringContextApplication} does nothing. Subclasses may override as necessary.
191 * </p>
192 *
193 * @param context the associated {@link WebApplicationContext} just refreshed
194 * @see #postProcessWebApplicationContext
195 * @see org.springframework.context.ConfigurableApplicationContext#refresh
196 */
197 protected void onRefresh(ApplicationContext context) {
198 }
199
200 /**
201 * Get the name for this application. This is used as the name of the XML file in {@code WEB-INF/} that
202 * defines the Spring application context associated with this instance.
203 *
204 * <p>
205 * The implementation in {@link SpringContextApplication} returns this instance's class'
206 * {@linkplain Class#getSimpleName simple name}.
207 * </p>
208 */
209 protected String getApplicationName() {
210 return this.getClass().getSimpleName();
211 }
212
213 // ApplicationContext setup
214
215 private void loadContext() {
216
217 // Logging
218 this.log.info("loading application context for Vaadin application " + this.getApplicationName());
219
220 // Sanity check
221 if (this.context != null)
222 throw new IllegalStateException("context already loaded");
223
224 // Find the application context associated with the servlet; it will be the parent
225 ServletContext servletContext;
226 HttpServletRequest request = ContextApplication.currentRequest();
227 try {
228 // getServletContext() is a servlet AIP 3.0 method, so don't freak out if it's not there
229 servletContext = (ServletContext)HttpServletRequest.class.getMethod("getServletContext").invoke(request);
230 } catch (Exception e) {
231 servletContext = ContextLoader.getCurrentWebApplicationContext().getServletContext();
232 }
233 WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(servletContext);
234
235 // Create and configure a new application context for this Application instance
236 this.context = new XmlWebApplicationContext();
237 this.context.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX
238 + servletContext.getContextPath() + "/" + this.getApplicationName() + "-"
239 + SpringContextApplication.UNIQUE_INDEX.incrementAndGet());
240 this.context.setParent(parent);
241 this.context.setServletContext(servletContext);
242 //context.setServletConfig(??);
243 this.context.setNamespace(this.getApplicationName());
244
245 // Register listener so we can notify subclass on refresh events
246 this.context.addApplicationListener(new SourceFilteringListener(this.context, new RefreshListener()));
247
248 // Invoke any subclass setup
249 this.postProcessWebApplicationContext(context);
250
251 // Refresh context
252 this.context.refresh();
253
254 // Get notified of application shutdown so we can shut down the context as well
255 this.addListener(new ContextCloseListener());
256 }
257
258 // Serialization
259
260 private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
261 input.defaultReadObject();
262 this.loadContext();
263 }
264
265 // Nested classes
266
267 // My refresh listener
268 private class RefreshListener implements ApplicationListener<ContextRefreshedEvent>, Serializable {
269 @Override
270 public void onApplicationEvent(ContextRefreshedEvent event) {
271 SpringContextApplication.this.onRefresh(event.getApplicationContext());
272 }
273 }
274
275 // My close listener
276 private class ContextCloseListener implements CloseListener, Serializable {
277 @Override
278 public void applicationClosed(CloseEvent closeEvent) {
279 SpringContextApplication.this.log.info("closing application context associated with Vaadin application "
280 + SpringContextApplication.this.getApplicationName());
281 SpringContextApplication.this.context.close();
282 SpringContextApplication.this.destroySpringApplication();
283 }
284 }
285 }
286