    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 final AtomicBoolean shutdown = new AtomicBoolean();
    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.set(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.get())
    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 ObjectCreator<T>()
    159        {
    160            public T createObject()
    161            {
    162                Map map = getPerthreadMap();
    163                T storedValue = (T) map.get(key);
    164
    165                if (storedValue != null)
    166                {
    167                    return (storedValue == NULL_VALUE) ? null : storedValue;
    168                }
    169
    170                T newValue = delegate.createObject();
    171
    172                map.put(key, newValue == null ? NULL_VALUE : newValue);
    173
    174                return newValue;
    175            }
    176        };
    177    }
    178
    179    public <T> ObjectCreator<T> createValue(ObjectCreator<T> delegate)
    180    {
    181        return createValue(uuidGenerator.getAndIncrement(), delegate);
    182    }
    183
    184    <T> PerThreadValue<T> createValue(final Object key)
    185    {
    186        return new PerThreadValue<T>()
    187        {
    188            @Override
    189            public T get()
    190            {
    191                return get(null);
    192            }
    193
    194            @Override
    195            public T get(T defaultValue)
    196            {
    197                Map map = getPerthreadMap();
    198
    199                Object storedValue = map.get(key);
    200
    201                if (storedValue == null)
    202                {
    203                    return defaultValue;
    204                }
    205
    206                if (storedValue == NULL_VALUE)
    207                {
    208                    return null;
    209                }
    210
    211                return (T) storedValue;
    212            }
    213
    214            @Override
    215            public T set(T newValue)
    216            {
    217                getPerthreadMap().put(key, newValue == null ? NULL_VALUE : newValue);
    218
    219                return newValue;
    220            }
    221
    222            @Override
    223            public boolean exists()
    224            {
    225                return getPerthreadMap().containsKey(key);
    226            }
    227        };
    228    }
    229
    230    @Override
    231    public <T> PerThreadValue<T> createValue()
    232    {
    233        return createValue(uuidGenerator.getAndIncrement());
    234    }
    235
    236    @Override
    237    public void run(Runnable runnable)
    238    {
    239        assert runnable != null;
    240
    241        try
    242        {
    243            runnable.run();
    244        } finally
    245        {
    246            cleanup();
    247        }
    248    }
    249
    250    @Override
    251    public <T> T invoke(Invokable<T> invokable)
    252    {
    253        try
    254        {
    255            return invokable.invoke();
    256        } finally
    257        {
    258            cleanup();
    259        }
    260    }
    261}