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 @Override
068 public void run()
069 {
070 cleanup();
071 shutdown.set(true);
072 }
073 });
074 }
075
076 private Map getPerthreadMap()
077 {
078 // This is a degenerate case; it may not even exist; but if during registry shutdown somehow code executes
079 // that attempts to create new values or add new listeners, those go into a new map instance that is
080 // not referenced (and so immediately GCed).
081 if (shutdown.get())
082 {
083 return CollectionFactory.newMap();
084 }
085
086 lock.lock();
087
088 try
089 {
090 return holder.get();
091 } finally
092 {
093 lock.unlock();
094 }
095 }
096
097 private List<ThreadCleanupListener> getListeners()
098 {
099 List<ThreadCleanupListener> result = listenersValue.get();
100
101 if (result == null)
102 {
103 result = CollectionFactory.newList();
104 listenersValue.set(result);
105 }
106
107 return result;
108 }
109
110 public void addThreadCleanupListener(ThreadCleanupListener listener)
111 {
112 getListeners().add(listener);
113 }
114
115 /**
116 * Instructs the hub to notify all its listeners (for the current thread).
117 * It also discards its list of listeners.
118 */
119 public void cleanup()
120 {
121 List<ThreadCleanupListener> listeners = getListeners();
122
123 listenersValue.set(null);
124
125 for (ThreadCleanupListener listener : listeners)
126 {
127 try
128 {
129 listener.threadDidCleanup();
130 } catch (Exception ex)
131 {
132 logger.warn(ServiceMessages.threadCleanupError(listener, ex), ex);
133 }
134 }
135
136 // Listeners should not re-add themselves or store any per-thread state
137 // here, it will be lost.
138
139 try
140 {
141 lock.lock();
142
143 // Discard the per-thread map of values, including the key that stores
144 // the listeners. This means that if a listener attempts to register
145 // new listeners, the new listeners will not be triggered and will be
146 // released to the GC.
147
148 holder.remove();
149 } finally
150 {
151 lock.unlock();
152 }
153 }
154
155 private static Object NULL_VALUE = new Object();
156
157 <T> PerThreadValue<T> createValue(final Object key)
158 {
159 return new PerThreadValue<T>()
160 {
161 public T get()
162 {
163 return get(null);
164 }
165
166 public T get(T defaultValue)
167 {
168 Map map = getPerthreadMap();
169
170 if (map.containsKey(key))
171 {
172 Object storedValue = map.get(key);
173
174 if (storedValue == NULL_VALUE)
175 return null;
176
177 return (T) storedValue;
178 }
179
180 return defaultValue;
181 }
182
183 public T set(T newValue)
184 {
185 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
186
187 return newValue;
188 }
189
190 public boolean exists()
191 {
192 return getPerthreadMap().containsKey(key);
193 }
194 };
195 }
196
197 public <T> PerThreadValue<T> createValue()
198 {
199 return createValue(uuidGenerator.getAndIncrement());
200 }
201
202 public void run(Runnable runnable)
203 {
204 assert runnable != null;
205
206 try
207 {
208 runnable.run();
209 } finally
210 {
211 cleanup();
212 }
213 }
214
215 public <T> T invoke(Invokable<T> invokable)
216 {
217 try
218 {
219 return invokable.invoke();
220 } finally
221 {
222 cleanup();
223 }
224 }
225 }