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