001/*
002 *  Copyright 2011 Anyware Services
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 */
016package org.ametys.plugins.blog;
017
018import java.util.ArrayList;
019import java.util.Calendar;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Comparator;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032import java.util.concurrent.TimeUnit;
033
034import org.apache.avalon.framework.activity.Initializable;
035import org.apache.avalon.framework.component.Component;
036import org.apache.avalon.framework.logger.AbstractLogEnabled;
037import org.apache.avalon.framework.service.ServiceException;
038import org.apache.avalon.framework.service.ServiceManager;
039import org.apache.avalon.framework.service.Serviceable;
040import org.apache.commons.lang.BooleanUtils;
041import org.joda.time.DateTime;
042
043import org.ametys.cms.ObservationConstants;
044import org.ametys.cms.repository.Content;
045import org.ametys.cms.repository.ContentLanguageExpression;
046import org.ametys.cms.repository.ContentTypeExpression;
047import org.ametys.cms.tag.Tag;
048import org.ametys.cms.tag.TagHelper;
049import org.ametys.cms.tag.TagProviderExtensionPoint;
050import org.ametys.core.observation.Event;
051import org.ametys.core.observation.ObservationManager;
052import org.ametys.core.user.UserIdentity;
053import org.ametys.plugins.blog.posts.PostContentType;
054import org.ametys.plugins.repository.AmetysObjectIterable;
055import org.ametys.plugins.repository.AmetysObjectResolver;
056import org.ametys.plugins.repository.metadata.CompositeMetadata;
057import org.ametys.plugins.repository.provider.WorkspaceSelector;
058import org.ametys.plugins.repository.query.QueryHelper;
059import org.ametys.plugins.repository.query.SortCriteria;
060import org.ametys.plugins.repository.query.expression.AndExpression;
061import org.ametys.plugins.repository.query.expression.Expression;
062import org.ametys.plugins.repository.query.expression.Expression.Operator;
063import org.ametys.plugins.repository.query.expression.MetadataExpression;
064import org.ametys.plugins.repository.query.expression.StringExpression;
065import org.ametys.web.WebConstants;
066import org.ametys.web.repository.content.jcr.DefaultWebContent;
067import org.ametys.web.repository.site.SiteManager;
068import org.ametys.web.site.SiteConfigurationExtensionPoint;
069
070import com.google.common.collect.Multimap;
071import com.google.common.collect.TreeMultimap;
072
073/**
074 * Blog cache manager.
075 */
076public class BlogCacheManager extends AbstractLogEnabled implements Component, Serviceable, Initializable
077{
078    /** The name of metadata for date of post */
079    public static final String POST_METADATA_DATE = "date";
080    
081    /** The avalon role. */
082    public static final String ROLE = BlogCacheManager.class.getName();
083    
084    /** The ametys object resolver. */
085    protected AmetysObjectResolver _ametysResolver;
086    
087    /** Th tag provider */
088    protected TagProviderExtensionPoint _tagProviderEP;
089    
090    /** The Site manager. */
091    protected SiteManager _siteManager;
092    
093    /** The site configuration extension point */
094    protected SiteConfigurationExtensionPoint _siteConf;
095    
096    /** The blog root page handler. */
097    protected BlogPageHandler _blogRootHandler;
098    
099    /** The workspace selector. */
100    protected WorkspaceSelector _workspaceSelector;
101    
102    /** The Ametys resolver */
103    protected AmetysObjectResolver _resolver;
104    
105    /** The observation manager */
106    protected ObservationManager _observationManager;
107    
108    /** Map of PostDateCache by workspace, site and sitemap/language. */
109    protected Map<String, Map<String, Map<String, PostDateCache>>> _postsByDate;
110    
111    /** Map of PostTagCache by workspace, site and sitemap/language. */
112    protected Map<String, Map<String, Map<String, PostTagCache>>> _postsByTag;
113    
114    /** Map of PostDateCache by workspace, site and sitemap/language. */
115    protected Map<String, Map<String, Map<String, AllPostCache>>> _allPosts;
116    
117    /** Map of FuturePostCache by workspace, site and sitemap/language. */
118    protected Map<String, Map<String, Map<String, FuturePostCache>>> _futurePostsCache;
119    
120    /** Caches are initialized by workspace, site and sitemap/language. */
121    protected Map<String, Map<String, Map<String, Boolean>>> _initialized;
122    
123    /** Future caches are updated by workspace, site and sitemap/language. */
124    protected Map<String, Map<String, Map<String, Date>>> _lastUpdateFuturePosts;
125    
126    @Override
127    public void service(ServiceManager serviceManager) throws ServiceException
128    {
129        _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
130        _siteManager = (SiteManager) serviceManager.lookup(SiteManager.ROLE);
131        _siteConf = (SiteConfigurationExtensionPoint) serviceManager.lookup(SiteConfigurationExtensionPoint.ROLE);
132        _blogRootHandler = (BlogPageHandler) serviceManager.lookup(BlogPageHandler.ROLE);
133        _workspaceSelector = (WorkspaceSelector) serviceManager.lookup(WorkspaceSelector.ROLE);
134        _tagProviderEP = (TagProviderExtensionPoint) serviceManager.lookup(TagProviderExtensionPoint.ROLE);
135        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
136        _observationManager = (ObservationManager) serviceManager.lookup(ObservationManager.ROLE);
137    }
138    
139    @Override
140    public void initialize() throws Exception
141    {
142        _postsByDate = new HashMap<>();
143        _postsByTag = new HashMap<>();
144        _allPosts = new HashMap<>();
145        _futurePostsCache = new HashMap<>();
146        _initialized = new HashMap<>();
147        _lastUpdateFuturePosts = new HashMap<>();
148    }
149    
150    /**
151     * Get the years with posts.
152     * @param siteName the site name.
153     * @param language the language.
154     * @param year the year
155     * @return the years with posts as a Set of Integer.
156     */
157    public boolean hasYear(String siteName, String language, int year)
158    {
159        PostDateCache cache = getPostDateCache(siteName, language);
160        
161        return cache.hasYear(year);
162    }
163    
164    /**
165     * Get the years with posts.
166     * @param siteName the site name.
167     * @param language the language.
168     * @return the years with posts as a Set of Integer.
169     */
170    public Set<Integer> getYears(String siteName, String language)
171    {
172        PostDateCache cache = getPostDateCache(siteName, language);
173        
174        return cache.getYears();
175    }
176    
177    /**
178     * Get the years with posts.
179     * @param siteName the site name.
180     * @param language the language.
181     * @param year the year
182     * @param month the month
183     * @return the years with posts as a Set of Integer.
184     */
185    public boolean hasMonth(String siteName, String language, int year, int month)
186    {
187        PostDateCache cache = getPostDateCache(siteName, language);
188        
189        return cache.hasMonth(year, month);
190    }
191    
192    /**
193     * Get the months with posts for a specified year.
194     * @param siteName the site name.
195     * @param language the language.
196     * @param year the year.
197     * @return the months with posts in the specified year as a Set of Integer.
198     */
199    public Set<Integer> getMonths(String siteName, String language, int year)
200    {
201        PostDateCache cache = getPostDateCache(siteName, language);
202        
203        return cache.getMonths(year);
204    }
205    
206    /**
207     * Get the posts for a year-month couple.
208     * @param siteName the site name.
209     * @param language the language.
210     * @param year the year.
211     * @param month the month.
212     * @return the posts in the specified year and month.
213     */
214    public Map<String, Post> getPostsByMonth(String siteName, String language, int year, int month)
215    {
216        PostDateCache cache = getPostDateCache(siteName, language);
217        
218        return cache.getPosts(year, month);
219    }
220    
221    /**
222     * Get the posts for a year-month couple.
223     * @param siteName the site name.
224     * @param language the language.
225     * @param year the year.
226     * @param month the month.
227     * @return the posts in the specified year and month.
228     */
229    public Collection<Post> getSortedPostsByMonth(String siteName, String language, int year, int month)
230    {
231        PostDateCache cache = getPostDateCache(siteName, language);
232        
233        List<Post> sortedPosts = new ArrayList<>(cache.getPosts(year, month).values());
234        
235        Collections.sort(sortedPosts, new ReverseDatePostComparator());
236        
237        return sortedPosts;
238    }
239    
240    /**
241     * Get the posts for a year.
242     * @param siteName the site name.
243     * @param language the language.
244     * @param year the year.
245     * @return the posts in the specified year.
246     */
247    public Map<String, Post> getPostsByYear(String siteName, String language, int year)
248    {
249        PostDateCache cache = getPostDateCache(siteName, language);
250        
251        return cache.getPosts(year);
252    }
253    
254    /**
255     * Get the posts for a year.
256     * @param siteName the site name.
257     * @param language the language.
258     * @param year the year.
259     * @return the posts in the specified year.
260     */
261    public Collection<Post> getSortedPostsByYear(String siteName, String language, int year)
262    {
263        PostDateCache cache = getPostDateCache(siteName, language);
264        
265        List<Post> sortedPosts = new ArrayList<>(cache.getPosts(year).values());
266        
267        Collections.sort(sortedPosts, new ReverseDatePostComparator());
268        
269        return sortedPosts;
270    }
271    
272    /**
273     * Get the posts for a year-month couple.
274     * @param siteName the site name.
275     * @param language the language.
276     * @param year the year.
277     * @param month the month.
278     * @param name the name 
279     * @return the posts in the specified year and month.
280     */
281    public Post getPostByName(String siteName, String language, int year, int month, String name)
282    {
283        PostDateCache cache = getPostDateCache(siteName, language);
284        
285        return cache.getPostByName(year, month, name);
286    }
287    
288    /**
289     * Get the posts for a year-month couple.
290     * @param siteName the site name.
291     * @param language the language.
292     * @param year the year.
293     * @param month the month.
294     * @param id the id of post
295     * @return the posts in the specified year and month.
296     */
297    public boolean hasPost(String siteName, String language, int year, int month, String id)
298    {
299        PostDateCache cache = getPostDateCache(siteName, language);
300        
301        return cache.hasPost(year, month, id);
302    }
303    
304    /**
305     * Get the posts for a year-month couple.
306     * @param siteName the site name.
307     * @param language the language.
308     * @param year the year.
309     * @param month the month.
310     * @param name the name of post
311     * @return the posts in the specified year and month.
312     */
313    public boolean hasPostByName(String siteName, String language, int year, int month, String name)
314    {
315        PostDateCache cache = getPostDateCache(siteName, language);
316        
317        return cache.hasPostByName(year, month, name);
318    }
319    
320    /////////////////////////////////////////////////
321    ///            POSTS BY TAG METHODS           ///
322    /////////////////////////////////////////////////
323    
324    /**
325     * Get the tags for a site and language.
326     * @param siteName The site name
327     * @param language The language
328     * @return the tag list.
329     */
330    public Set<String> getTags(String siteName, String language)
331    {
332        PostTagCache cache = getPostTagCache(siteName, language);
333        
334        return cache.getTags();
335    }
336    
337    /**
338     * Test if a tag exists in the given site and language.
339     * @param siteName The site name
340     * @param language The language
341     * @param tagName The tag name
342     * @return true if the tag exists, false otherwise.
343     */
344    public boolean hasTag(String siteName, String language, String tagName)
345    {
346        PostTagCache cache = getPostTagCache(siteName, language);
347        
348        Map<String, Object> params = new HashMap<>();
349        params.put("siteName", siteName);
350        Tag tag = _tagProviderEP.getTag(tagName, params);
351        Set<String> descendantNamesAndItSelf = TagHelper.getDescendantNames(tag, true);
352        
353        for (String descendantName : descendantNamesAndItSelf)
354        {
355            if (cache.hasTag(descendantName))
356            {
357                return true;
358            }
359        }
360        
361        return false;
362    }
363    
364    /**
365     * Get the posts for a given tag.
366     * @param siteName The site name
367     * @param language The language
368     * @param tagName The tag name
369     * @return the tag posts, indexed by ID.
370     */
371    public Map<String, Post> getPostsByTag(String siteName, String language, String tagName)
372    {
373        PostTagCache cache = getPostTagCache(siteName, language);
374        
375        return cache.getPosts(tagName);
376    }
377    
378    /**
379     * Get the posts for a given tag.
380     * @param siteName The site name
381     * @param language The language
382     * @param tagName The tag name
383     * @return the tag posts, indexed by ID.
384     */
385    public Collection<Post> getSortedPostsByTag(String siteName, String language, String tagName)
386    {
387        return getSortedPostsByTag (siteName, language, tagName, false);
388    }
389    
390    /**
391     * Get the posts for a given tag.
392     * @param siteName The site name
393     * @param language the language
394     * @param tagName the tag name
395     * @param deepSearch true to perform deep search
396     * @return the tag posts, indexed by ID.
397     */
398    public Collection<Post> getSortedPostsByTag(String siteName, String language, String tagName, boolean deepSearch)
399    {
400        PostTagCache cache = getPostTagCache(siteName, language);
401        
402        List<Post> sortedPosts = new ArrayList<>(cache.getPosts(tagName).values());
403        
404        if (deepSearch)
405        {
406            Map<String, Object> contextualParams = new HashMap<>();
407            contextualParams.put("siteName", siteName);
408            
409            Tag tag = _tagProviderEP.getTag(tagName, contextualParams);
410            if (tag != null)
411            {
412                Set<String> descendantsNames = TagHelper.getDescendantNames(tag, false);
413                for (String descendantsName : descendantsNames)
414                {
415                    sortedPosts.addAll(cache.getPosts(descendantsName).values());
416                }
417            }
418        }
419        
420        Collections.sort(sortedPosts, new ReverseDatePostComparator());
421        
422        return sortedPosts;
423    }
424    
425    /**
426     * Test if a given post has a tag, by its ID.
427     * @param siteName The site name
428     * @param language The language
429     * @param tagName The tag name
430     * @param postId The id of post
431     * @return true if the given post has the given tag.
432     */
433    public boolean hasPostByTag(String siteName, String language, String tagName, String postId)
434    {
435        PostTagCache cache = getPostTagCache(siteName, language);
436        
437        return cache.hasPost(tagName, postId);
438    }
439    
440    /**
441     * Get the posts for a year-month couple.
442     * @param siteName The site name
443     * @param language The language
444     * @param tagName The tag name
445     * @param id The id of post
446     * @return the posts in the specified year and month.
447     */
448    public Post getPost(String siteName, String language, String tagName, String id)
449    {
450        PostTagCache cache = getPostTagCache(siteName, language);
451        
452        return cache.getPost(tagName, id);
453    }
454    
455    /**
456     * Get the posts for a year-month couple.
457     * @param siteName The site name
458     * @param language The language
459     * @param tagName The tag name
460     * @param name the name of post
461     * @return the posts in the specified year and month.
462     */
463    public Post getPostByName(String siteName, String language, String tagName, String name)
464    {
465        PostTagCache cache = getPostTagCache(siteName, language);
466        
467        return cache.getPostByName(tagName, name);
468    }
469    
470    /**
471     * Get the posts for a year-month couple.
472     * @param siteName The site name
473     * @param language The language
474     * @param tagName The tag name
475     * @param id The id of post
476     * @return the posts in the specified year and month.
477     */
478    public boolean hasPost(String siteName, String language, String tagName, String id)
479    {
480        PostTagCache cache = getPostTagCache(siteName, language);
481        
482        return cache.hasPost(tagName, id);
483    }
484    
485    /**
486     * Get the posts for a year-month couple.
487     * @param siteName The site name
488     * @param language The language
489     * @param tagName The tag name
490     * @param name the name of post
491     * @return the posts in the specified year and month.
492     */
493    public boolean hasPostByName(String siteName, String language, String tagName, String name)
494    {
495        PostTagCache cache = getPostTagCache(siteName, language);
496        
497        return cache.hasPostByName(tagName, name);
498    }
499    
500    /////////////////////////////////////////////////
501    ///              ALL POSTS METHODS            ///
502    /////////////////////////////////////////////////
503    
504    /**
505     * Get a list of all the posts' IDs.
506     * @param siteName the site name.
507     * @param language the language.
508     * @return all the posts' IDs as a Set.
509     */
510    public Set<String> getPostIds(String siteName, String language)
511    {
512        AllPostCache cache = getAllPostCache(siteName, language);
513        
514        return cache.getPostIds();
515    }
516    
517    /**
518     * Get the exhaustive list of Posts.
519     * @param siteName the site name.
520     * @param language the language.
521     * @return the exhaustive list of Posts, indexed by their ID.
522     */
523    public Map<String, Post> getPosts(String siteName, String language)
524    {
525        AllPostCache cache = getAllPostCache(siteName, language);
526        
527        return cache.getPosts();
528    }
529    
530    /**
531     * Get the exhaustive list of Posts.
532     * @param siteName the site name.
533     * @param language the language.
534     * @return the exhaustive list of Posts, indexed by their ID.
535     */
536    public Collection<Post> getSortedPosts(String siteName, String language)
537    {
538        AllPostCache cache = getAllPostCache(siteName, language);
539        
540        List<Post> sortedPosts = new ArrayList<>(cache.getPosts().values());
541        
542        Collections.sort(sortedPosts, new ReverseDatePostComparator());
543        
544        return sortedPosts;
545    }
546    
547    /**
548     * Test if a post exists, provided its ID.
549     * @param id the post content ID.
550     * @param siteName the site name.
551     * @param language the language.
552     * @return the Post.
553     */
554    public Post getPost(String siteName, String language, String id)
555    {
556        AllPostCache cache = getAllPostCache(siteName, language);
557        
558        return cache.getPost(id);
559    }
560    
561    /**
562     * Test if a post exists, provided its content name.
563     * @param name the post content name.
564     * @param siteName the site name.
565     * @param language the language.
566     * @return true if the post exists.
567     */
568    public Post getPostByName(String siteName, String language, String name)
569    {
570        AllPostCache cache = getAllPostCache(siteName, language);
571        
572        return cache.getPostByName(name);
573    }
574    
575    
576    /**
577     * Test if a post exists, provided its ID.
578     * @param id the post content ID.
579     * @param siteName the site name.
580     * @param language the language.
581     * @return true if the post exists.
582     */
583    public boolean hasPost(String siteName, String language, String id)
584    {
585        AllPostCache cache = getAllPostCache(siteName, language);
586        
587        return cache.hasPost(id);
588    }
589    
590    /**
591     * Test if a post exists, provided its content name.
592     * @param name the post content name.
593     * @param siteName the site name.
594     * @param language the language.
595     * @return true if the post exists.
596     */
597    public boolean hasPostByName(String siteName, String language, String name)
598    {
599        AllPostCache cache = getAllPostCache(siteName, language);
600        
601        return cache.hasPostByName(name);
602    }
603    
604    /**
605     * Add a post content to the cache.
606     * @param siteName The site name
607     * @param language The language
608     * @param postContent The content
609     */
610    public void addPost(String siteName, String language, Content postContent)
611    {
612        PostDateCache dateCache = getPostDateCache(siteName, language);
613        PostTagCache tagCache = getPostTagCache(siteName, language);
614        AllPostCache allCache = getAllPostCache(siteName, language);
615        FuturePostCache futureCache = getFuturePostCache(siteName, language);
616        
617        boolean displayFuturePosts = siteName != null ? BooleanUtils.isTrue(_siteConf.getValueAsBoolean(siteName, "display-future-posts")) : true;
618
619        boolean isFuturePost = _isFuturePost(postContent);
620        if (displayFuturePosts || !isFuturePost)
621        {
622            dispatchPost(dateCache, postContent);
623            dispatchPost(tagCache, postContent);
624            dispatchPost(allCache, postContent);
625        }
626        else // => !displayFuturePosts && isFuturePost
627        {
628            dispatchPost(futureCache, postContent);
629        }
630    }
631    
632    /**
633     * Modify a post content in the cache.
634     * @param siteName The site name
635     * @param language The language
636     * @param postContent The content
637     */
638    public void modifyPost(String siteName, String language, Content postContent)
639    {
640        PostDateCache dateCache = getPostDateCache(siteName, language);
641        PostTagCache tagCache = getPostTagCache(siteName, language);
642        AllPostCache allCache = getAllPostCache(siteName, language);
643        FuturePostCache futureCache = getFuturePostCache(siteName, language);
644        
645        Post post = allCache.getPost(postContent.getId());
646        Post futurePost = futureCache.getPost(postContent.getId());
647        
648        boolean displayFuturePosts = siteName != null ? BooleanUtils.isTrue(_siteConf.getValueAsBoolean(siteName, "display-future-posts")) : true;
649        boolean isFuturePost = _isFuturePost(postContent);
650        
651        if (displayFuturePosts || !isFuturePost)
652        {
653            if (futurePost != null)
654            {
655                removePost(futureCache, futurePost);
656            }
657            
658            if (post == null)
659            {
660                dispatchPost(dateCache, postContent);
661                dispatchPost(tagCache, postContent);
662                dispatchPost(allCache, postContent);
663            }
664            else
665            {
666                modifyPost(dateCache, post, postContent);
667                modifyPost(tagCache, post, postContent);
668                modifyPost(allCache, post, postContent);
669            }
670        }
671        else // => !displayFuturePosts && isFuturePost
672        {
673            if (futurePost == null)
674            {
675                dispatchPost(futureCache, postContent);
676            }
677            else
678            {
679                modifyPost(futureCache, futurePost, postContent);
680            }
681            
682            if (post != null)
683            {
684                removePost(dateCache, post);
685                removePost(tagCache, post);
686                removePost(allCache, post);
687            }
688        }
689    }
690    
691    /**
692     * Validate a post content.
693     * @param siteName The site name
694     * @param language The language
695     * @param postContent The content
696     */
697    public void validatePost(String siteName, String language, Content postContent)
698    {
699        boolean displayFuturePosts = siteName != null ? BooleanUtils.isTrue(_siteConf.getValueAsBoolean(siteName, "display-future-posts")) : true;
700
701        // Get the "live" caches.
702        PostDateCache dateCache = getOrCreatePostDateCache(WebConstants.LIVE_WORKSPACE, siteName, language);
703        PostTagCache tagCache = getOrCreatePostTagCache(WebConstants.LIVE_WORKSPACE, siteName, language);
704        AllPostCache allCache = getOrCreateAllPostCache(WebConstants.LIVE_WORKSPACE, siteName, language);
705        FuturePostCache futureCache = getOrCreateFuturePostCache(WebConstants.LIVE_WORKSPACE, siteName, language);
706        
707        if (displayFuturePosts || !_isFuturePost(postContent))
708        {
709            // Dispatch the post in the live caches.
710            removePost(futureCache, postContent);
711            
712            dispatchPost(dateCache, postContent);
713            dispatchPost(tagCache, postContent);
714            dispatchPost(allCache, postContent);
715        }
716        else // => !displayFuturePosts && isFuturePost
717        {
718            dispatchPost(futureCache, postContent);
719            
720            removePost(dateCache, postContent);
721            removePost(tagCache, postContent);
722            removePost(allCache, postContent);
723        }
724    }
725    
726    /**
727     * Remove a post content from the cache.
728     * @param siteName The site name
729     * @param language The language
730     * @param postContent The content
731     */
732    public void removePost(String siteName, String language, Content postContent)
733    {
734        PostDateCache dateCache = getPostDateCache(siteName, language);
735        PostTagCache tagCache = getPostTagCache(siteName, language);
736        AllPostCache allCache = getAllPostCache(siteName, language);
737        FuturePostCache futureCache = getFuturePostCache(siteName, language);
738        
739        removePost(dateCache, postContent);
740        removePost(tagCache, postContent);
741        removePost(allCache, postContent);
742        removePost(futureCache, postContent);
743    }
744    
745    /**
746     * Remove a post content from the cache.
747     * @param siteName the site name.
748     * @param postId the post ID.
749     */
750    public void removePostById(String siteName, String postId)
751    {
752        PostSiteLang postWithSiteLang = getPost(siteName, postId);
753        PostSiteLang futurePostWithSiteLang = getFuturePost(siteName, postId);
754        
755        if (postWithSiteLang != null)
756        {
757            Post post = postWithSiteLang.getPost();
758            String language = postWithSiteLang.getLanguage();
759            
760            // Get cache from current workspace
761            PostDateCache dateCache = getPostDateCache(siteName, language);
762            PostTagCache tagCache = getPostTagCache(siteName, language);
763            AllPostCache allCache = getAllPostCache(siteName, language);
764            
765            // Get cache from live workspace
766            PostDateCache workspaceDateCache = getOrCreatePostDateCache(WebConstants.LIVE_WORKSPACE, siteName, language);
767            PostTagCache workspaceTagCache = getOrCreatePostTagCache(WebConstants.LIVE_WORKSPACE, siteName, language);
768            AllPostCache workspaceAllCache = getOrCreateAllPostCache(WebConstants.LIVE_WORKSPACE, siteName, language);
769            
770            // Update the cache
771            removePost(dateCache, post);
772            removePost(tagCache, post);
773            removePost(allCache, post);
774            
775            removePost(workspaceDateCache, post);
776            removePost(workspaceTagCache, post);
777            removePost(workspaceAllCache, post);
778        }
779        
780        if (futurePostWithSiteLang != null)
781        {
782            Post post = futurePostWithSiteLang.getPost();
783            String language = futurePostWithSiteLang.getLanguage();
784            
785            // Get cache from current workspace
786            FuturePostCache futureCache = getFuturePostCache(siteName, language);
787            
788            // Get cache from live workspace
789            FuturePostCache workspaceFutureCache = getOrCreateFuturePostCache(WebConstants.LIVE_WORKSPACE, siteName, language);
790            
791            // Update the cache
792            removePost(futureCache, post);
793            removePost(workspaceFutureCache, post);
794        }
795    }
796    
797    /**
798     * Clear the caches.
799     */
800    public void clearCaches()
801    {
802        _postsByDate.clear();
803        _postsByTag.clear();
804        _allPosts.clear();
805        _futurePostsCache.clear();
806        
807        _initialized.clear();
808    }
809    
810    /**
811     * Clear the caches for a given siteName and language.
812     * @param siteName The site name
813     * @param language The language
814     */
815    public void clearCaches(String siteName, String language)
816    {
817        for (String workspaceName : _postsByDate.keySet())
818        {
819            PostDateCache dateCache = getOrCreatePostDateCache(workspaceName, siteName, language);
820            dateCache.clear();
821        }
822        
823        for (String workspaceName : _postsByTag.keySet())
824        {
825            PostTagCache tagCache = getOrCreatePostTagCache(workspaceName, siteName, language);
826            tagCache.clear();
827        }
828        
829        for (String workspaceName : _allPosts.keySet())
830        {
831            AllPostCache cache = getOrCreateAllPostCache(workspaceName, siteName, language);
832            cache.clear();
833        }
834        
835        for (String workspaceName : _futurePostsCache.keySet())
836        {
837            FuturePostCache cache = getOrCreateFuturePostCache(workspaceName, siteName, language);
838            cache.clear();
839        }
840        
841        for (String workspaceName : _initialized.keySet())
842        {
843            setInitialized(workspaceName, siteName, language, Boolean.FALSE);
844        }
845    }
846    
847    /**
848     * Get the post date cache.
849     * @param siteName The site name
850     * @param language The language
851     * @return the post date cache for the given site and language.
852     */
853    protected PostDateCache getPostDateCache(String siteName, String language)
854    {
855        // Get the workspace name.
856        String workspaceName = _workspaceSelector.getWorkspace();
857        
858        // Ensure the cache is created and initialized for the site and language.
859        if (!isInitialized(workspaceName, siteName, language))
860        {
861            initializeCaches(workspaceName, siteName, language);
862        }
863        
864        // Update the future posts cache.
865        _updateFuturePosts(workspaceName, siteName, language);
866        
867        return _postsByDate.get(workspaceName).get(siteName).get(language);
868    }
869    
870    /**
871     * Get the post date cache.
872     * @param workspaceName The workspace
873     * @param siteName The site name
874     * @param language The language
875     * @return the post date cache for the given site and language.
876     */
877    protected PostDateCache getOrCreatePostDateCache(String workspaceName, String siteName, String language)
878    {
879        Map<String, Map<String, PostDateCache>> workspaceCache;
880        if (_postsByDate.containsKey(workspaceName))
881        {
882            workspaceCache = _postsByDate.get(workspaceName);
883        }
884        else
885        {
886            workspaceCache = new HashMap<>();
887            _postsByDate.put(workspaceName, workspaceCache);
888        }
889        
890        Map<String, PostDateCache> siteCache;
891        if (workspaceCache.containsKey(siteName))
892        {
893            siteCache = workspaceCache.get(siteName);
894        }
895        else
896        {
897            siteCache = new HashMap<>();
898            workspaceCache.put(siteName, siteCache);
899        }
900        
901        PostDateCache cache;
902        if (siteCache.containsKey(language))
903        {
904            cache = siteCache.get(language);
905        }
906        else
907        {
908            cache = new PostDateCache();
909            siteCache.put(language, cache);
910        }
911        
912        return cache;
913    }
914    
915    /**
916     * Get the post tag cache.
917     * @param siteName The site name
918     * @param language The language
919     * @return the post tag cache for the given site and language.
920     */
921    protected PostTagCache getPostTagCache(String siteName, String language)
922    {
923        // Get the workspace name.
924        String workspaceName = _workspaceSelector.getWorkspace();
925        
926        // Ensure the cache is created and initialized for the site and language.
927        if (!isInitialized(workspaceName, siteName, language))
928        {
929            initializeCaches(workspaceName, siteName, language);
930        }
931        
932        // Update the future posts cache.
933        _updateFuturePosts(workspaceName, siteName, language);
934        
935        return _postsByTag.get(workspaceName).get(siteName).get(language);
936    }
937    
938    /**
939     * Get the post tag cache.
940     * @param workspaceName The workspace
941     * @param siteName The site name
942     * @param language The language
943     * @return the post tag cache for the given site and language.
944     */
945    protected PostTagCache getOrCreatePostTagCache(String workspaceName, String siteName, String language)
946    {
947        Map<String, Map<String, PostTagCache>> workspaceCache;
948        if (_postsByTag.containsKey(workspaceName))
949        {
950            workspaceCache = _postsByTag.get(workspaceName);
951        }
952        else
953        {
954            workspaceCache = new HashMap<>();
955            _postsByTag.put(workspaceName, workspaceCache);
956        }
957        
958        Map<String, PostTagCache> siteCache;
959        if (workspaceCache.containsKey(siteName))
960        {
961            siteCache = workspaceCache.get(siteName);
962        }
963        else
964        {
965            siteCache = new HashMap<>();
966            workspaceCache.put(siteName, siteCache);
967        }
968        
969        PostTagCache cache;
970        if (siteCache.containsKey(language))
971        {
972            cache = siteCache.get(language);
973        }
974        else
975        {
976            cache = new PostTagCache();
977            siteCache.put(language, cache);
978        }
979        
980        return cache;
981    }
982    
983    /**
984     * Get the exhaustive post cache.
985     * @param siteName The site name
986     * @param language The language
987     * @return the post date cache for the given site and language.
988     */
989    protected AllPostCache getAllPostCache(String siteName, String language)
990    {
991        // Get the workspace name.
992        String workspaceName = _workspaceSelector.getWorkspace();
993        
994        // Ensure the cache is created and initialized for the site and language.
995        if (!isInitialized(workspaceName, siteName, language))
996        {
997            initializeCaches(workspaceName, siteName, language);
998        }
999        
1000        // Update the future posts cache.
1001        _updateFuturePosts(workspaceName, siteName, language);
1002        
1003        return _allPosts.get(workspaceName).get(siteName).get(language);
1004    }
1005    
1006    /**
1007     * Get the exhaustive post cache.
1008     * @param workspaceName The workspace name
1009     * @param siteName The site name
1010     * @param language The language
1011     * @return the post date cache for the given site and language.
1012     */
1013    protected AllPostCache getOrCreateAllPostCache(String workspaceName, String siteName, String language)
1014    {
1015        Map<String, Map<String, AllPostCache>> workspaceCache;
1016        if (_allPosts.containsKey(workspaceName))
1017        {
1018            workspaceCache = _allPosts.get(workspaceName);
1019        }
1020        else
1021        {
1022            workspaceCache = new HashMap<>();
1023            _allPosts.put(workspaceName, workspaceCache);
1024        }
1025        
1026        Map<String, AllPostCache> siteCache;
1027        if (workspaceCache.containsKey(siteName))
1028        {
1029            siteCache = workspaceCache.get(siteName);
1030        }
1031        else
1032        {
1033            siteCache = new HashMap<>();
1034            workspaceCache.put(siteName, siteCache);
1035        }
1036        
1037        AllPostCache cache;
1038        if (siteCache.containsKey(language))
1039        {
1040            cache = siteCache.get(language);
1041        }
1042        else
1043        {
1044            cache = new AllPostCache();
1045            siteCache.put(language, cache);
1046        }
1047        
1048        return cache;
1049    }
1050    
1051    
1052    /**
1053     * Get the future post cache.
1054     * @param siteName the name of the site
1055     * @param language the language
1056     * @return the post date cache for the given site and language.
1057     */
1058    protected FuturePostCache getFuturePostCache(String siteName, String language)
1059    {
1060        // Get the workspace name.
1061        String workspaceName = _workspaceSelector.getWorkspace();
1062        
1063        // Ensure the cache is created and initialized for the site and language.
1064        if (!isInitialized(workspaceName, siteName, language))
1065        {
1066            initializeCaches(workspaceName, siteName, language);
1067        }
1068        
1069        return _futurePostsCache.get(workspaceName).get(siteName).get(language);
1070    }
1071    
1072    /**
1073     * Get the future post cache.
1074     * @param workspaceName the name of the workspace
1075     * @param siteName the name of the site
1076     * @param language the language
1077     * @return the future post cache for the given site and language.
1078     */
1079    protected FuturePostCache getOrCreateFuturePostCache(String workspaceName, String siteName, String language)
1080    {
1081        Map<String, Map<String, FuturePostCache>> workspaceCache;
1082        if (_futurePostsCache.containsKey(workspaceName))
1083        {
1084            workspaceCache = _futurePostsCache.get(workspaceName);
1085        }
1086        else
1087        {
1088            workspaceCache = new HashMap<>();
1089            _futurePostsCache.put(workspaceName, workspaceCache);
1090        }
1091        
1092        Map<String, FuturePostCache> siteCache;
1093        if (workspaceCache.containsKey(siteName))
1094        {
1095            siteCache = workspaceCache.get(siteName);
1096        }
1097        else
1098        {
1099            siteCache = new HashMap<>();
1100            workspaceCache.put(siteName, siteCache);
1101        }
1102        
1103        FuturePostCache cache;
1104        if (siteCache.containsKey(language))
1105        {
1106            cache = siteCache.get(language);
1107        }
1108        else
1109        {
1110            cache = new FuturePostCache();
1111            siteCache.put(language, cache);
1112        }
1113        
1114        return cache;
1115    }
1116    
1117    /**
1118     * Get the exhaustive post cache.
1119     * @param workspaceName The workspace name
1120     * @param siteName The site name
1121     * @param language The language
1122     * @return the post date cache for the given site and language.
1123     */
1124    protected boolean isInitialized(String workspaceName, String siteName, String language)
1125    {
1126        if (_initialized.containsKey(workspaceName))
1127        {
1128            Map<String, Map<String, Boolean>> workspaceInitialized = _initialized.get(workspaceName);
1129            if (workspaceInitialized.containsKey(siteName))
1130            {
1131                Map<String, Boolean> siteInitialized = workspaceInitialized.get(siteName);
1132                
1133                if (siteInitialized.containsKey(language))
1134                {
1135                    return siteInitialized.get(language);
1136                }
1137            }
1138        }
1139        
1140        return false;
1141    }
1142    
1143    /**
1144     * Set the initialized map
1145     * @param siteName The site name
1146     * @param language The language
1147     */
1148    protected void setInitialized(String siteName, String language)
1149    {
1150        setInitialized(siteName, language, Boolean.TRUE);
1151    }
1152    
1153    /**
1154     * Set the initialized map
1155     * @param siteName The site name
1156     * @param language The language
1157     * @param initialized true to set initialized
1158     */
1159    protected void setInitialized(String siteName, String language, Boolean initialized)
1160    {
1161        String workspaceName = _workspaceSelector.getWorkspace();
1162        
1163        setInitialized(workspaceName, siteName, language, initialized);
1164    }
1165    
1166    /**
1167     * Set the initialized map
1168     * @param workspaceName The workspace name
1169     * @param siteName The site name
1170     * @param language The language
1171     * @param initialized true if to set initialized
1172     */
1173    protected void setInitialized(String workspaceName, String siteName, String language, Boolean initialized)
1174    {
1175        Map<String, Map<String, Boolean>> workspaceInitialized;
1176        if (_initialized.containsKey(workspaceName))
1177        {
1178            workspaceInitialized = _initialized.get(workspaceName);
1179        }
1180        else
1181        {
1182            workspaceInitialized = new HashMap<>();
1183            _initialized.put(workspaceName, workspaceInitialized);
1184        }
1185        
1186        Map<String, Boolean> siteInitialized;
1187        if (workspaceInitialized.containsKey(siteName))
1188        {
1189            siteInitialized = workspaceInitialized.get(siteName);
1190        }
1191        else
1192        {
1193            siteInitialized = new HashMap<>();
1194            workspaceInitialized.put(siteName, siteInitialized);
1195        }
1196        
1197        siteInitialized.put(language, initialized);
1198    }
1199    
1200    /**
1201     * Initialize all the caches.
1202     * @param workspaceName The workspace name
1203     * @param siteName The site name
1204     * @param language The language
1205     */
1206    protected void initializeCaches(String workspaceName, String siteName, String language)
1207    {
1208        PostDateCache dateCache = getOrCreatePostDateCache(workspaceName, siteName, language);
1209        PostTagCache tagCache = getOrCreatePostTagCache(workspaceName, siteName, language);
1210        AllPostCache allCache = getOrCreateAllPostCache(workspaceName, siteName, language);
1211        FuturePostCache futureCache = getOrCreateFuturePostCache(workspaceName, siteName, language);
1212        
1213        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, PostContentType.CONTENT_TYPE);
1214        Expression siteExpr = new StringExpression(DefaultWebContent.METADATA_SITE, Operator.EQ, siteName);
1215        Expression languageExpr = new ContentLanguageExpression(Operator.EQ, language);
1216        MetadataExpression dateExpr = new MetadataExpression("date");
1217        
1218        Expression expression = new AndExpression(cTypeExpr, siteExpr, languageExpr, dateExpr);
1219        
1220        boolean displayFuturePosts = siteName != null ? BooleanUtils.isTrue(_siteConf.getValueAsBoolean(siteName, "display-future-posts")) : true;
1221        
1222        SortCriteria sort = new SortCriteria();
1223        sort.addCriterion("date", false, false);
1224        sort.addJCRPropertyCriterion("ametys:lastModified", false, false);
1225        
1226        String query = QueryHelper.getXPathQuery(null, "ametys:content", expression, sort);
1227        
1228        try (AmetysObjectIterable<Content> posts = _ametysResolver.query(query);)
1229        {
1230            for (Content postContent : posts)
1231            {
1232                if (displayFuturePosts || !_isFuturePost(postContent))
1233                {
1234                    dispatchPost(dateCache, postContent);
1235                    dispatchPost(tagCache, postContent);
1236                    dispatchPost(allCache, postContent);
1237                }
1238                else
1239                {
1240                    dispatchPost(futureCache, postContent);
1241                }
1242            }
1243            setInitialized(siteName, language);
1244        }
1245    }
1246    
1247    private void _updateFuturePosts(String workspaceName, String siteName, String language)
1248    {
1249        Date now = new Date();
1250        
1251        Calendar calendar = Calendar.getInstance();
1252        calendar.setTime(now);
1253
1254        int minutes = calendar.get(Calendar.MINUTE);
1255        
1256        int mod = minutes % 15;
1257        calendar.add(Calendar.MINUTE, -mod);
1258        calendar.set(Calendar.SECOND, 0);   
1259        calendar.set(Calendar.MILLISECOND, 0);
1260        
1261        Date roundedNow = calendar.getTime();
1262        
1263        if (_needUpdateFuturePosts(workspaceName, siteName, language, roundedNow))
1264        {
1265            // Update the last update time
1266            _lastUpdateFuturePosts.get(workspaceName).get(siteName).put(language, roundedNow);
1267            
1268            // Remove past "future posts"
1269            FuturePostCache futureCache = getOrCreateFuturePostCache(workspaceName, siteName, language);
1270            Collection<Post> pastPosts = futureCache.removePastPosts();
1271            
1272            // Notify that the posts are pasts, and must be visible now.
1273            for (Post pastPost : pastPosts)
1274            {
1275                if (_resolver.hasAmetysObjectForId(pastPost.getId()))
1276                {
1277                    Content content = _resolver.resolveById(pastPost.getId());
1278                    
1279                    Map<String, Object> eventParams = new HashMap<>();
1280                    eventParams.put(ObservationConstants.ARGS_CONTENT, content);
1281                    _observationManager.notify(new Event(BlogObservationConstants.EVENT_FUTURE_CONTENT_VISIBLE, new UserIdentity("admin", "admin"), eventParams));
1282                }
1283            }
1284        }
1285    }
1286    
1287    private boolean _needUpdateFuturePosts(String workspaceName, String siteName, String language, Date roundedNow)
1288    {
1289        Date lastUpdate = _getOrCreateLastUpdateFuturePosts(workspaceName, siteName, language);
1290        
1291        if (lastUpdate != null)
1292        {
1293            long duration  = roundedNow.getTime() - lastUpdate.getTime();
1294            long diffInMinutes = TimeUnit.MILLISECONDS.toMinutes(duration);
1295            
1296            return diffInMinutes >= 15;
1297        }
1298        
1299        return true;
1300    }
1301    
1302    private Date _getOrCreateLastUpdateFuturePosts(String workspaceName, String siteName, String language)
1303    {
1304        Map<String, Map<String, Date>> workspaceMap;
1305        if (_lastUpdateFuturePosts.containsKey(workspaceName))
1306        {
1307            workspaceMap = _lastUpdateFuturePosts.get(workspaceName);
1308        }
1309        else
1310        {
1311            workspaceMap = new HashMap<>();
1312            _lastUpdateFuturePosts.put(workspaceName, workspaceMap);
1313        }
1314        
1315        Map<String, Date> siteMap;
1316        if (workspaceMap.containsKey(siteName))
1317        {
1318            siteMap = workspaceMap.get(siteName);
1319        }
1320        else
1321        {
1322            siteMap = new HashMap<>();
1323            workspaceMap.put(siteName, siteMap);
1324        }
1325        
1326        Date lastUpdate = null;
1327        if (siteMap.containsKey(language))
1328        {
1329            lastUpdate = siteMap.get(language);
1330        }
1331        
1332        return lastUpdate;
1333    }
1334    
1335    private boolean _isFuturePost (Content post)
1336    {
1337        Date postDate = post.getMetadataHolder().getDate(POST_METADATA_DATE, null);
1338        return postDate != null && postDate.after(new Date());
1339    }
1340    
1341    /**
1342     * Get a post by its site name and id.
1343     * @param siteName the site name.
1344     * @param postId the post ID.
1345     * @return the Post if it exists in the cache, or null if it doesn't.
1346     */
1347    protected PostSiteLang getPost(String siteName, String postId)
1348    {
1349        String workspaceName = _workspaceSelector.getWorkspace();
1350        if (_allPosts.containsKey(workspaceName))
1351        {
1352            Map<String, Map<String, AllPostCache>> workspacePosts = _allPosts.get(workspaceName);
1353            
1354            if (workspacePosts.containsKey(siteName))
1355            {
1356                Map<String, AllPostCache> sitePosts = workspacePosts.get(siteName);
1357                
1358                for (Entry<String, AllPostCache> langPosts : sitePosts.entrySet())
1359                {
1360                    if (langPosts.getValue().hasPost(postId))
1361                    {
1362                        String language = langPosts.getKey();
1363                        Post post = langPosts.getValue().getPost(postId);
1364                        
1365                        return new PostSiteLang(post, siteName, language);
1366                    }
1367                }
1368            }
1369        }
1370        
1371        return null;
1372    }
1373    
1374    /**
1375     * Get a future post by its site name and id.
1376     * @param siteName the site name.
1377     * @param postId the post ID.
1378     * @return the Post if it exists in the cache, or null if it doesn't.
1379     */
1380    protected PostSiteLang getFuturePost(String siteName, String postId)
1381    {
1382        String workspaceName = _workspaceSelector.getWorkspace();
1383        if (_futurePostsCache.containsKey(workspaceName))
1384        {
1385            Map<String, Map<String, FuturePostCache>> workspacePosts = _futurePostsCache.get(workspaceName);
1386            
1387            if (workspacePosts.containsKey(siteName))
1388            {
1389                Map<String, FuturePostCache> sitePosts = workspacePosts.get(siteName);
1390                
1391                for (Entry<String, FuturePostCache> langPosts : sitePosts.entrySet())
1392                {
1393                    if (langPosts.getValue().hasPost(postId))
1394                    {
1395                        String language = langPosts.getKey();
1396                        Post post = langPosts.getValue().getPost(postId);
1397                        
1398                        return new PostSiteLang(post, siteName, language);
1399                    }
1400                }
1401            }
1402        }
1403        
1404        return null;
1405    }
1406    
1407    /**
1408     * Dispatch a post content in the post date cache.
1409     * @param dateCache the post date cache.
1410     * @param content the Post content.
1411     */
1412    protected void dispatchPost(PostDateCache dateCache, Content content)
1413    {
1414        CompositeMetadata meta = content.getMetadataHolder();
1415        Date postDate = meta.getDate("date", null);
1416        
1417        // Dispatch by date.
1418        if (postDate != null)
1419        {
1420            DateTime dateTime = new DateTime(postDate.getTime());
1421            int month = dateTime.getMonthOfYear();
1422            int year = dateTime.getYear();
1423            
1424            dateCache.addPost(content, year, month);
1425        }
1426    }
1427    
1428    /**
1429     * Add a post in the post date cache.
1430     * @param dateCache the post date cache.
1431     * @param post the Post to add.
1432     */
1433    protected void dispatchPost(PostDateCache dateCache, Post post)
1434    {
1435        Date postDate = post.getDate();
1436        
1437        // Dispatch by date.
1438        if (postDate != null)
1439        {
1440            DateTime dateTime = new DateTime(postDate.getTime());
1441            int month = dateTime.getMonthOfYear();
1442            int year = dateTime.getYear();
1443            
1444            dateCache.addPost(post, year, month);
1445        }
1446    }
1447    
1448    /**
1449     * Dispatch a post content in the post tag cache.
1450     * @param tagCache the post tag cache.
1451     * @param content the Post content.
1452     */
1453    protected void dispatchPost(PostTagCache tagCache, Content content)
1454    {
1455        CompositeMetadata meta = content.getMetadataHolder();
1456        String[] postTags = meta.getStringArray("tags", new String[0]);
1457        
1458        for (String tagName : postTags)
1459        {
1460            tagCache.addPost(content, tagName);
1461        }
1462    }
1463    
1464    /**
1465     * Dispatch a post in the post tag cache.
1466     * @param tagCache the post tag cache.
1467     * @param post the Post to add.
1468     */
1469    protected void dispatchPost(PostTagCache tagCache, Post post)
1470    {
1471        String[] postTags = post.getTags();
1472        
1473        for (String tagName : postTags)
1474        {
1475            tagCache.addPost(post, tagName);
1476        }
1477    }
1478    
1479    /**
1480     * Dispatch a post content in the post cache.
1481     * @param cache the post cache.
1482     * @param content the Post content.
1483     */
1484    protected void dispatchPost(AllPostCache cache, Content content)
1485    {
1486        cache.addPost(content);
1487    }
1488    
1489    /**
1490     * Dispatch a post in the post cache.
1491     * @param cache the post cache.
1492     * @param post the Post content.
1493     */
1494    protected void dispatchPost(AllPostCache cache, Post post)
1495    {
1496        cache.addPost(post);
1497    }
1498    
1499    /**
1500     * Dispatch a post content in the future post cache.
1501     * @param cache the future post cache.
1502     * @param content the Post content.
1503     */
1504    protected void dispatchPost(FuturePostCache cache, Content content)
1505    {
1506        cache.addPost(content);
1507    }
1508    
1509    /**
1510     * Remove a post content.
1511     * @param dateCache the post date cache.
1512     * @param content the Post content.
1513     */
1514    protected void removePost(PostDateCache dateCache, Content content)
1515    {
1516        CompositeMetadata meta = content.getMetadataHolder();
1517        Date postDate = meta.getDate("date", null);
1518        
1519        // Dispatch by date.
1520        if (postDate != null)
1521        {
1522            DateTime dateTime = new DateTime(postDate.getTime());
1523            int month = dateTime.getMonthOfYear();
1524            int year = dateTime.getYear();
1525            
1526            dateCache.removePost(content.getId(), year, month);
1527        }
1528    }
1529    
1530    /**
1531     * Remove a post content.
1532     * @param dateCache the post date cache.
1533     * @param post the Post object to remove.
1534     */
1535    protected void removePost(PostDateCache dateCache, Post post)
1536    {
1537        Date postDate = post.getDate();
1538        
1539        // Dispatch by date.
1540        if (postDate != null)
1541        {
1542            DateTime dateTime = new DateTime(postDate.getTime());
1543            int month = dateTime.getMonthOfYear();
1544            int year = dateTime.getYear();
1545            
1546            dateCache.removePost(post.getId(), year, month);
1547        }
1548    }
1549    
1550    /**
1551     * Remove a post content.
1552     * @param cache the post tag cache.
1553     * @param content the Post content.
1554     */
1555    protected void removePost(PostTagCache cache, Content content)
1556    {
1557        CompositeMetadata meta = content.getMetadataHolder();
1558        String[] postTags = meta.getStringArray("tags", new String[0]);
1559        
1560        for (String tagName : postTags)
1561        {
1562            cache.removePost(content.getId(), tagName);
1563        }
1564    }
1565    
1566    /**
1567     * Remove a post content.
1568     * @param cache the post tag cache.
1569     * @param post the Post to remove.
1570     */
1571    protected void removePost(PostTagCache cache, Post post)
1572    {
1573        String[] postTags = post.getTags();
1574        
1575        for (String tagName : postTags)
1576        {
1577            cache.removePost(post.getId(), tagName);
1578        }
1579    }
1580    
1581    /**
1582     * Remove a post content.
1583     * @param cache the post cache.
1584     * @param content the Post content.
1585     */
1586    protected void removePost(AllPostCache cache, Content content)
1587    {
1588        cache.removePost(content);
1589    }
1590    
1591    /**
1592     * Remove a post content.
1593     * @param cache the post cache.
1594     * @param post the Post to remove.
1595     */
1596    protected void removePost(AllPostCache cache, Post post)
1597    {
1598        cache.removePost(post.getId(), post.getName());
1599    }
1600    
1601    /**
1602     * Remove a future post content.
1603     * @param cache the future post cache.
1604     * @param content the Post content to remove.
1605     */
1606    protected void removePost(FuturePostCache cache, Content content)
1607    {
1608        cache.removePost(content);
1609    }
1610    
1611    /**
1612     * Remove a future post content.
1613     * @param cache the future post cache.
1614     * @param post the Post to remove.
1615     */
1616    protected void removePost(FuturePostCache cache, Post post)
1617    {
1618        cache.removePost(post);
1619    }
1620    
1621    /**
1622     * Remove a post content.
1623     * @param dateCache the post date cache.
1624     * @param post the post content
1625     * @param postContent the post Content.
1626     */
1627    protected void modifyPost(PostDateCache dateCache, Post post, Content postContent)
1628    {
1629        Date oldDate = post.getDate();
1630        Date newDate = postContent.getMetadataHolder().getDate("date");
1631        
1632        if (!newDate.equals(oldDate))
1633        {
1634            if (oldDate != null)
1635            {
1636                DateTime oldDateTime = new DateTime(oldDate.getTime());
1637                dateCache.removePost(postContent.getId(), oldDateTime.getYear(), oldDateTime.getMonthOfYear());
1638            }
1639                
1640            DateTime newDateTime = new DateTime(newDate.getTime());
1641            dateCache.addPost(postContent, newDateTime.getYear(), newDateTime.getMonthOfYear());
1642        }
1643    }
1644    
1645    /**
1646     * Remove a post content.
1647     * @param cache the post tag cache.
1648     * @param post the post content
1649     * @param postContent the post Content.
1650     */
1651    protected void modifyPost(PostTagCache cache, Post post, Content postContent)
1652    {
1653        CompositeMetadata meta = postContent.getMetadataHolder();
1654        String[] oldPostTags = post.getTags();
1655        String[] newPostTags = meta.getStringArray("tags", new String[0]);
1656        
1657        for (String oldTagName : oldPostTags)
1658        {
1659            cache.removePost(postContent.getId(), oldTagName);
1660        }
1661        
1662        for (String newTagName : newPostTags)
1663        {
1664            cache.addPost(postContent, newTagName);
1665        }
1666    }
1667    
1668    /**
1669     * Modify a post content.
1670     * @param cache the post cache.
1671     * @param post the post content
1672     * @param postContent the post Content.
1673     */
1674    protected void modifyPost(AllPostCache cache, Post post, Content postContent)
1675    {
1676        cache.removePost(postContent);
1677        cache.addPost(postContent);
1678    }
1679    
1680    /**
1681     * Modify a future post content.
1682     * @param cache the post cache.
1683     * @param post the post
1684     * @param postContent the post Content.
1685     */
1686    protected void modifyPost(FuturePostCache cache, Post post, Content postContent)
1687    {
1688        cache.removePost(postContent);
1689        cache.addPost(postContent);
1690    }
1691    
1692    /**
1693     * Class representing a Post in the cache.
1694     */
1695    public class Post
1696    {
1697        
1698        /** The post content id. */
1699        protected String _id;
1700        
1701        /** The post content name. */
1702        protected String _name;
1703        
1704        /** The post date. */
1705        protected Date _date;
1706        
1707        /** The post tags. */
1708        protected String[] _tags;
1709        
1710        /**
1711         * Get the id.
1712         * @return the id
1713         */
1714        public String getId()
1715        {
1716            return _id;
1717        }
1718
1719        /**
1720         * Set the id.
1721         * @param id the id to set
1722         */
1723        public void setId(String id)
1724        {
1725            this._id = id;
1726        }
1727
1728        /**
1729         * Get the name.
1730         * @return the name
1731         */
1732        public String getName()
1733        {
1734            return _name;
1735        }
1736
1737        /**
1738         * Set the name.
1739         * @param name the name to set
1740         */
1741        public void setName(String name)
1742        {
1743            this._name = name;
1744        }
1745
1746        /**
1747         * Get the tags.
1748         * @return the tags
1749         */
1750        public String[] getTags()
1751        {
1752            return _tags;
1753        }
1754        
1755        /**
1756         * Set the tags.
1757         * @param tags the tags to set
1758         */
1759        public void setTags(String[] tags)
1760        {
1761            this._tags = new String[tags.length];
1762            System.arraycopy(tags, 0, _tags, 0, tags.length);
1763        }
1764        
1765        /**
1766         * Get the date.
1767         * @return the date
1768         */
1769        public Date getDate()
1770        {
1771            return _date;
1772        }
1773        
1774        /**
1775         * Set the date.
1776         * @param date the date to set
1777         */
1778        public void setDate(Date date)
1779        {
1780            this._date = date;
1781        }
1782        
1783        @Override
1784        public int hashCode()
1785        {
1786            return _id.hashCode();
1787        }
1788        
1789        @Override
1790        public boolean equals(Object obj)
1791        {
1792            if (this == obj)
1793            {
1794                return true;
1795            }
1796            if (obj == null)
1797            {
1798                return false;
1799            }
1800            if (getClass() != obj.getClass())
1801            {
1802                return false;
1803            }
1804            Post other = (Post) obj;
1805            if (_id == null)
1806            {
1807                if (other._id != null)
1808                {
1809                    return false;
1810                }
1811            }
1812            else if (!_id.equals(other._id))
1813            {
1814                return false;
1815            }
1816            return true;
1817        }
1818        
1819    }
1820    
1821    /**
1822     * A Post with site name and language.
1823     */
1824    protected class PostSiteLang extends Post
1825    {
1826        
1827        /** The post. */
1828        protected Post _post;
1829        
1830        /** The post site name. */
1831        protected String _siteName;
1832        
1833        /** The post language. */
1834        protected String _language;
1835        
1836        /**
1837         * Default constructor.
1838         */
1839        public PostSiteLang()
1840        {
1841            // Nothing to do.
1842        }
1843        
1844        /**
1845         * Constructor with all parameters.
1846         * @param post the Post.
1847         * @param siteName the site name.
1848         * @param language the language.
1849         */
1850        public PostSiteLang(Post post, String siteName, String language)
1851        {
1852            super();
1853            this._post = post;
1854            this._siteName = siteName;
1855            this._language = language;
1856        }
1857
1858        /**
1859         * Get the post.
1860         * @return the post
1861         */
1862        public Post getPost()
1863        {
1864            return _post;
1865        }
1866
1867        /**
1868         * Set the post.
1869         * @param post the post to set
1870         */
1871        public void setPost(Post post)
1872        {
1873            this._post = post;
1874        }
1875
1876        /**
1877         * Get the siteName.
1878         * @return the siteName
1879         */
1880        public String getSiteName()
1881        {
1882            return _siteName;
1883        }
1884
1885        /**
1886         * Set the siteName.
1887         * @param siteName the siteName to set
1888         */
1889        public void setSiteName(String siteName)
1890        {
1891            this._siteName = siteName;
1892        }
1893
1894        /**
1895         * Get the language.
1896         * @return the language
1897         */
1898        public String getLanguage()
1899        {
1900            return _language;
1901        }
1902
1903        /**
1904         * Set the language.
1905         * @param language the language to set
1906         */
1907        public void setLanguage(String language)
1908        {
1909            this._language = language;
1910        }
1911        
1912    }
1913    
1914    /**
1915     * Post cache by date.
1916     */
1917    protected class PostDateCache
1918    {
1919        
1920        /** The posts, indexed by year, month and ID. */
1921        private Map<Integer, Map<Integer, Map<String, Post>>> _posts;
1922        
1923        /**
1924         * Construct a PostDateCache instance.
1925         */
1926        public PostDateCache()
1927        {
1928            _posts = new HashMap<>();
1929        }
1930        
1931        /**
1932         * Determines if the year page exists
1933         * @param year the year
1934         * @return true if there are posts for this year
1935         */
1936        public boolean hasYear(int year)
1937        {
1938            return _posts.containsKey(year);
1939        }
1940        
1941        /**
1942         * Get the years with posts.
1943         * @return the years with posts as a Set of Integer.
1944         */
1945        public Set<Integer> getYears()
1946        {
1947            return _posts.keySet();
1948        }
1949        
1950        /**
1951         * Determines if the page month exists in given year
1952         * @param year The year
1953         * @param month  The month
1954         * @return true if there are posts for this year/month couple
1955         */
1956        public boolean hasMonth(int year, int month)
1957        {
1958            boolean hasMonth = false;
1959            
1960            if (_posts.containsKey(year))
1961            {
1962                hasMonth = _posts.get(year).containsKey(month);
1963            }
1964            
1965            return hasMonth;
1966        }
1967        
1968        /**
1969         * Get the months with posts for a specified year.
1970         * @param year the year.
1971         * @return the months with posts in the specified year as a Set of Integer.
1972         */
1973        public Set<Integer> getMonths(int year)
1974        {
1975            Set<Integer> months = new HashSet<>();
1976            
1977            if (_posts.containsKey(year))
1978            {
1979                months = _posts.get(year).keySet();
1980            }
1981            
1982            return months;
1983        }
1984        
1985        /**
1986         * Get the posts for a year couple.
1987         * @param year the year.
1988         * @return the posts in the specified year.
1989         */
1990        public Map<String, Post> getPosts(int year)
1991        {
1992            Map<String, Post> posts = new HashMap<>();
1993            
1994            if (_posts.containsKey(year))
1995            {
1996                Map<Integer, Map<String, Post>> months = _posts.get(year);
1997                
1998                for (Map<String, Post> month : months.values())
1999                {
2000                    posts.putAll(month);
2001                }
2002            }
2003            
2004            return posts;
2005        }
2006        
2007        /**
2008         * Get the posts for a year-month couple.
2009         * @param year the year.
2010         * @param month the month.
2011         * @return the posts in the specified year and month.
2012         */
2013        public Map<String, Post> getPosts(int year, int month)
2014        {
2015            Map<String, Post> posts = new HashMap<>();
2016            
2017            if (_posts.containsKey(year))
2018            {
2019                Map<Integer, Map<String, Post>> months = _posts.get(year);
2020                
2021                if (months.containsKey(month))
2022                {
2023                    posts = months.get(month);
2024                }
2025            }
2026            
2027            return posts;
2028        }
2029        
2030        /**
2031         * Add a post to the cache.
2032         * @param content The content
2033         * @param year the year
2034         * @param month the month
2035         */
2036        public void addPost(Content content, int year, int month)
2037        {
2038            Map<String, Post> posts = getOrCreatePostMap(year, month);
2039            
2040            CompositeMetadata meta = content.getMetadataHolder();
2041            
2042            Post post = new Post();
2043            post.setId(content.getId());
2044            post.setName(content.getName());
2045            post.setDate(meta.getDate("date"));
2046            post.setTags(meta.getStringArray("tags", new String[0]));
2047            
2048            posts.put(post.getId(), post);
2049        }
2050        
2051        /**
2052         * Add a post to the cache.
2053         * @param post the post
2054         * @param year the year of the post
2055         * @param month the month of the post
2056         */
2057        public void addPost(Post post, int year, int month)
2058        {
2059            Map<String, Post> posts = getOrCreatePostMap(year, month);
2060            posts.put(post.getId(), post);
2061        }
2062        
2063        /**
2064         * Remove a post from the cache.
2065         * @param contentId The content id
2066         * @param year the year
2067         * @param month the month
2068         */
2069        public void removePost(String contentId, int year, int month)
2070        {
2071            if (_posts.containsKey(year))
2072            {
2073                Map<Integer, Map<String, Post>> months = _posts.get(year);
2074                
2075                if (months.containsKey(month))
2076                {
2077                    Map<String, Post> posts = months.get(month);
2078                    
2079                    posts.remove(contentId);
2080                    if (posts.isEmpty())
2081                    {
2082                        months.remove(month);
2083                    }
2084                }
2085                
2086                if (months.isEmpty())
2087                {
2088                    _posts.remove(year);
2089                }
2090            }
2091        }
2092        
2093        /**
2094         * Test if the post with the given ID exists for this year and month.
2095         * @param year the year.
2096         * @param month the month.
2097         * @param id the id of post
2098         * @return the posts in the specified year and month.
2099         */
2100        public boolean hasPost(int year, int month, String id)
2101        {
2102            return getPosts(year, month).containsKey(id);
2103        }
2104        
2105        /**
2106         * Get the posts for a year-month couple.
2107         * @param year the year.
2108         * @param month the month.
2109         * @param name the name of post
2110         * @return the posts in the specified year and month.
2111         */
2112        public Post getPostByName(int year, int month, String name)
2113        {
2114            Post post = null;
2115            
2116            Iterator<Post> posts = getPosts(year, month).values().iterator();
2117            while (posts.hasNext() && post == null)
2118            {
2119                Post currentPost = posts.next();
2120                if (currentPost.getName().equals(name))
2121                {
2122                    post = currentPost;
2123                }
2124            }
2125            
2126            return post;
2127        }
2128        
2129        /**
2130         * Get the posts for a year-month couple.
2131         * @param year the year.
2132         * @param month the month.
2133         * @param name the name of post
2134         * @return the posts in the specified year and month.
2135         */
2136        public boolean hasPostByName(int year, int month, String name)
2137        {
2138            boolean hasPost = false;
2139            
2140            Iterator<Post> posts = getPosts(year, month).values().iterator();
2141            while (posts.hasNext() && !hasPost)
2142            {
2143                hasPost = posts.next().getName().equals(name);
2144            }
2145            
2146            return hasPost;
2147        }
2148        
2149        /**
2150         * Clear the cache.
2151         */
2152        public void clear()
2153        {
2154            _posts.clear();
2155        }
2156        
2157        /**
2158         * Get or create the post set for the specified year and month.
2159         * @param year the year.
2160         * @param month the month.
2161         * @return the post set.
2162         */
2163        protected Map<String, Post> getOrCreatePostMap(int year, int month)
2164        {
2165            Map<String, Post> posts;
2166            
2167            Map<Integer, Map<String, Post>> months;
2168            
2169            if (_posts.containsKey(year))
2170            {
2171                months = _posts.get(year);
2172            }
2173            else
2174            {
2175                months = new HashMap<>();
2176                _posts.put(year, months);
2177            }
2178            
2179            if (months.containsKey(month))
2180            {
2181                posts = months.get(month);
2182            }
2183            else
2184            {
2185                posts = new LinkedHashMap<>();
2186                months.put(month, posts);
2187            }
2188            
2189            return posts;
2190        }
2191                                
2192    }
2193    
2194    /**
2195     * Post cache by tag.
2196     */
2197    protected class PostTagCache
2198    {
2199        
2200        /** The posts, indexed by tag and ID. */
2201        private Map<String, Map<String, Post>> _posts;
2202        
2203        /**
2204         * Construct a PostTagCache instance.
2205         */
2206        public PostTagCache()
2207        {
2208            _posts = new HashMap<>();
2209        }
2210        
2211        /**
2212         * Get the years with posts.
2213         * @param tagId The id of tag
2214         * @return the years with posts as a Set of Integer.
2215         */
2216        public boolean hasTag(String tagId)
2217        {
2218            return _posts.containsKey(tagId);
2219        }
2220        
2221        /**
2222         * Get the years with posts.
2223         * @return the years with posts as a Set of Integer.
2224         */
2225        public Set<String> getTags()
2226        {
2227            return _posts.keySet();
2228        }
2229        
2230        /**
2231         * Get the posts for a year-month couple.
2232         * @param tagId the year.
2233         * @return the posts in the specified year and month.
2234         */
2235        public Map<String, Post> getPosts(String tagId)
2236        {
2237            Map<String, Post> posts = new HashMap<>();
2238            
2239            if (_posts.containsKey(tagId))
2240            {
2241                posts = _posts.get(tagId);
2242            }
2243            
2244            return posts;
2245        }
2246        
2247        /**
2248         * Add a post to the cache.
2249         * @param content The content
2250         * @param tagId the tag ID.
2251         */
2252        public void addPost(Content content, String tagId)
2253        {
2254            Map<String, Post> posts = getOrCreatePostMap(tagId);
2255            
2256            CompositeMetadata meta = content.getMetadataHolder();
2257            
2258            Post post = new Post();
2259            post.setId(content.getId());
2260            post.setName(content.getName());
2261            post.setDate(meta.getDate("date"));
2262            post.setTags(meta.getStringArray("tags", new String[0]));
2263            
2264            posts.put(post.getId(), post);
2265        }
2266        
2267        /**
2268         * Add a post to the cache.
2269         * @param post the post
2270         * @param tagId the tag ID.
2271         */
2272        public void addPost(Post post, String tagId)
2273        {
2274            Map<String, Post> posts = getOrCreatePostMap(tagId);
2275            posts.put(post.getId(), post);
2276        }
2277        
2278        /**
2279         * Remove a post from the cache.
2280         * @param contentId The id of content
2281         * @param tagId  the id of tag
2282         */
2283        public void removePost(String contentId, String tagId)
2284        {
2285            if (_posts.containsKey(tagId))
2286            {
2287                Map<String, Post> posts = _posts.get(tagId);
2288                
2289                posts.remove(contentId);
2290                if (posts.isEmpty())
2291                {
2292                    _posts.remove(tagId);
2293                }
2294            }
2295        }
2296        
2297        /**
2298         * Test if the post with the given ID exists for this year and month.
2299         * @param tagId the tag id.
2300         * @param id the post ID.
2301         * @return the posts in the specified year and month.
2302         */
2303        public Post getPost(String tagId, String id)
2304        {
2305            return getPosts(tagId).get(id);
2306        }
2307        
2308        /**
2309         * Test if the post with the given ID exists for this year and month.
2310         * @param tagId the tag id.
2311         * @param id the post ID.
2312         * @return the posts in the specified year and month.
2313         */
2314        public boolean hasPost(String tagId, String id)
2315        {
2316            return getPosts(tagId).containsKey(id);
2317        }
2318        
2319        /**
2320         * Get the posts for a year-month couple.
2321         * @param tagId the year.
2322         * @param name The name of post
2323         * @return the posts in the specified year and month.
2324         */
2325        public Post getPostByName(String tagId, String name)
2326        {
2327            Post post = null;
2328            
2329            Iterator<Post> posts = getPosts(tagId).values().iterator();
2330            while (posts.hasNext() && post == null)
2331            {
2332                Post currentPost = posts.next();
2333                if (currentPost.getName().equals(name))
2334                {
2335                    post = currentPost;
2336                }
2337            }
2338            
2339            return post;
2340        }
2341        
2342        /**
2343         * Get the posts for a year-month couple.
2344         * @param tagId the year.
2345         * @param name the name of post
2346         * @return the posts in the specified year and month.
2347         */
2348        public boolean hasPostByName(String tagId, String name)
2349        {
2350            boolean hasPost = false;
2351            
2352            Iterator<Post> posts = getPosts(tagId).values().iterator();
2353            while (posts.hasNext() && !hasPost)
2354            {
2355                hasPost = posts.next().getName().equals(name);
2356            }
2357            
2358            return hasPost;
2359        }
2360        
2361        /**
2362         * Clear the cache.
2363         */
2364        public void clear()
2365        {
2366            _posts.clear();
2367        }
2368        
2369        /**
2370         * Get or create the post set for the specified year and month.
2371         * @param tagId the year.
2372         * @return the post set, indexed by id.
2373         */
2374        protected Map<String, Post> getOrCreatePostMap(String tagId)
2375        {
2376            Map<String, Post> posts;
2377            
2378            if (_posts.containsKey(tagId))
2379            {
2380                posts = _posts.get(tagId);
2381            }
2382            else
2383            {
2384                posts = new LinkedHashMap<>();
2385                _posts.put(tagId, posts);
2386            }
2387            
2388            return posts;
2389        }
2390        
2391    }
2392    
2393    /**
2394     * Cache of all the posts.
2395     */
2396    protected class AllPostCache
2397    {
2398        
2399        /** The posts, indexed by ID. */
2400        private Map<String, Post> _posts;
2401        
2402        /** The posts, indexed by name. */
2403        private Map<String, Post> _postsByName;
2404        
2405        /**
2406         * Construct a AllPostCache instance.
2407         */
2408        public AllPostCache()
2409        {
2410            _posts = new LinkedHashMap<>();
2411            _postsByName = new LinkedHashMap<>();
2412        }
2413        
2414        /**
2415         * Get a list of all the posts' IDs.
2416         * @return all the posts' IDs as a Set.
2417         */
2418        public Set<String> getPostIds()
2419        {
2420            return Collections.unmodifiableSet(_posts.keySet());
2421        }
2422        
2423        /**
2424         * Get the exhaustive list of Posts.
2425         * @return the exhaustive list of Posts.
2426         */
2427        public Map<String, Post> getPosts()
2428        {
2429            return Collections.unmodifiableMap(_posts);
2430        }
2431        
2432        /**
2433         * Test if a post exists, provided its ID.
2434         * @param id the post content ID.
2435         * @return true if the post exists.
2436         */
2437        public Post getPost(String id)
2438        {
2439            return _posts.get(id);
2440        }
2441        
2442        /**
2443         * Test if a post exists, provided its ID.
2444         * @param id the post content ID.
2445         * @return true if the post exists.
2446         */
2447        public boolean hasPost(String id)
2448        {
2449            return _posts.containsKey(id);
2450        }
2451        
2452        /**
2453         * Test if a post exists, provided its content name.
2454         * @param name the post content name.
2455         * @return true if the post exists.
2456         */
2457        public Post getPostByName(String name)
2458        {
2459            return _postsByName.get(name);
2460        }
2461        
2462        /**
2463         * Test if a post exists, provided its content name.
2464         * @param name the post content name.
2465         * @return true if the post exists.
2466         */
2467        public boolean hasPostByName(String name)
2468        {
2469            return _postsByName.containsKey(name);
2470        }
2471        
2472        /**
2473         * Add a post to the cache.
2474         * @param content the post content.
2475         */
2476        public void addPost(Content content)
2477        {
2478            CompositeMetadata meta = content.getMetadataHolder();
2479            
2480            Post post = new Post();
2481            post.setId(content.getId());
2482            post.setName(content.getName());
2483            if (meta.hasMetadata("date"))
2484            {
2485                post.setDate(meta.getDate("date"));
2486            }
2487            post.setTags(meta.getStringArray("tags", new String[0]));
2488            
2489            _posts.put(post.getId(), post);
2490            _postsByName.put(post.getName(), post);
2491        }
2492        
2493        /**
2494         * Add a post to the cache.
2495         * @param post The post
2496         */
2497        public void addPost(Post post)
2498        {
2499            _posts.put(post.getId(), post);
2500            _postsByName.put(post.getName(), post);
2501        }
2502        
2503        /**
2504         * Remove a post from the cache.
2505         * @param content the post content to remove from the cache.
2506         */
2507        public void removePost(Content content)
2508        {
2509            removePost(content.getId(), content.getName());
2510        }
2511        
2512        /**
2513         * Remove a post from the cache.
2514         * @param id The id of post
2515         * @param name The name of post
2516         */
2517        public void removePost(String id, String name)
2518        {
2519            _posts.remove(id);
2520            _postsByName.remove(name);
2521        }
2522        
2523        /**
2524         * Clear the cache.
2525         */
2526        public void clear()
2527        {
2528            _posts.clear();
2529            _postsByName.clear();
2530        }
2531    }
2532    
2533    /**
2534     * Cache of identifier of the future posts.
2535     */
2536    protected class FuturePostCache
2537    {
2538        /** The posts, indexed by ID. */
2539        private Map<String, Post> _posts;
2540        
2541        /** The posts id, indexed by date. */
2542        private Multimap<Date, String> _sortedPostIds;
2543        
2544        /**
2545         * Construct a AllPostCache instance.
2546         */
2547        public FuturePostCache()
2548        {
2549            _posts = new HashMap<>();
2550            _sortedPostIds = TreeMultimap.create();
2551        }
2552        
2553        /**
2554         * Remove the post that are not future anymore (given the current date) 
2555         * @return The collection of past posts
2556         */
2557        public Collection<Post> removePastPosts()
2558        {
2559            Set<Post> pastPosts = new HashSet<>();
2560            
2561            Set<Date> sortedDates = _sortedPostIds.keySet();
2562            Iterator<Date> iterator = sortedDates.iterator();
2563            boolean isFuture = false;
2564            Date now = new Date();
2565            
2566            while (iterator.hasNext() && !isFuture)
2567            {
2568                Date date = iterator.next();
2569                isFuture = date.after(now);
2570                
2571                if (!isFuture)
2572                {
2573                    Collection<String> pastPostIds = _sortedPostIds.get(date);
2574                    for (String id : pastPostIds)
2575                    {
2576                        Post pastPost = _posts.get(id);
2577                        if (pastPost != null)
2578                        {
2579                            pastPosts.add(pastPost);
2580                        }
2581                    }
2582                }
2583            }
2584            
2585            for (Post pastPost : pastPosts)
2586            {
2587                removePost(pastPost);
2588            }
2589            
2590            return pastPosts;
2591        }
2592
2593        /**
2594         * Test if a post exists, provided its ID.
2595         * @param id the post content ID.
2596         * @return true if the post exists.
2597         */
2598        public Post getPost(String id)
2599        {
2600            return _posts.get(id);
2601        }
2602        
2603        /**
2604         * Test if a post exists, provided its ID.
2605         * @param id the post content ID.
2606         * @return true if the post exists.
2607         */
2608        public boolean hasPost(String id)
2609        {
2610            return _posts.containsKey(id);
2611        }
2612        
2613        /**
2614         * Add a post to the cache.
2615         * @param content the post content.
2616         */
2617        public void addPost(Content content)
2618        {
2619            CompositeMetadata meta = content.getMetadataHolder();
2620            String id = content.getId();
2621            
2622            if (_posts.containsKey(id))
2623            {
2624                removePost(content);
2625            }
2626            
2627            Date date = meta.getDate("date");
2628            
2629            Post post = new Post();
2630            post.setId(id);
2631            post.setName(content.getName());
2632            post.setDate(meta.getDate("date"));
2633            post.setTags(meta.getStringArray("tags", new String[0]));
2634            
2635            _posts.put(post.getId(), post);
2636            _sortedPostIds.put(date, id);
2637        }
2638        
2639        /**
2640         * Remove a post from the cache.
2641         * @param content the post content to remove from the cache.
2642         */
2643        public void removePost(Content content)
2644        {
2645            Post post = _posts.remove(content.getId());
2646            if (post != null)
2647            {
2648                _sortedPostIds.remove(post.getDate(), post.getId());
2649            }
2650        }
2651        
2652        /**
2653         * Remove a post from the cache.
2654         * @param post the post content to remove from the cache.
2655         */
2656        public void removePost(Post post)
2657        {
2658            _posts.remove(post.getId());
2659            _sortedPostIds.remove(post.getDate(), post.getId());
2660        }
2661        
2662        /**
2663         * Clear the cache.
2664         */
2665        public void clear()
2666        {
2667            _posts.clear();
2668            _sortedPostIds.clear();
2669        }
2670    }
2671    
2672    /**
2673     * Reverse date post comparator.
2674     */
2675    public class ReverseDatePostComparator implements Comparator<Post>
2676    {
2677        @Override
2678        public int compare(Post p1, Post p2)
2679        {
2680            if (p1.getDate() == null)
2681            {
2682                return 1;
2683            }
2684            if (p2.getDate() == null)
2685            {
2686                return -1;
2687            }
2688            return p2.getDate().compareTo(p1.getDate());
2689        }
2690    }
2691    
2692}