001/* 002 * Copyright 2016 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.core.ui; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.Map; 022import java.util.Map.Entry; 023 024import org.apache.avalon.framework.activity.Initializable; 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.component.WrapperComponentManager; 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.excalibur.source.SourceResolver; 034 035import org.ametys.core.cache.AbstractCacheManager; 036import org.ametys.core.cache.Cache; 037import org.ametys.core.ui.ribbonconfiguration.RibbonConfiguration; 038import org.ametys.core.ui.ribbonconfiguration.RibbonConfigurationSource; 039import org.ametys.runtime.i18n.I18nizableText; 040import org.ametys.runtime.plugin.component.AbstractLogEnabled; 041import org.ametys.runtime.plugin.component.PluginsComponentManager; 042import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 043 044/** 045 * Helper for RibbonManager, that support Thread safe usage of the managers, while implementing a cache for performances. 046 */ 047public class RibbonManagerCache extends AbstractLogEnabled implements Contextualizable, Serviceable, Component, Initializable 048{ 049 /** Avalon role */ 050 public static final String ROLE = RibbonManagerCache.class.getName(); 051 052 private Map<RibbonManager, Map<String, Long>> _ribbonManagerCacheValidity = new HashMap<>(); 053 private Map<RibbonManager, Integer> _ribbonManagerUsageCache = new HashMap<>(); 054 private Map<RibbonManager, RibbonConfiguration> _ribbonConfigurationCache = new HashMap<>(); 055 private Map<RibbonManager, ThreadSafeComponentManager<Object>> _ribbonServiceManagers = new HashMap<>(); 056 057 private Context _context; 058 059 private ServiceManager _cocoonManager; 060 061 private RibbonControlsManager _ribbonControlsManager; 062 063 private RibbonTabsManager _ribbonTabsManager; 064 065 private SourceResolver _resolver; 066 067 private AbstractCacheManager _cacheManager; 068 069 public void contextualize(Context context) throws ContextException 070 { 071 _context = context; 072 } 073 074 public void service(ServiceManager manager) throws ServiceException 075 { 076 _cocoonManager = manager; 077 _ribbonControlsManager = (RibbonControlsManager) manager.lookup(RibbonControlsManager.ROLE); 078 _ribbonTabsManager = (RibbonTabsManager) manager.lookup(RibbonTabsManager.ROLE); 079 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 080 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 081 } 082 083 public void initialize() throws Exception 084 { 085 _cacheManager.createMemoryCache(ROLE, 086 new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_RIBBON_MANAGER_LABEL"), 087 new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_RIBBON_MANAGER_DESCRIPTION"), 088 false, 089 null); 090 } 091 092 /** 093 * Invalidate the cache 094 */ 095 public void invalidate() 096 { 097 _getRibbonManagerCache().invalidateAll(); 098 } 099 100 /** 101 * Create the RibbonManager associated with the given ribbon file. 102 * If the RibbonManager already exists for the ribbon file and is still valid, it is simply returned. 103 * @param uri The ribbon configuration uri 104 * @return The RibbonManager 105 * @throws Exception If an error occurs 106 */ 107 public synchronized RibbonManager getManager(String uri) throws Exception 108 { 109 RibbonManager ribbonManager = _getRibbonManagerCache().get(uri); 110 if (ribbonManager != null) 111 { 112 if (_isRibbonManagerStillValid(ribbonManager)) 113 { 114 _increaseUsage(ribbonManager); 115 return ribbonManager; 116 } 117 else 118 { 119 _ribbonManagerCacheValidity.remove(ribbonManager); 120 _decreaseUsage(ribbonManager, true); 121 } 122 } 123 124 ribbonManager = _createRibbonManager(); 125 _getRibbonManagerCache().put(uri, ribbonManager); 126 _ribbonManagerCacheValidity.put(ribbonManager, null); 127 _increaseUsage(ribbonManager); 128 129 return ribbonManager; 130 } 131 132 private boolean _isRibbonManagerStillValid(RibbonManager ribbonManager) 133 { 134 Map<String, Long> validity = _ribbonManagerCacheValidity.get(ribbonManager); 135 if (validity == null) 136 { 137 return false; 138 } 139 140 for (Entry<String, Long> entry : validity.entrySet()) 141 { 142 RibbonConfigurationSource ribbonConfigurationEntry = null; 143 try 144 { 145 ribbonConfigurationEntry = RibbonConfigurationSource.createFromUri(entry.getKey(), _resolver); 146 if (!_isRibbonEntryStillValid(entry.getValue(), ribbonConfigurationEntry)) 147 { 148 return false; 149 } 150 } 151 catch (IOException e) 152 { 153 // invalid import, uri is out of date 154 return false; 155 } 156 finally 157 { 158 if (ribbonConfigurationEntry != null) 159 { 160 _resolver.release(ribbonConfigurationEntry.getSource()); 161 } 162 } 163 } 164 165 return true; 166 } 167 168 private boolean _isRibbonEntryStillValid(Long cachedValidity, RibbonConfigurationSource ribbonEntrySource) 169 { 170 boolean entryExists = ribbonEntrySource.getSource().exists(); 171 if (cachedValidity == 0 && entryExists) 172 { 173 // Unable to get "Last Modified" of Entry. Assume changed. 174 return false; 175 } 176 177 if (cachedValidity > 0 && !entryExists) 178 { 179 // Entry was deleted since it was put in cache. 180 return false; 181 } 182 183 // Compare the last modified to the cached "last modified" value, to check for changes 184 return ribbonEntrySource.getLastModified().equals(cachedValidity); 185 } 186 187 private RibbonManager _createRibbonManager() throws Exception 188 { 189 PluginsComponentManager ribbonServiceManager = new PluginsComponentManager(new WrapperComponentManager(_cocoonManager)); 190 ribbonServiceManager.contextualize(_context); 191 ribbonServiceManager.service(_cocoonManager); 192 ribbonServiceManager.setLogger(getLogger()); 193 ribbonServiceManager.addExtensionPoint("core-ui", RibbonManager.ROLE, RibbonManager.class, null, new ArrayList<>()); 194 ribbonServiceManager.initialize(); 195 196 RibbonManager ribbonManager = (RibbonManager) ribbonServiceManager.lookup(RibbonManager.ROLE); 197 ribbonManager.initialize(); 198 199 _ribbonServiceManagers.put(ribbonManager, ribbonServiceManager); 200 201 _ribbonControlsManager.registerRibbonManager(ribbonManager); 202 _ribbonTabsManager.registerRibbonManager(ribbonManager); 203 return ribbonManager; 204 } 205 206 /** 207 * Dispose of a RibbonManager that was previously retrieve with this helper 208 * @param ribbonManager The ribbon manager 209 */ 210 public void dispose(RibbonManager ribbonManager) 211 { 212 if (ribbonManager != null) 213 { 214 _decreaseUsage(ribbonManager, false); 215 } 216 } 217 218 private synchronized void _increaseUsage(RibbonManager ribbonManager) 219 { 220 Integer usage = _ribbonManagerUsageCache.get(ribbonManager); 221 _ribbonManagerUsageCache.put(ribbonManager, usage != null ? usage + 1 : 1); 222 } 223 224 @SuppressWarnings("null") 225 private synchronized void _decreaseUsage(RibbonManager ribbonManager, boolean forceRemove) 226 { 227 Integer usage = _ribbonManagerUsageCache.get(ribbonManager); 228 if (forceRemove || usage != null) 229 { 230 if (!forceRemove && usage > 1) 231 { 232 _ribbonManagerUsageCache.put(ribbonManager, usage - 1); 233 } 234 else 235 { 236 _ribbonManagerUsageCache.remove(ribbonManager); 237 if (!_ribbonManagerCacheValidity.containsKey(ribbonManager)) 238 { 239 _ribbonConfigurationCache.remove(ribbonManager); 240 ThreadSafeComponentManager<Object> serviceManager = _ribbonServiceManagers.get(ribbonManager); 241 _ribbonServiceManagers.remove(ribbonManager); 242 _ribbonControlsManager.unregisterRibbonManager(ribbonManager); 243 _ribbonTabsManager.unregisterRibbonManager(ribbonManager); 244 serviceManager.dispose(); 245 } 246 } 247 } 248 } 249 250 /** 251 * Get the ribbon configuration managed by the RibbonManager 252 * @param ribbonManager The ribbon manager 253 * @return The configuration, or null if no configuration was cached 254 */ 255 public RibbonConfiguration getCachedConfiguration(RibbonManager ribbonManager) 256 { 257 return _ribbonConfigurationCache.get(ribbonManager); 258 } 259 260 /** 261 * Add a ribbon configuration to the cache 262 * @param ribbonManager The ribbon manager 263 * @param configuration The configuration 264 * @param importsValidity The list of imports for this ribbon configuration and their validity 265 */ 266 public void addCachedConfiguration(RibbonManager ribbonManager, RibbonConfiguration configuration, Map<String, Long> importsValidity) 267 { 268 _ribbonConfigurationCache.put(ribbonManager, configuration); 269 _ribbonManagerCacheValidity.put(ribbonManager, importsValidity); 270 } 271 272 private Cache<String, RibbonManager> _getRibbonManagerCache() 273 { 274 return this._cacheManager.get(ROLE); 275 } 276 277}