001 // Copyright 2006-2012 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.ioc.internal.services;
016
017 import org.apache.tapestry5.ioc.Invokable;
018 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019 import org.apache.tapestry5.ioc.internal.util.JDKUtils;
020 import org.apache.tapestry5.ioc.services.PerThreadValue;
021 import org.apache.tapestry5.ioc.services.PerthreadManager;
022 import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
023 import org.apache.tapestry5.ioc.services.ThreadCleanupListener;
024 import org.slf4j.Logger;
025
026 import java.util.List;
027 import java.util.Map;
028 import java.util.concurrent.atomic.AtomicBoolean;
029 import java.util.concurrent.atomic.AtomicInteger;
030 import java.util.concurrent.locks.Lock;
031
032 @SuppressWarnings("all")
033 public class PerthreadManagerImpl implements PerthreadManager
034 {
035 private final Lock lock = JDKUtils.createLockForThreadLocalCreation();
036
037 private final PerThreadValue<List<ThreadCleanupListener>> listenersValue;
038
039 private static class MapHolder extends ThreadLocal<Map>
040 {
041 @Override
042 protected Map initialValue()
043 {
044 return CollectionFactory.newMap();
045 }
046 }
047
048 private final Logger logger;
049
050 private final MapHolder holder = new MapHolder();
051
052 private final AtomicInteger uuidGenerator = new AtomicInteger();
053
054 private final AtomicBoolean shutdown = new AtomicBoolean();
055
056 public PerthreadManagerImpl(Logger logger)
057 {
058 this.logger = logger;
059
060 listenersValue = createValue();
061 }
062
063 public void registerForShutdown(RegistryShutdownHub hub)
064 {
065 hub.addRegistryShutdownListener(new Runnable()
066 {
067 public void run()
068 {
069 cleanup();
070 shutdown.set(true);
071 }
072 });
073 }
074
075 private Map getPerthreadMap()
076 {
077 // This is a degenerate case; it may not even exist; but if during registry shutdown somehow code executes
078 // that attempts to create new values or add new listeners, those go into a new map instance that is
079 // not referenced (and so immediately GCed).
080 if (shutdown.get())
081 {
082 return CollectionFactory.newMap();
083 }
084
085 lock.lock();
086
087 try
088 {
089 return holder.get();
090 } finally
091 {
092 lock.unlock();
093 }
094 }
095
096 private List<ThreadCleanupListener> getListeners()
097 {
098 List<ThreadCleanupListener> result = listenersValue.get();
099
100 if (result == null)
101 {
102 result = CollectionFactory.newList();
103 listenersValue.set(result);
104 }
105
106 return result;
107 }
108
109 public void addThreadCleanupListener(ThreadCleanupListener listener)
110 {
111 getListeners().add(listener);
112 }
113
114 /**
115 * Instructs the hub to notify all its listeners (for the current thread).
116 * It also discards its list of listeners.
117 */
118 public void cleanup()
119 {
120 List<ThreadCleanupListener> listeners = getListeners();
121
122 listenersValue.set(null);
123
124 for (ThreadCleanupListener listener : listeners)
125 {
126 try
127 {
128 listener.threadDidCleanup();
129 } catch (Exception ex)
130 {
131 logger.warn(ServiceMessages.threadCleanupError(listener, ex), ex);
132 }
133 }
134
135 // Listeners should not re-add themselves or store any per-thread state
136 // here, it will be lost.
137
138 try
139 {
140 lock.lock();
141
142 // Discard the per-thread map of values, including the key that stores
143 // the listeners. This means that if a listener attempts to register
144 // new listeners, the new listeners will not be triggered and will be
145 // released to the GC.
146
147 holder.remove();
148 } finally
149 {
150 lock.unlock();
151 }
152 }
153
154 private static Object NULL_VALUE = new Object();
155
156 <T> PerThreadValue<T> createValue(final Object key)
157 {
158 return new PerThreadValue<T>()
159 {
160 public T get()
161 {
162 return get(null);
163 }
164
165 public T get(T defaultValue)
166 {
167 Map map = getPerthreadMap();
168
169 if (map.containsKey(key))
170 {
171 Object storedValue = map.get(key);
172
173 if (storedValue == NULL_VALUE)
174 return null;
175
176 return (T) storedValue;
177 }
178
179 return defaultValue;
180 }
181
182 public T set(T newValue)
183 {
184 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
185
186 return newValue;
187 }
188
189 public boolean exists()
190 {
191 return getPerthreadMap().containsKey(key);
192 }
193 };
194 }
195
196 public <T> PerThreadValue<T> createValue()
197 {
198 return createValue(uuidGenerator.getAndIncrement());
199 }
200
201 public void run(Runnable runnable)
202 {
203 assert runnable != null;
204
205 try
206 {
207 runnable.run();
208 } finally
209 {
210 cleanup();
211 }
212 }
213
214 public <T> T invoke(Invokable<T> invokable)
215 {
216 try
217 {
218 return invokable.invoke();
219 } finally
220 {
221 cleanup();
222 }
223 }
224 }