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}