001// Copyright 2006-2013 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
015package org.apache.tapestry5.ioc.internal.services;
016
017import org.apache.tapestry5.ioc.Invokable;
018import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019import org.apache.tapestry5.ioc.internal.util.JDKUtils;
020import org.apache.tapestry5.ioc.services.PerThreadValue;
021import org.apache.tapestry5.ioc.services.PerthreadManager;
022import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
023import org.apache.tapestry5.ioc.services.ThreadCleanupListener;
024import org.slf4j.Logger;
025
026import java.util.List;
027import java.util.Map;
028import java.util.concurrent.atomic.AtomicBoolean;
029import java.util.concurrent.atomic.AtomicInteger;
030import java.util.concurrent.locks.Lock;
031
032@SuppressWarnings("all")
033public class PerthreadManagerImpl implements PerthreadManager
034{
035    private final Lock lock = JDKUtils.createLockForThreadLocalCreation();
036
037    private final PerThreadValue<List<Runnable>> callbacksValue;
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        callbacksValue = 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<Runnable> getCallbacks()
097    {
098        List<Runnable> result = callbacksValue.get();
099
100        if (result == null)
101        {
102            result = CollectionFactory.newList();
103            callbacksValue.set(result);
104        }
105
106        return result;
107    }
108
109    public void addThreadCleanupListener(final ThreadCleanupListener listener)
110    {
111        assert listener != null;
112
113        addThreadCleanupCallback(new Runnable()
114        {
115            public void run()
116            {
117                listener.threadDidCleanup();
118            }
119        });
120    }
121
122    public void addThreadCleanupCallback(Runnable callback)
123    {
124        assert callback != null;
125
126        getCallbacks().add(callback);
127    }
128
129    /**
130     * Instructs the hub to notify all its listeners (for the current thread).
131     * It also discards its list of listeners.
132     */
133    public void cleanup()
134    {
135        List<Runnable> callbacks = getCallbacks();
136
137        callbacksValue.set(null);
138
139        for (Runnable callback : callbacks)
140        {
141            try
142            {
143                callback.run();
144            } catch (Exception ex)
145            {
146                logger.warn(String.format("Error invoking callback %s: %s", callback, ex),
147                        ex);
148            }
149        }
150
151        // Listeners should not re-add themselves or store any per-thread state
152        // here, it will be lost.
153
154        try
155        {
156            lock.lock();
157
158            // Discard the per-thread map of values, including the key that stores
159            // the listeners. This means that if a listener attempts to register
160            // new listeners, the new listeners will not be triggered and will be
161            // released to the GC.
162
163            holder.remove();
164        } finally
165        {
166            lock.unlock();
167        }
168    }
169
170    private static Object NULL_VALUE = new Object();
171
172    <T> PerThreadValue<T> createValue(final Object key)
173    {
174        return new PerThreadValue<T>()
175        {
176            public T get()
177            {
178                return get(null);
179            }
180
181            public T get(T defaultValue)
182            {
183                Map map = getPerthreadMap();
184
185                if (map.containsKey(key))
186                {
187                    Object storedValue = map.get(key);
188
189                    if (storedValue == NULL_VALUE)
190                        return null;
191
192                    return (T) storedValue;
193                }
194
195                return defaultValue;
196            }
197
198            public T set(T newValue)
199            {
200                getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
201
202                return newValue;
203            }
204
205            public boolean exists()
206            {
207                return getPerthreadMap().containsKey(key);
208            }
209        };
210    }
211
212    public <T> PerThreadValue<T> createValue()
213    {
214        return createValue(uuidGenerator.getAndIncrement());
215    }
216
217    public void run(Runnable runnable)
218    {
219        assert runnable != null;
220
221        try
222        {
223            runnable.run();
224        } finally
225        {
226            cleanup();
227        }
228    }
229
230    public <T> T invoke(Invokable<T> invokable)
231    {
232        try
233        {
234            return invokable.invoke();
235        } finally
236        {
237            cleanup();
238        }
239    }
240}