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}