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; 034import org.ametys.core.cache.AbstractCacheManager; 035import org.ametys.core.cache.AbstractCacheManager.CacheType; 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.createCache(ROLE, 086 new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_RIBBON_MANAGER_LABEL"), 087 new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_RIBBON_MANAGER_DESCRIPTION"), 088 CacheType.MEMORY, 089 false); 090 } 091 092 /** 093 * Create the RibbonManager associated with the given ribbon file. 094 * If the RibbonManager already exists for the ribbon file and is still valid, it is simply returned. 095 * @param uri The ribbon configuration uri 096 * @return The RibbonManager 097 * @throws Exception If an error occurs 098 */ 099 public synchronized RibbonManager getManager(String uri) throws Exception 100 { 101 RibbonManager ribbonManager = _getRibbonManagerCache().get(uri); 102 if (ribbonManager != null) 103 { 104 if (_isRibbonManagerStillValid(ribbonManager)) 105 { 106 _increaseUsage(ribbonManager); 107 return ribbonManager; 108 } 109 else 110 { 111 _ribbonManagerCacheValidity.remove(ribbonManager); 112 _decreaseUsage(ribbonManager, true); 113 } 114 } 115 116 ribbonManager = _createRibbonManager(); 117 _getRibbonManagerCache().put(uri, ribbonManager); 118 _ribbonManagerCacheValidity.put(ribbonManager, null); 119 _increaseUsage(ribbonManager); 120 121 return ribbonManager; 122 } 123 124 private boolean _isRibbonManagerStillValid(RibbonManager ribbonManager) 125 { 126 Map<String, Long> validity = _ribbonManagerCacheValidity.get(ribbonManager); 127 if (validity == null) 128 { 129 return false; 130 } 131 132 for (Entry<String, Long> entry : validity.entrySet()) 133 { 134 RibbonConfigurationSource ribbonConfigurationEntry = null; 135 try 136 { 137 ribbonConfigurationEntry = RibbonConfigurationSource.createFromUri(entry.getKey(), _resolver); 138 if (!_isRibbonEntryStillValid(entry.getValue(), ribbonConfigurationEntry)) 139 { 140 return false; 141 } 142 } 143 catch (IOException e) 144 { 145 // invalid import, uri is out of date 146 return false; 147 } 148 finally 149 { 150 if (ribbonConfigurationEntry != null) 151 { 152 _resolver.release(ribbonConfigurationEntry.getSource()); 153 } 154 } 155 } 156 157 return true; 158 } 159 160 private boolean _isRibbonEntryStillValid(Long cachedValidity, RibbonConfigurationSource ribbonEntrySource) 161 { 162 boolean entryExists = ribbonEntrySource.getSource().exists(); 163 if (cachedValidity == 0 && entryExists) 164 { 165 // Unable to get "Last Modified" of Entry. Assume changed. 166 return false; 167 } 168 169 if (cachedValidity > 0 && !entryExists) 170 { 171 // Entry was deleted since it was put in cache. 172 return false; 173 } 174 175 // Compare the last modified to the cached "last modified" value, to check for changes 176 return ribbonEntrySource.getLastModified().equals(cachedValidity); 177 } 178 179 private RibbonManager _createRibbonManager() throws Exception 180 { 181 PluginsComponentManager ribbonServiceManager = new PluginsComponentManager(new WrapperComponentManager(_cocoonManager)); 182 ribbonServiceManager.contextualize(_context); 183 ribbonServiceManager.service(_cocoonManager); 184 ribbonServiceManager.setLogger(getLogger()); 185 ribbonServiceManager.addExtensionPoint("core-ui", RibbonManager.ROLE, RibbonManager.class, null, new ArrayList<>()); 186 ribbonServiceManager.initialize(); 187 188 RibbonManager ribbonManager = (RibbonManager) ribbonServiceManager.lookup(RibbonManager.ROLE); 189 ribbonManager.initialize(); 190 191 _ribbonServiceManagers.put(ribbonManager, ribbonServiceManager); 192 193 _ribbonControlsManager.registerRibbonManager(ribbonManager); 194 _ribbonTabsManager.registerRibbonManager(ribbonManager); 195 return ribbonManager; 196 } 197 198 /** 199 * Dispose of a RibbonManager that was previously retrieve with this helper 200 * @param ribbonManager The ribbon manager 201 */ 202 public void dispose(RibbonManager ribbonManager) 203 { 204 if (ribbonManager != null) 205 { 206 _decreaseUsage(ribbonManager, false); 207 } 208 } 209 210 private synchronized void _increaseUsage(RibbonManager ribbonManager) 211 { 212 Integer usage = _ribbonManagerUsageCache.get(ribbonManager); 213 _ribbonManagerUsageCache.put(ribbonManager, usage != null ? usage + 1 : 1); 214 } 215 216 @SuppressWarnings("null") 217 private synchronized void _decreaseUsage(RibbonManager ribbonManager, boolean forceRemove) 218 { 219 Integer usage = _ribbonManagerUsageCache.get(ribbonManager); 220 if (forceRemove || usage != null) 221 { 222 if (!forceRemove && usage > 1) 223 { 224 _ribbonManagerUsageCache.put(ribbonManager, usage - 1); 225 } 226 else 227 { 228 _ribbonManagerUsageCache.remove(ribbonManager); 229 if (!_ribbonManagerCacheValidity.containsKey(ribbonManager)) 230 { 231 _ribbonConfigurationCache.remove(ribbonManager); 232 ThreadSafeComponentManager<Object> serviceManager = _ribbonServiceManagers.get(ribbonManager); 233 _ribbonServiceManagers.remove(ribbonManager); 234 _ribbonControlsManager.unregisterRibbonManager(ribbonManager); 235 _ribbonTabsManager.unregisterRibbonManager(ribbonManager); 236 serviceManager.dispose(); 237 } 238 } 239 } 240 } 241 242 /** 243 * Get the ribbon configuration managed by the RibbonManager 244 * @param ribbonManager The ribbon manager 245 * @return The configuration, or null if no configuration was cached 246 */ 247 public RibbonConfiguration getCachedConfiguration(RibbonManager ribbonManager) 248 { 249 return _ribbonConfigurationCache.get(ribbonManager); 250 } 251 252 /** 253 * Add a ribbon configuration to the cache 254 * @param ribbonManager The ribbon manager 255 * @param configuration The configuration 256 * @param importsValidity The list of imports for this ribbon configuration and their validity 257 */ 258 public void addCachedConfiguration(RibbonManager ribbonManager, RibbonConfiguration configuration, Map<String, Long> importsValidity) 259 { 260 _ribbonConfigurationCache.put(ribbonManager, configuration); 261 _ribbonManagerCacheValidity.put(ribbonManager, importsValidity); 262 } 263 264 private Cache<String, RibbonManager> _getRibbonManagerCache() 265 { 266 return this._cacheManager.get(ROLE); 267 } 268 269}