    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(String.format("Error invoking callback %s: %s", callback, ex),
    139                        ex);
    140            }
    141        }
    142
    143        // Listeners should not re-add themselves or store any per-thread state
    144        // here, it will be lost.
    145
    146        // Discard the per-thread map of values, including the key that stores
    147        // the listeners. This means that if a listener attempts to register
    148        // new listeners, the new listeners will not be triggered and will be
    149        // released to the GC.
    150
    151        holder.remove();
    152    }
    153
    154    private static Object NULL_VALUE = new Object();
    155
    156    <T> ObjectCreator<T> createValue(final Object key, final ObjectCreator<T> delegate)
    157    {
    158        return new DefaultObjectCreator<T>(key, delegate);
    159    }
    160
    161    public <T> ObjectCreator<T> createValue(ObjectCreator<T> delegate)
    162    {
    163        return createValue(uuidGenerator.getAndIncrement(), delegate);
    164    }
    165
    166    <T> PerThreadValue<T> createValue(final Object key)
    167    {
    168        return new DefaultPerThreadValue(key);
    169    }
    170
    171    @Override
    172    public <T> PerThreadValue<T> createValue()
    173    {
    174        return createValue(uuidGenerator.getAndIncrement());
    175    }
    176
    177    @Override
    178    public void run(Runnable runnable)
    179    {
    180        assert runnable != null;
    181
    182        try
    183        {
    184            runnable.run();
    185        } finally
    186        {
    187            cleanup();
    188        }
    189    }
    190
    191    @Override
    192    public <T> T invoke(Invokable<T> invokable)
    193    {
    194        try
    195        {
    196            return invokable.invoke();
    197        } finally
    198        {
    199            cleanup();
    200        }
    201    }
    202
    203    private final class DefaultPerThreadValue<T> implements PerThreadValue<T>
    204    {
    205        private final Object key;
    206
    207        DefaultPerThreadValue(final Object key)
    208        {
    209            this.key = key;
    210
    211        }
    212        @Override
    213        public T get()
    214        {
    215            return get(null);
    216        }
    217
    218        @Override
    219        public T get(T defaultValue)
    220        {
    221            Map map = getPerthreadMap();
    222
    223            Object storedValue = map.get(key);
    224
    225            if (storedValue == null)
    226            {
    227                return defaultValue;
    228            }
    229
    230            if (storedValue == NULL_VALUE)
    231            {
    232                return null;
    233            }
    234
    235            return (T) storedValue;
    236        }
    237
    238        @Override
    239        public T set(T newValue)
    240        {
    241            getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
    242
    243            return newValue;
    244        }
    245
    246        @Override
    247        public boolean exists()
    248        {
    249            return getPerthreadMap().containsKey(key);
    250        }
    251    }
    252
    253    private final class DefaultObjectCreator<T> implements ObjectCreator<T>
    254    {
    255
    256        private final Object key;
    257        private final ObjectCreator<T> delegate;
    258
    259        DefaultObjectCreator(final Object key, final ObjectCreator<T> delegate)
    260        {
    261            this.key = key;
    262            this.delegate = delegate;
    263        }
    264
    265        public T createObject()
    266        {
    267            Map map = getPerthreadMap();
    268            T storedValue = (T) map.get(key);
    269
    270            if (storedValue != null)
    271            {
    272                return (storedValue == NULL_VALUE) ? null : storedValue;
    273            }
    274
    275            T newValue = delegate.createObject();
    276
    277            map.put(key, newValue == null ? NULL_VALUE : newValue);
    278
    279            return newValue;
    280        }
    281    }
    282}