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.ioc.Invokable; 021import org.apache.tapestry5.ioc.ObjectCreator; 022import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 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(String.format("Error invoking callback %s: %s", callback, ex), 139 ex); 140 } 141 } 142 143 // Listeners should not re-add themselves or store any per-thread state 144 // here, it will be lost. 145 146 // Discard the per-thread map of values, including the key that stores 147 // the listeners. This means that if a listener attempts to register 148 // new listeners, the new listeners will not be triggered and will be 149 // released to the GC. 150 151 holder.remove(); 152 } 153 154 private static Object NULL_VALUE = new Object(); 155 156 <T> ObjectCreator<T> createValue(final Object key, final ObjectCreator<T> delegate) 157 { 158 return new DefaultObjectCreator<T>(key, delegate); 159 } 160 161 public <T> ObjectCreator<T> createValue(ObjectCreator<T> delegate) 162 { 163 return createValue(uuidGenerator.getAndIncrement(), delegate); 164 } 165 166 <T> PerThreadValue<T> createValue(final Object key) 167 { 168 return new DefaultPerThreadValue(key); 169 } 170 171 @Override 172 public <T> PerThreadValue<T> createValue() 173 { 174 return createValue(uuidGenerator.getAndIncrement()); 175 } 176 177 @Override 178 public void run(Runnable runnable) 179 { 180 assert runnable != null; 181 182 try 183 { 184 runnable.run(); 185 } finally 186 { 187 cleanup(); 188 } 189 } 190 191 @Override 192 public <T> T invoke(Invokable<T> invokable) 193 { 194 try 195 { 196 return invokable.invoke(); 197 } finally 198 { 199 cleanup(); 200 } 201 } 202 203 private final class DefaultPerThreadValue<T> implements PerThreadValue<T> 204 { 205 private final Object key; 206 207 DefaultPerThreadValue(final Object key) 208 { 209 this.key = key; 210 211 } 212 @Override 213 public T get() 214 { 215 return get(null); 216 } 217 218 @Override 219 public T get(T defaultValue) 220 { 221 Map map = getPerthreadMap(); 222 223 Object storedValue = map.get(key); 224 225 if (storedValue == null) 226 { 227 return defaultValue; 228 } 229 230 if (storedValue == NULL_VALUE) 231 { 232 return null; 233 } 234 235 return (T) storedValue; 236 } 237 238 @Override 239 public T set(T newValue) 240 { 241 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue); 242 243 return newValue; 244 } 245 246 @Override 247 public boolean exists() 248 { 249 return getPerthreadMap().containsKey(key); 250 } 251 } 252 253 private final class DefaultObjectCreator<T> implements ObjectCreator<T> 254 { 255 256 private final Object key; 257 private final ObjectCreator<T> delegate; 258 259 DefaultObjectCreator(final Object key, final ObjectCreator<T> delegate) 260 { 261 this.key = key; 262 this.delegate = delegate; 263 } 264 265 public T createObject() 266 { 267 Map map = getPerthreadMap(); 268 T storedValue = (T) map.get(key); 269 270 if (storedValue != null) 271 { 272 return (storedValue == NULL_VALUE) ? null : storedValue; 273 } 274 275 T newValue = delegate.createObject(); 276 277 map.put(key, newValue == null ? NULL_VALUE : newValue); 278 279 return newValue; 280 } 281 } 282}