001/*
002 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Anahide Tchertchian
016 */
017package org.nuxeo.ecm.platform.contentview.jsf;
018
019import java.io.Serializable;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.nuxeo.ecm.platform.ui.web.cache.LRUCachingMap;
027
028/**
029 * Cache for content views, handling cache keys set on content views.
030 * <p>
031 * Each content view instance will be cached if its cache key is not null. Each instance will be cached using the cache
032 * key so its state is restored. Also handles refresh of caches when receiving events configured on the content view.
033 *
034 * @author Anahide Tchertchian
035 * @since 5.4
036 */
037public class ContentViewCache implements Serializable {
038
039    private static final long serialVersionUID = 1L;
040
041    /**
042     * Default cache size, set to 5 instances per content view
043     */
044    public static final Integer DEFAULT_CACHE_SIZE = new Integer(5);
045
046    protected final Map<String, String> namedCacheKeys = new HashMap<String, String>();
047
048    protected final Map<String, ContentView> namedContentViews = new HashMap<String, ContentView>();
049
050    protected final Map<String, Map<String, ContentView>> cacheInstances = new HashMap<String, Map<String, ContentView>>();
051
052    /**
053     * Map holding content view names that need their page provider to be refreshed for a given event
054     */
055    protected final Map<String, Set<String>> refreshEventToContentViewName = new HashMap<String, Set<String>>();
056
057    /**
058     * Map holding content view names that need their page provider to be reset for a given event
059     */
060    protected final Map<String, Set<String>> resetEventToContentViewName = new HashMap<String, Set<String>>();
061
062    /**
063     * Add given content view to the cache, resolving its cache key and initializing it with its cache size.
064     * <p>
065     * Since 5.7, content views with a cache size <= 0 will be cached anyhow to handle selections, and rendering is in
066     * charge of forcing cache reset when re-displaying the page.
067     */
068    public void add(ContentView cView) {
069        if (cView != null) {
070            String cacheKey = cView.getCacheKey();
071            if (cacheKey == null) {
072                // no cache
073                return;
074            }
075            String name = cView.getName();
076            Integer cacheSize = cView.getCacheSize();
077            if (cacheSize == null) {
078                cacheSize = DEFAULT_CACHE_SIZE;
079            }
080            if (cacheSize.intValue() <= 0) {
081                // if cacheSize <= 0, selection actions will not behave
082                // accurately => behave as if cacheSize was 1 in this case, and
083                // use a dummy cache key. Template rendering will force cache
084                // refresh when rendering the page, as if content view was not
085                // cached.
086                cacheSize = Integer.valueOf(1);
087            }
088
089            Map<String, ContentView> cacheEntry = cacheInstances.get(name);
090            if (cacheEntry == null) {
091                cacheEntry = new LRUCachingMap<String, ContentView>(cacheSize.intValue());
092            }
093            cacheEntry.put(cacheKey, cView);
094            cacheInstances.put(name, cacheEntry);
095            namedCacheKeys.put(name, cacheKey);
096            namedContentViews.put(name, cView);
097            List<String> events = cView.getRefreshEventNames();
098            if (events != null && !events.isEmpty()) {
099                for (String event : events) {
100                    if (refreshEventToContentViewName.containsKey(event)) {
101                        refreshEventToContentViewName.get(event).add(name);
102                    } else {
103                        Set<String> set = new HashSet<String>();
104                        set.add(name);
105                        refreshEventToContentViewName.put(event, set);
106                    }
107                }
108            }
109            events = cView.getResetEventNames();
110            if (events != null && !events.isEmpty()) {
111                for (String event : events) {
112                    if (resetEventToContentViewName.containsKey(event)) {
113                        resetEventToContentViewName.get(event).add(name);
114                    } else {
115                        Set<String> set = new HashSet<String>();
116                        set.add(name);
117                        resetEventToContentViewName.put(event, set);
118                    }
119                }
120            }
121        }
122    }
123
124    /**
125     * Returns cached content view with given name, or null if not found.
126     */
127    public ContentView get(String name) {
128        ContentView cView = namedContentViews.get(name);
129        if (cView != null) {
130            String oldCacheKey = namedCacheKeys.get(name);
131            String newCacheKey = cView.getCacheKey();
132            if (newCacheKey != null && !newCacheKey.equals(oldCacheKey)) {
133                Map<String, ContentView> contentViews = cacheInstances.get(name);
134                if (contentViews.containsKey(newCacheKey)) {
135                    cView = contentViews.get(newCacheKey);
136                    // refresh named caches
137                    namedCacheKeys.put(name, newCacheKey);
138                    namedContentViews.put(name, cView);
139                } else {
140                    // cache not here or expired => return null
141                    return null;
142                }
143            }
144        }
145        return cView;
146    }
147
148    /**
149     * Refresh page providers for content views in the cache with given name.refreshEventToContentViewName
150     * <p>
151     * Other contextual information set on the content view and the page provider will be kept.
152     */
153    public void refresh(String contentViewName, boolean rewind) {
154        ContentView cv = namedContentViews.get(contentViewName);
155        if (cv != null) {
156            if (rewind) {
157                cv.refreshAndRewindPageProvider();
158            } else {
159                cv.refreshPageProvider();
160            }
161        }
162        Map<String, ContentView> instances = cacheInstances.get(contentViewName);
163        if (instances != null) {
164            for (ContentView cView : instances.values()) {
165                // avoid refreshing twice the same content view, see NXP-13604
166                if (cView != null && !cView.equals(cv)) {
167                    if (rewind) {
168                        cView.refreshAndRewindPageProvider();
169                    } else {
170                        cView.refreshPageProvider();
171                    }
172                }
173            }
174        }
175    }
176
177    /**
178     * Resets page providers for content views in the cache with given name.
179     * <p>
180     * Other contextual information set on the content view will be kept.
181     */
182    public void resetPageProvider(String contentViewName) {
183        ContentView cv = namedContentViews.get(contentViewName);
184        if (cv != null) {
185            cv.resetPageProvider();
186        }
187        Map<String, ContentView> instances = cacheInstances.get(contentViewName);
188        if (instances != null) {
189            for (ContentView cView : instances.values()) {
190                if (cView != null) {
191                    cView.resetPageProvider();
192                }
193            }
194        }
195    }
196
197    /**
198     * Resets page providers aggregates.
199     *
200     * @since 6.0
201     */
202    public void resetPageProviderAggregates(String contentViewName) {
203        ContentView cv = namedContentViews.get(contentViewName);
204        if (cv != null) {
205            cv.resetPageProviderAggregates();
206        }
207        Map<String, ContentView> instances = cacheInstances.get(contentViewName);
208        if (instances != null) {
209            for (ContentView cView : instances.values()) {
210                if (cView != null) {
211                    cView.resetPageProviderAggregates();
212                }
213            }
214        }
215    }
216
217    /**
218     * Refresh page providers for content views having declared given event as a refresh event.
219     * <p>
220     * Other contextual information set on the content view and the page provider will be kept.
221     */
222    public void refreshOnEvent(String eventName) {
223        if (eventName != null) {
224            Set<String> contentViewNames = refreshEventToContentViewName.get(eventName);
225            if (contentViewNames != null) {
226                for (String contentViewName : contentViewNames) {
227                    refresh(contentViewName, false);
228                }
229            }
230        }
231    }
232
233    /**
234     * Resets page providers for content views having declared given event as a reset event.
235     * <p>
236     * Other contextual information set on the content view will be kept.
237     */
238    public void resetPageProviderOnEvent(String eventName) {
239        if (eventName != null) {
240            Set<String> contentViewNames = resetEventToContentViewName.get(eventName);
241            if (contentViewNames != null) {
242                for (String contentViewName : contentViewNames) {
243                    resetPageProvider(contentViewName);
244                }
245            }
246        }
247    }
248
249    /**
250     * Resets all cached information for given content view.
251     */
252    public void reset(String contentViewName) {
253        namedContentViews.remove(contentViewName);
254        namedCacheKeys.remove(contentViewName);
255        cacheInstances.remove(contentViewName);
256    }
257
258    /**
259     * Resets all cached information for all content views
260     */
261    public void resetAllContent() {
262        namedContentViews.clear();
263        namedCacheKeys.clear();
264        cacheInstances.clear();
265    }
266
267    /**
268     * Resets all cached information for all content views, as well as configuration caches (refresh and reset events
269     * linked to content views).
270     */
271    public void resetAll() {
272        resetAllContent();
273        refreshEventToContentViewName.clear();
274        resetEventToContentViewName.clear();
275    }
276
277    /**
278     * Iterates over all cached content view instances to refresh them.
279     * <p>
280     * Can be costly if some page providers need to fetch content or perform costly operations at refresh.
281     *
282     * @since 5.7
283     */
284    public void refreshAll() {
285        refreshAll(false);
286    }
287
288    /**
289     * Iterates over all cached content view instances to refresh them.
290     * <p>
291     * Can be costly if some page providers need to fetch content or perform costly operations at refresh.
292     *
293     * @since 5.7
294     */
295    public void refreshAndRewindAll() {
296        refreshAll(true);
297    }
298
299    /**
300     * @since 5.7
301     */
302    protected void refreshAll(boolean rewind) {
303        Set<String> cvNames = namedContentViews.keySet();
304        for (String cvName : cvNames) {
305            refresh(cvName, rewind);
306        }
307    }
308
309}