    001// Copyright 2006, 2007, 2008, 2011, 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.internal.event;
    016
    017import java.util.ArrayList;
    018import java.util.Collections;
    019import java.util.HashSet;
    020import java.util.List;
    021import java.util.Map;
    022import java.util.Set;
    023import java.util.function.Function;
    024import java.util.stream.Collectors;
    025
    026import org.apache.tapestry5.commons.internal.util.TapestryException;
    027import org.apache.tapestry5.commons.services.InvalidationEventHub;
    028import org.apache.tapestry5.commons.services.InvalidationListener;
    029import org.apache.tapestry5.commons.util.CollectionFactory;
    030import org.slf4j.Logger;
    031
    032/**
    033 * Base implementation class for classes (especially services) that need to manage a list of
    034 * {@link org.apache.tapestry5.commons.services.InvalidationListener}s.
    035 */
    036public class InvalidationEventHubImpl implements InvalidationEventHub
    037{
    038    private final List<Function<List<String>, List<String>>> callbacks;
    039
    040    private final Logger logger;
    041
    042    protected InvalidationEventHubImpl(boolean productionMode, Logger logger)
    043    {
    044        if (productionMode)
    045        {
    046            callbacks = null;
    047        } else
    048        {
    049            callbacks = CollectionFactory.newThreadSafeList();
    050        }
    051        this.logger = logger;
    052    }
    053
    054    /**
    055     * Notifies all listeners/callbacks.
    056     */
    057    protected final void fireInvalidationEvent()
    058    {
    059        fireInvalidationEvent(Collections.emptyList());
    060    }
    061
    062    /**
    063     * Notifies all listeners/callbacks.
    064     */
    065    public final void fireInvalidationEvent(List<String> resources)
    066    {
    067        if (callbacks == null)
    068        {
    069            return;
    070        }
    071
    072        final Set<String> alreadyProcessed = new HashSet<>();
    073
    074        int level = 1;
    075        do
    076        {
    077            final Set<String> extraResources = new HashSet<>();
    078            Set<String> actuallyNewResources;
    079            if (!resources.isEmpty())
    080            {
    081                logger.info("Invalidating {} resource(s) at level {}: {}", resources.size(), level, String.join(", ", resources));
    082            }
    083            else
    084            {
    085                logger.info("Invalidating all resources");
    086            }
    087            for (Function<List<String>, List<String>> callback : callbacks)
    088            {
    089                final List<String> newResources = callback.apply(resources);
    090                if (newResources == null) {
    091                    throw new TapestryException("InvalidationEventHub callback functions cannot return null", null);
    092                }
    093                actuallyNewResources = newResources.stream()
    094                        .filter(r -> !alreadyProcessed.contains(r))
    095                        .collect(Collectors.toSet());
    096                extraResources.addAll(actuallyNewResources);
    097                alreadyProcessed.addAll(newResources);
    098            }
    099            level++;
    100            resources = new ArrayList<>(extraResources);
    101        }
    102        while (!resources.isEmpty());
    103    }
    104
    105    public final void addInvalidationCallback(final Runnable callback)
    106    {
    107        assert callback != null;
    108
    109        // In production mode, callbacks may be null, in which case, just
    110        // ignore the callback.
    111        if (callbacks != null)
    112        {
    113            callbacks.add((r) -> {
    114                callback.run();
    115                return Collections.emptyList();
    116            });
    117        }
    118    }
    119
    120    public final void clearOnInvalidation(final Map<?, ?> map)
    121    {
    122        assert map != null;
    123
    124        addInvalidationCallback(new Runnable()
    125        {
    126            public void run()
    127            {
    128                map.clear();
    129            }
    130        });
    131    }
    132
    133    public final void addInvalidationListener(final InvalidationListener listener)
    134    {
    135        assert listener != null;
    136
    137        addInvalidationCallback(new Runnable()
    138        {
    139            public void run()
    140            {
    141                listener.objectWasInvalidated();
    142            }
    143        });
    144    }
    145
    146    @Override
    147    public void addInvalidationCallback(Function<List<String>, List<String>> callback)
    148    {
    149        if (callbacks != null)
    150        {
    151            callbacks.add(callback);
    152        }
    153    }
    154
    155}