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}