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