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
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<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}