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 final AtomicBoolean shutdown = new AtomicBoolean(); 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.set(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.get()) 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 ObjectCreator<T>() 159 { 160 public T createObject() 161 { 162 Map map = getPerthreadMap(); 163 T storedValue = (T) map.get(key); 164 165 if (storedValue != null) 166 { 167 return (storedValue == NULL_VALUE) ? null : storedValue; 168 } 169 170 T newValue = delegate.createObject(); 171 172 map.put(key, newValue == null ? NULL_VALUE : newValue); 173 174 return newValue; 175 } 176 }; 177 } 178 179 public <T> ObjectCreator<T> createValue(ObjectCreator<T> delegate) 180 { 181 return createValue(uuidGenerator.getAndIncrement(), delegate); 182 } 183 184 <T> PerThreadValue<T> createValue(final Object key) 185 { 186 return new PerThreadValue<T>() 187 { 188 @Override 189 public T get() 190 { 191 return get(null); 192 } 193 194 @Override 195 public T get(T defaultValue) 196 { 197 Map map = getPerthreadMap(); 198 199 Object storedValue = map.get(key); 200 201 if (storedValue == null) 202 { 203 return defaultValue; 204 } 205 206 if (storedValue == NULL_VALUE) 207 { 208 return null; 209 } 210 211 return (T) storedValue; 212 } 213 214 @Override 215 public T set(T newValue) 216 { 217 getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue); 218 219 return newValue; 220 } 221 222 @Override 223 public boolean exists() 224 { 225 return getPerthreadMap().containsKey(key); 226 } 227 }; 228 } 229 230 @Override 231 public <T> PerThreadValue<T> createValue() 232 { 233 return createValue(uuidGenerator.getAndIncrement()); 234 } 235 236 @Override 237 public void run(Runnable runnable) 238 { 239 assert runnable != null; 240 241 try 242 { 243 runnable.run(); 244 } finally 245 { 246 cleanup(); 247 } 248 } 249 250 @Override 251 public <T> T invoke(Invokable<T> invokable) 252 { 253 try 254 { 255 return invokable.invoke(); 256 } finally 257 { 258 cleanup(); 259 } 260 } 261}