001    // Copyright 2006, 2007, 2008, 2010, 2011 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.ThreadCleanupListener;
023    import org.slf4j.Logger;
024    
025    import java.util.List;
026    import java.util.Map;
027    import java.util.concurrent.atomic.AtomicInteger;
028    import java.util.concurrent.locks.Lock;
029    
030    @SuppressWarnings("all")
031    public class PerthreadManagerImpl implements PerthreadManager
032    {
033        private final Lock lock = JDKUtils.createLockForThreadLocalCreation();
034    
035        private final PerThreadValue<List<ThreadCleanupListener>> listenersValue;
036    
037        private static class MapHolder extends ThreadLocal<Map>
038        {
039            @Override
040            protected Map initialValue()
041            {
042                return CollectionFactory.newMap();
043            }
044        }
045    
046        private final Logger logger;
047    
048        private final MapHolder holder = new MapHolder();
049    
050        private final AtomicInteger uuidGenerator = new AtomicInteger();
051    
052        public PerthreadManagerImpl(Logger logger)
053        {
054            this.logger = logger;
055    
056            listenersValue = createValue();
057        }
058    
059        private Map getPerthreadMap()
060        {
061            lock.lock();
062    
063            try
064            {
065                return holder.get();
066            } finally
067            {
068                lock.unlock();
069            }
070        }
071    
072        private List<ThreadCleanupListener> getListeners()
073        {
074            List<ThreadCleanupListener> result = listenersValue.get();
075    
076            if (result == null)
077            {
078                result = CollectionFactory.newList();
079                listenersValue.set(result);
080            }
081    
082            return result;
083        }
084    
085        public void addThreadCleanupListener(ThreadCleanupListener listener)
086        {
087            getListeners().add(listener);
088        }
089    
090        /**
091         * Instructs the hub to notify all its listeners (for the current thread).
092         * It also discards its list of listeners.
093         */
094        public void cleanup()
095        {
096            List<ThreadCleanupListener> listeners = getListeners();
097    
098            listenersValue.set(null);
099    
100            for (ThreadCleanupListener listener : listeners)
101            {
102                try
103                {
104                    listener.threadDidCleanup();
105                } catch (Exception ex)
106                {
107                    logger.warn(ServiceMessages.threadCleanupError(listener, ex), ex);
108                }
109            }
110    
111            // Listeners should not re-add themselves or store any per-thread state
112            // here, it will be lost.
113    
114            try
115            {
116                lock.lock();
117    
118                // Discard the per-thread map of values, including the key that stores
119                // the listeners. This means that if a listener attempts to register
120                // new listeners, the new listeners will not be triggered and will be
121                // released to the GC.
122    
123                holder.remove();
124            } finally
125            {
126                lock.unlock();
127            }
128        }
129    
130        private static Object NULL_VALUE = new Object();
131    
132        <T> PerThreadValue<T> createValue(final Object key)
133        {
134            return new PerThreadValue<T>()
135            {
136                public T get()
137                {
138                    return get(null);
139                }
140    
141                public T get(T defaultValue)
142                {
143                    Map map = getPerthreadMap();
144    
145                    if (map.containsKey(key))
146                    {
147                        Object storedValue = map.get(key);
148    
149                        if (storedValue == NULL_VALUE)
150                            return null;
151    
152                        return (T) storedValue;
153                    }
154    
155                    return defaultValue;
156                }
157    
158                public T set(T newValue)
159                {
160                    getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
161    
162                    return newValue;
163                }
164    
165                public boolean exists()
166                {
167                    return getPerthreadMap().containsKey(key);
168                }
169            };
170        }
171    
172        public <T> PerThreadValue<T> createValue()
173        {
174            return createValue(uuidGenerator.getAndIncrement());
175        }
176    
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        public <T> T invoke(Invokable<T> invokable)
191        {
192            try
193            {
194                return invokable.invoke();
195            } finally
196            {
197                cleanup();
198            }
199        }
200    }