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