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