001/*
002 *  Copyright 2012 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.web.cache.pageelement;
017
018import java.util.HashSet;
019import java.util.Iterator;
020import java.util.Map;
021import java.util.Set;
022
023import org.apache.avalon.framework.configuration.Configurable;
024import org.apache.avalon.framework.configuration.Configuration;
025import org.apache.avalon.framework.configuration.ConfigurationException;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.core.observation.Event;
032import org.ametys.core.observation.Observer;
033import org.ametys.web.WebHelper;
034import org.ametys.web.cache.pageelement.PageElementCachePolicy.PolicyResult;
035import org.ametys.web.repository.site.Site;
036import org.ametys.web.repository.site.SiteManager;
037
038/**
039 * Handles the {@link PageElementCache} based on the various {@link PageElementCachePolicy}.
040 */
041public class InvalidatePageElementCacheObserver extends AbstractLogEnabled implements Observer, Serviceable, Configurable
042{
043    private ServiceManager _manager;
044
045    private PageElementCache _pageElementCache;
046    private PageElementCachePolicyExtensionPoint _pageElementCachePolicyExtensionPoint;
047    private SiteManager _siteManager;
048
049    @Override
050    public void service(ServiceManager manager) throws ServiceException
051    {
052        _manager = manager;
053        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
054    }
055
056    @Override
057    public void configure(Configuration configuration) throws ConfigurationException
058    {
059        String cache = configuration.getChild("cache").getValue();
060        String policies = configuration.getChild("policies").getValue();
061
062        try
063        {
064            _pageElementCache = (PageElementCache) _manager.lookup(cache);
065            _pageElementCachePolicyExtensionPoint = (PageElementCachePolicyExtensionPoint) _manager.lookup(policies);
066        }
067        catch (ServiceException e)
068        {
069            throw new ConfigurationException("Wrong value for cache and/or policies values.", configuration, e);
070        }
071    }
072
073    @Override
074    public int getPriority(Event event)
075    {
076        // processed just before front-office cache invalidation
077        return MAX_PRIORITY + 3500;
078    }
079
080    @Override
081    public boolean supports(Event event)
082    {
083        // supports all events, as they are further processed by the various cache policies.
084        return true;
085    }
086
087    @Override
088    public void observe(Event event, Map<String, Object> transientVars) throws Exception
089    {
090        Site site = _getSite(event);
091
092        if (site != null)
093        {
094            _doObserve("default", site, event);
095            _doObserve("live", site, event);
096        }
097        else
098        {
099            for (Site s : _siteManager.getSites())
100            {
101                _doObserve("default", s, event);
102                _doObserve("live", s, event);
103            }
104        }
105        
106    }
107    
108    /*
109     * The algorithm is:
110     * 
111     * for each cached inputdata
112     *      for each associated InputDataCachePolicy, call the first shouldClearCache method
113     *          all null values are ignored
114     *          if there's no non-null value, or if there's at least one REMOVE, remove the cache associated to the InputData role
115     *          if there's only KEEP values, do nothing
116     *          if there's at least one NEED_INFO, call the second shouldClearCache method
117     *          if there's no non-null value, or if there's at least one REMOVE or NEED_INFO, remove the cache associated to the InputData role
118     *          if there's only KEEP values, do nothing
119     */
120    private void _doObserve(String workspace, Site site, Event event)
121    {
122        String siteName = site.getName();
123
124        for (String pageElementType : _pageElementCache.getPageElementTypes(workspace, siteName))
125        {
126            Set<PageElementCachePolicy> policies = _pageElementCachePolicyExtensionPoint.getPolicies(pageElementType);
127
128            Set<PageElementCachePolicy> needInfoPolicies = _firstPass(policies, workspace, site, pageElementType, event);
129
130            if (needInfoPolicies != null)
131            {
132                // there's at least one NEED_INFORMATION, iterate on pages
133
134                for (String elementId : _pageElementCache.getElementCache(workspace, siteName, pageElementType).keySet())
135                {
136                    _secondPass(needInfoPolicies, workspace, site, pageElementType, elementId, event);
137                }
138            }
139        }
140    }
141
142    private Set<PageElementCachePolicy> _firstPass(Set<PageElementCachePolicy> policies, String workspace, Site site, String pageElementType, Event event)
143    {
144        String siteName = site.getName();
145
146        boolean hasAtLeastOneRemove = false;
147        boolean hasAtLeastOneKeep = false;
148        boolean hasAtLeastOneNeedInfo = false;
149
150        Iterator<PageElementCachePolicy> it = policies.iterator();
151
152        Set<PageElementCachePolicy> needInfoPolicies = new HashSet<>();
153
154        StringBuilder buff = new StringBuilder();
155        if (getLogger().isDebugEnabled())
156        {
157            buff.append("First pass: testing ").append(event).append(" on workspace '").append(workspace).append("', site '").append(siteName).append("' and type '").append(pageElementType).append("'");
158        }
159
160        while (!hasAtLeastOneRemove && it.hasNext())
161        {
162            PageElementCachePolicy policy = it.next();
163            PolicyResult result = policy.shouldClearCache(workspace, site, pageElementType, event);
164
165            if (getLogger().isDebugEnabled())
166            {
167                buff.append("\n - Policy ").append(policy.getClass().getName()).append(" answered ").append(result);
168            }
169
170            if (result != null)
171            {
172                switch (result)
173                {
174                    case KEEP:
175                        hasAtLeastOneKeep = true;
176                        break;
177                    case REMOVE:
178                        hasAtLeastOneRemove = true;
179                        break;
180                    case NEED_MORE_INFORMATION:
181                        hasAtLeastOneNeedInfo = true;
182                        needInfoPolicies.add(policy);
183                        break;
184                    default:
185                        throw new IllegalArgumentException("Should never occur.");
186                }
187            }
188        }
189
190        if (getLogger().isDebugEnabled())
191        {
192            getLogger().debug(buff.toString());
193        }
194
195        if (hasAtLeastOneRemove || !hasAtLeastOneRemove && !hasAtLeastOneKeep && !hasAtLeastOneNeedInfo)
196        {
197            // there's no result or at least one REMOVE
198            _pageElementCache.clear(workspace, siteName, pageElementType);
199        }
200        else if (hasAtLeastOneNeedInfo)
201        {
202            return needInfoPolicies;
203        }
204
205        return null;
206    }
207
208    private void _secondPass(Set<PageElementCachePolicy> policies, String workspace, Site site, String pageElementType, String elementId, Event event)
209    {
210        String siteName = site.getName();
211
212        boolean hasAtLeastOneRemove = false;
213        boolean hasAtLeastOneKeep = false;
214        boolean hasAtLeastOneNeedInfo = false;
215
216        Iterator<PageElementCachePolicy> it = policies.iterator();
217
218        StringBuilder buff = new StringBuilder();
219        if (getLogger().isDebugEnabled())
220        {
221            buff.append("Second pass: testing ").append(event).append(" on workspace '").append(workspace).append("', site '").append(siteName).append("', type '").append(pageElementType).append("' and ID '").append(elementId).append("'");
222        }
223
224        while (!hasAtLeastOneRemove && !hasAtLeastOneNeedInfo && it.hasNext())
225        {
226            PageElementCachePolicy policy = it.next();
227            PolicyResult result = policy.shouldClearCache(workspace, site, pageElementType, elementId, event);
228
229            if (getLogger().isDebugEnabled())
230            {
231                buff.append("\n - Policy ").append(policy.getClass().getName()).append(" answered ").append(result);
232            }
233
234            if (result != null)
235            {
236                switch (result)
237                {
238                    case KEEP:
239                        hasAtLeastOneKeep = true;
240                        break;
241                    case REMOVE:
242                        hasAtLeastOneRemove = true;
243                        break;
244                    case NEED_MORE_INFORMATION:
245                        hasAtLeastOneNeedInfo = true;
246                        break;
247                    default:
248                        throw new IllegalArgumentException("Should never occur.");
249                }
250            }
251        }
252
253        if (getLogger().isDebugEnabled())
254        {
255            getLogger().debug(buff.toString());
256        }
257
258        _removeItemIfNecessary(workspace, pageElementType, elementId, siteName, hasAtLeastOneRemove, hasAtLeastOneKeep, hasAtLeastOneNeedInfo);
259    }
260
261    private void _removeItemIfNecessary(String workspace, String pageElementType, String elementId, String siteName, boolean hasAtLeastOneRemove, boolean hasAtLeastOneKeep, boolean hasAtLeastOneNeedInfo)
262    {
263        if (hasAtLeastOneRemove || hasAtLeastOneNeedInfo || !hasAtLeastOneRemove && !hasAtLeastOneKeep && !hasAtLeastOneNeedInfo)
264        {
265            // there's no result or at least one REMOVE or one NEED_INFO
266            _pageElementCache.removeItem(workspace, siteName, pageElementType, elementId);
267        }
268    }
269
270    private Site _getSite(Event event)
271    {
272        return WebHelper.findSite(event);
273    }
274}