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