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    }