    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    }