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 * <!-- Enable the "vaadinApplication" custom scope -->
030 * <bean class="org.dellroad.stuff.vaadin.VaadinApplicationScope"/>
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