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}