001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: VaadinApplicationScope.java 143 2011-10-12 14:50:00Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.vaadin;
009    
010    import java.util.HashMap;
011    
012    import org.springframework.beans.factory.ObjectFactory;
013    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
014    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
015    import org.springframework.beans.factory.config.Scope;
016    
017    /**
018     * A Spring custom {@link Scope} for Vaadin applications.
019     *
020     * <p>
021     * This works for applications that subclass {@link ContextApplication}; objects will be scoped to each
022     * {@link ContextApplication} instance. Spring {@linkplain org.springframework.beans.factory.DisposableBean#destroy destroy-methods}
023     * will be invoked when the {@link ContextApplication} is closed.
024     * </p>
025     *
026     * <p>
027     * To enable this scope, simply add this bean to your application context as a singleton (it will register itself):
028     * <blockquote><pre>
029     *  &lt;!-- Enable the "vaadinApplication" custom scope --&gt;
030     *  &lt;bean class="org.dellroad.stuff.vaadin.VaadinApplicationScope"/&gt;
031     * </pre></blockquote>
032     * Then declare scoped beans normally using the scope name {@code "vaadinApplication"}.
033     * </p>
034     */
035    public class VaadinApplicationScope implements Scope, BeanFactoryPostProcessor, ContextApplication.CloseListener {
036    
037        /**
038         * Key to the current application instance. For use by {@link #resolveContextualObject}.
039         */
040        public static final String APPLICATION_KEY = "application";
041    
042        /**
043         * The name of this scope (i.e., <code>{@value}</code>).
044         */
045        public static final String SCOPE_NAME = "vaadinApplication";
046    
047        private final HashMap<ContextApplication, ApplicationBeanHolder> beanHolders
048          = new HashMap<ContextApplication, ApplicationBeanHolder>();
049    
050    // BeanFactoryPostProcessor methods
051    
052        @Override
053        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
054            beanFactory.registerScope(VaadinApplicationScope.SCOPE_NAME, this);
055        }
056    
057    // ContextApplication.CloseListener methods
058    
059        @Override
060        public void applicationClosed(ContextApplication.CloseEvent closeEvent) {
061            ApplicationBeanHolder beanHolder;
062            synchronized (this) {
063                beanHolder = this.beanHolders.remove(closeEvent.getContextApplication());
064            }
065            if (beanHolder != null)
066                beanHolder.close();
067        }
068    
069    // Scope methods
070    
071        @Override
072        public synchronized Object get(String name, ObjectFactory<?> objectFactory) {
073            return this.getApplicationBeanHolder(true).getBean(name, objectFactory);
074        }
075    
076        @Override
077        public synchronized Object remove(String name) {
078            ApplicationBeanHolder beanHolder = this.getApplicationBeanHolder(false);
079            return beanHolder != null ? beanHolder.remove(name) : null;
080        }
081    
082        @Override
083        public synchronized void registerDestructionCallback(String name, Runnable callback) {
084            this.getApplicationBeanHolder(true).registerDestructionCallback(name, callback);
085        }
086    
087        @Override
088        public String getConversationId() {
089            ContextApplication application = ContextApplication.currentApplication();
090            if (application == null)
091                return null;
092            return application.getClass().getName() + "@" + System.identityHashCode(application);
093        }
094    
095        @Override
096        public Object resolveContextualObject(String key) {
097            if (APPLICATION_KEY.equals(key))
098                return ContextApplication.currentApplication();
099            return null;
100        }
101    
102    // Internal methods
103    
104        private synchronized ApplicationBeanHolder getApplicationBeanHolder(boolean create) {
105            ContextApplication application = ContextApplication.get();
106            application.addListener(this);
107            ApplicationBeanHolder beanHolder = this.beanHolders.get(application);
108            if (beanHolder == null && create) {
109                beanHolder = new ApplicationBeanHolder(application);
110                this.beanHolders.put(application, beanHolder);
111            }
112            return beanHolder;
113        }
114    
115    // Bean holder class corresponding to a single Application instance
116    
117        private static class ApplicationBeanHolder {
118    
119            private final HashMap<String, Object> beans = new HashMap<String, Object>();
120            private final HashMap<String, Runnable> destructionCallbacks = new HashMap<String, Runnable>();
121            private final ContextApplication application;
122    
123            public ApplicationBeanHolder(ContextApplication application) {
124                this.application = application;
125            }
126    
127            public Object getBean(String name, ObjectFactory<?> objectFactory) {
128                Object bean = this.beans.get(name);
129                if (bean == null) {
130                    bean = objectFactory.getObject();
131                    this.beans.put(name, bean);
132                }
133                return bean;
134            }
135    
136            public Object remove(String name) {
137                this.destructionCallbacks.remove(name);
138                return this.beans.remove(name);
139            }
140    
141            public void registerDestructionCallback(String name, Runnable callback) {
142                this.destructionCallbacks.put(name, callback);
143            }
144    
145            public void close() {
146                for (Runnable callback : this.destructionCallbacks.values())
147                    callback.run();
148                this.beans.clear();
149                this.destructionCallbacks.clear();
150            }
151    
152            public boolean isEmpty() {
153                return this.beans.isEmpty();
154            }
155        }
156    }
157