001// Licensed under the Apache License, Version 2.0 (the "License"); 002// you may not use this file except in compliance with the License. 003// You may obtain a copy of the License at 004// 005// http://www.apache.org/licenses/LICENSE-2.0 006// 007// Unless required by applicable law or agreed to in writing, software 008// distributed under the License is distributed on an "AS IS" BASIS, 009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 010// See the License for the specific language governing permissions and 011// limitations under the License. 012 013package org.apache.tapestry5.ioc.internal.services; 014 015import java.util.List; 016import java.util.Map; 017import java.util.concurrent.atomic.AtomicBoolean; 018import java.util.concurrent.atomic.AtomicInteger; 019 020import org.apache.tapestry5.commons.ObjectCreator; 021import org.apache.tapestry5.commons.util.CollectionFactory; 022import org.apache.tapestry5.ioc.Invokable; 023import org.apache.tapestry5.ioc.services.PerThreadValue; 024import org.apache.tapestry5.ioc.services.PerthreadManager; 025import org.apache.tapestry5.ioc.services.RegistryShutdownHub; 026import org.apache.tapestry5.ioc.services.ThreadCleanupListener; 027import org.slf4j.Logger; 028 029@SuppressWarnings("all") 030public class PerthreadManagerImpl implements PerthreadManager 031{ 032 private final PerThreadValue<List<Runnable>> callbacksValue; 033 034 private static class MapHolder extends ThreadLocal<Map> 035 { 036 @Override 037 protected Map initialValue() 038 { 039 return CollectionFactory.newMap(); 040 } 041 } 042 043 private final Logger logger; 044 045 private final MapHolder holder = new MapHolder(); 046 047 private final AtomicInteger uuidGenerator = new AtomicInteger(); 048 049 private volatile boolean shutdown = false; 050 051 public PerthreadManagerImpl(Logger logger) 052 { 053 this.logger = logger; 054 055 callbacksValue = createValue(); 056 } 057 058 public void registerForShutdown(RegistryShutdownHub hub) 059 { 060 hub.addRegistryShutdownListener(new Runnable() 061 { 062 @Override 063 public void run() 064 { 065 cleanup(); 066 shutdown = true; 067 } 068 }); 069 } 070 071 private Map getPerthreadMap() 072 { 073 // This is a degenerate case; it may not even exist; but if during registry shutdown somehow code executes 074 // that attempts to create new values or add new listeners, those go into a new map instance that is 075 // not referenced (and so immediately GCed). 076 if (shutdown) 077 { 078 return CollectionFactory.newMap(); 079 } 080 081 return holder.get(); 082 } 083 084 private List<Runnable> getCallbacks() 085 { 086 List<Runnable> result = callbacksValue.get(); 087 088 if (result == null) 089 { 090 result = CollectionFactory.newList(); 091 callbacksValue.set(result); 092 } 093 094 return result; 095 } 096 097 @Override 098 public void addThreadCleanupListener(final ThreadCleanupListener listener) 099 { 100 assert listener != null; 101 102 addThreadCleanupCallback(new Runnable() 103 { 104 @Override 105 public void run() 106 { 107 listener.threadDidCleanup(); 108 } 109 }); 110 } 111 112 @Override 113 public void addThreadCleanupCallback(Runnable callback) 114 { 115 assert callback != null; 116 117 getCallbacks().add(callback); 118 } 119 120 /** 121 * Instructs the hub to notify all its listeners (for the current thread). 122 * It also discards its list of listeners. 123 */ 124 @Override 125 public void cleanup() 126 { 127 List<Runnable> callbacks = getCallbacks(); 128 129 callbacksValue.set(null); 130 131 for (Runnable callback : callbacks) 132 { 133 try 134 { 135 callback.run(); 136 } catch (Exception ex) 137 { 138 logger.warn("Error invoking callback {}: {}", callback, ex, ex); 139 } 140 } 141 142 // Listeners should not re-add themselves or store any per-thread state 143 // here, it will be lost. 144 145 // Discard the per-thread map of values, including the key that stores 146 // the listeners. This means that if a listener attempts to register 147 // new listeners, the new listeners will not be triggered and will be 148 // released to the GC. 149 150 holder.remove(); 151 } 152 153 private static Object NULL_VALUE = new Object(); 154 155 <T> ObjectCreator<T> createValue(final Object key, final ObjectCreator<T> delegate) 156 { 157 return new DefaultObjectCreator<T>(key, delegate); 158 } 159 160 public <T> ObjectCreator<T> createValue(ObjectCreator<T> delegate) 161 { 162 return createValue(uuidGenerator.getAndIncrement(), delegate); 163 } 164 165 <T> PerThreadValue<T> createValue(final Object key) 166 { 167 return new DefaultPerThreadValue(key); 168 } 169 170 @Override 171 public <T> PerThreadValue<T> createValue() 172 { 173 return createValue(uuidGenerator.getAndIncrement()); 174 } 175 176 @Override 177 public void run(Runnable runnable) 178 { 179 assert runnable != null; 180 181 try 182 { 183 runnable.run(); 184 } finally 185 { 186 cleanup(); 187 } 188 } 189 190 @Override 191 public <T> T invoke(Invokable<T> invokable) 192 { 193 try 194 { 195 return invokable.invoke(); 196 } finally 197 { 198 cleanup(); 199 } 200 } 201 202 private final class DefaultPerThreadValue<T> implements PerThreadValue<T> 203 { 204 private final Object key; 205 206 DefaultPerThreadValue(final Object key) 207 { 208 this.key = key; 209 210 } 211 @Override 212 public T get() 213 { 214 return get(null); 215 } 216 217 @Override 218 public T get(T defaultValue) 219 { 220 Map map = getPerthreadMap(); 221 222 Object storedValue = map.get(key); 223 224 if (storedValue == null) 225 { 226 return defaultValue; 227 } 228 229 if (storedValue == NULL_VALUE) 230 { 231 return null; 232 } 233 234 return (T) storedValue; 235 } 236 237 @Override 238 public T set(T newValue) 239 { 240 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue); 241 242 return newValue; 243 } 244 245 @Override 246 public boolean exists() 247 { 248 return getPerthreadMap().containsKey(key); 249 } 250 } 251 252 private final class DefaultObjectCreator<T> implements ObjectCreator<T> 253 { 254 255 private final Object key; 256 private final ObjectCreator<T> delegate; 257 258 DefaultObjectCreator(final Object key, final ObjectCreator<T> delegate) 259 { 260 this.key = key; 261 this.delegate = delegate; 262 } 263 264 public T createObject() 265 { 266 Map map = getPerthreadMap(); 267 T storedValue = (T) map.get(key); 268 269 if (storedValue != null) 270 { 271 return (storedValue == NULL_VALUE) ? null : storedValue; 272 } 273 274 T newValue = delegate.createObject(); 275 276 map.put(key, newValue == null ? NULL_VALUE : newValue); 277 278 return newValue; 279 } 280 } 281}