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     *  &lt;bean id="myVaadinApplication" class="org.dellroad.stuff.vaadin.ContextApplication" factory-method="get"/&gt;
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     *  &lt;bean id="myVaadinApplication" class="com.example.MyApplication" factory-method="get"/&gt;
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