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    }