    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.ioc.internal.services;
    014
    015import java.util.List;
    016import java.util.Map;
    017import java.util.concurrent.atomic.AtomicBoolean;
    018import java.util.concurrent.atomic.AtomicInteger;
    019
    020import org.apache.tapestry5.ioc.Invokable;
    021import org.apache.tapestry5.ioc.ObjectCreator;
    022import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    023import org.apache.tapestry5.ioc.services.PerThreadValue;
    024import org.apache.tapestry5.ioc.services.PerthreadManager;
    025import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
    026import org.apache.tapestry5.ioc.services.ThreadCleanupListener;
    027import org.slf4j.Logger;
    028
    029@SuppressWarnings("all")
    030public class PerthreadManagerImpl implements PerthreadManager
    031{
    032    private final PerThreadValue<List<Runnable>> callbacksValue;
    033
    034    private static class MapHolder extends ThreadLocal<Map>
    035    {
    036        @Override
    037        protected Map initialValue()
    038        {
    039            return CollectionFactory.newMap();
    040        }
    041    }
    042
    043    private final Logger logger;
    044
    045    private final MapHolder holder = new MapHolder();
    046
    047    private final AtomicInteger uuidGenerator = new AtomicInteger();
    048
    049    private volatile boolean shutdown = false;
    050
    051    public PerthreadManagerImpl(Logger logger)
    052    {
    053        this.logger = logger;
    054
    055        callbacksValue = createValue();
    056    }
    057
    058    public void registerForShutdown(RegistryShutdownHub hub)
    059    {
    060        hub.addRegistryShutdownListener(new Runnable()
    061        {
    062            @Override
    063            public void run()
    064            {
    065                cleanup();
    066                shutdown = true;
    067            }
    068        });
    069    }
    070
    071    private Map getPerthreadMap()
    072    {
    073        // This is a degenerate case; it may not even exist; but if during registry shutdown somehow code executes
    074        // that attempts to create new values or add new listeners, those go into a new map instance that is
    075        // not referenced (and so immediately GCed).
    076        if (shutdown)
    077        {
    078            return CollectionFactory.newMap();
    079        }
    080
    081        return holder.get();
    082    }
    083
    084    private List<Runnable> getCallbacks()
    085    {
    086        List<Runnable> result = callbacksValue.get();
    087
    088        if (result == null)
    089        {
    090            result = CollectionFactory.newList();
    091            callbacksValue.set(result);
    092        }
    093
    094        return result;
    095    }
    096
    097    @Override
    098    public void addThreadCleanupListener(final ThreadCleanupListener listener)
    099    {
    100        assert listener != null;
    101
    102        addThreadCleanupCallback(new Runnable()
    103        {
    104            @Override
    105            public void run()
    106            {
    107                listener.threadDidCleanup();
    108            }
    109        });
    110    }
    111
    112    @Override
    113    public void addThreadCleanupCallback(Runnable callback)
    114    {
    115        assert callback != null;
    116
    117        getCallbacks().add(callback);
    118    }
    119
    120    /**
    121     * Instructs the hub to notify all its listeners (for the current thread).
    122     * It also discards its list of listeners.
    123     */
    124    @Override
    125    public void cleanup()
    126    {
    127        List<Runnable> callbacks = getCallbacks();
    128
    129        callbacksValue.set(null);
    130
    131        for (Runnable callback : callbacks)
    132        {
    133            try
    134            {
    135                callback.run();
    136            } catch (Exception ex)
    137            {
    138                logger.warn("Error invoking callback {}: {}", callback, ex, ex);
    139            }
    140        }
    141
    142        // Listeners should not re-add themselves or store any per-thread state
    143        // here, it will be lost.
    144
    145        // Discard the per-thread map of values, including the key that stores
    146        // the listeners. This means that if a listener attempts to register
    147        // new listeners, the new listeners will not be triggered and will be
    148        // released to the GC.
    149
    150        holder.remove();
    151    }
    152
    153    private static Object NULL_VALUE = new Object();
    154
    155    <T> ObjectCreator<T> createValue(final Object key, final ObjectCreator<T> delegate)
    156    {
    157        return new DefaultObjectCreator<T>(key, delegate);
    158    }
    159
    160    public <T> ObjectCreator<T> createValue(ObjectCreator<T> delegate)
    161    {
    162        return createValue(uuidGenerator.getAndIncrement(), delegate);
    163    }
    164
    165    <T> PerThreadValue<T> createValue(final Object key)
    166    {
    167        return new DefaultPerThreadValue(key);
    168    }
    169
    170    @Override
    171    public <T> PerThreadValue<T> createValue()
    172    {
    173        return createValue(uuidGenerator.getAndIncrement());
    174    }
    175
    176    @Override
    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    @Override
    191    public <T> T invoke(Invokable<T> invokable)
    192    {
    193        try
    194        {
    195            return invokable.invoke();
    196        } finally
    197        {
    198            cleanup();
    199        }
    200    }
    201
    202    private final class DefaultPerThreadValue<T> implements PerThreadValue<T>
    203    {
    204        private final Object key;
    205
    206        DefaultPerThreadValue(final Object key)
    207        {
    208            this.key = key;
    209
    210        }
    211        @Override
    212        public T get()
    213        {
    214            return get(null);
    215        }
    216
    217        @Override
    218        public T get(T defaultValue)
    219        {
    220            Map map = getPerthreadMap();
    221
    222            Object storedValue = map.get(key);
    223
    224            if (storedValue == null)
    225            {
    226                return defaultValue;
    227            }
    228
    229            if (storedValue == NULL_VALUE)
    230            {
    231                return null;
    232            }
    233
    234            return (T) storedValue;
    235        }
    236
    237        @Override
    238        public T set(T newValue)
    239        {
    240            getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
    241
    242            return newValue;
    243        }
    244
    245        @Override
    246        public boolean exists()
    247        {
    248            return getPerthreadMap().containsKey(key);
    249        }
    250    }
    251
    252    private final class DefaultObjectCreator<T> implements ObjectCreator<T>
    253    {
    254
    255        private final Object key;
    256        private final ObjectCreator<T> delegate;
    257
    258        DefaultObjectCreator(final Object key, final ObjectCreator<T> delegate)
    259        {
    260            this.key = key;
    261            this.delegate = delegate;
    262        }
    263
    264        public T createObject()
    265        {
    266            Map map = getPerthreadMap();
    267            T storedValue = (T) map.get(key);
    268
    269            if (storedValue != null)
    270            {
    271                return (storedValue == NULL_VALUE) ? null : storedValue;
    272            }
    273
    274            T newValue = delegate.createObject();
    275
    276            map.put(key, newValue == null ? NULL_VALUE : newValue);
    277
    278            return newValue;
    279        }
    280    }
    281}