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.component.Component;
025import org.apache.avalon.framework.component.WrapperComponentManager;
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceResolver;
034
035import org.ametys.core.ui.ribbonconfiguration.RibbonConfiguration;
036import org.ametys.runtime.plugin.component.AbstractLogEnabled;
037import org.ametys.runtime.plugin.component.PluginsComponentManager;
038import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
039
040/**
041 * Helper for RibbonManager, that support Thread safe usage of the managers, while implementing a cache for performances.
042 */
043public class RibbonManagerCache extends AbstractLogEnabled implements Contextualizable, Serviceable, Component
044{
045    /** Avalon role */
046    public static final String ROLE = RibbonManagerCache.class.getName();
047    
048    private Map<String, RibbonManager> _ribbonManagerCache = new HashMap<>();
049    private Map<RibbonManager, Map<String, Long>> _ribbonManagerCacheValidity = new HashMap<>();
050    private Map<RibbonManager, Integer> _ribbonManagerUsageCache = new HashMap<>();
051    private Map<RibbonManager, RibbonConfiguration> _ribbonConfigurationCache = new HashMap<>();
052    private Map<RibbonManager, ThreadSafeComponentManager<Object>> _ribbonServiceManagers = new HashMap<>();
053    
054    private Context _context;
055
056    private ServiceManager _cocoonManager;
057
058    private RibbonControlsManager _ribbonControlsManager;
059
060    private RibbonTabsManager _ribbonTabsManager;
061    
062    private SourceResolver _resolver;
063    
064    public void contextualize(Context context) throws ContextException
065    {
066        _context = context;
067    }
068    
069    public void service(ServiceManager manager) throws ServiceException
070    {
071        _cocoonManager = manager;
072        _ribbonControlsManager = (RibbonControlsManager) manager.lookup(RibbonControlsManager.ROLE);
073        _ribbonTabsManager = (RibbonTabsManager) manager.lookup(RibbonTabsManager.ROLE);
074        _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
075    }
076    
077    /**
078     * Create the RibbonManager associated with the given ribbon file. 
079     * If the RibbonManager already exists for the ribbon file and is still valid, it is simply returned.  
080     * @param uri The ribbon configuration uri
081     * @return The RibbonManager
082     * @throws Exception If an error occurs
083     */
084    public synchronized RibbonManager getManager(String uri) throws Exception
085    {
086        RibbonManager ribbonManager = _ribbonManagerCache.get(uri);
087        if (ribbonManager != null)
088        {
089            if (_isRibbonManagerStillValid(ribbonManager))
090            {
091                _increaseUsage(ribbonManager);
092                return ribbonManager;
093            }
094            else
095            {
096                _ribbonManagerCacheValidity.remove(ribbonManager);
097                _decreaseUsage(ribbonManager, true);
098            }
099        }
100        
101        ribbonManager = _createRibbonManager();
102        _ribbonManagerCache.put(uri, ribbonManager);
103        _ribbonManagerCacheValidity.put(ribbonManager, null);
104        _increaseUsage(ribbonManager);
105        
106        return ribbonManager;
107    }
108
109    private boolean _isRibbonManagerStillValid(RibbonManager ribbonManager)
110    {
111        Map<String, Long> validity = _ribbonManagerCacheValidity.get(ribbonManager);
112        if (validity == null)
113        {
114            return false;
115        }
116        
117        for (Entry<String, Long> entry : validity.entrySet())
118        {
119            String uri = entry.getKey();
120            try
121            {
122                Source importSource = _resolver.resolveURI(uri);
123                if (entry.getValue() == 0 && importSource.exists()
124                        || entry.getValue() != 0 && !importSource.exists()
125                        || importSource.getLastModified() != entry.getValue())
126                {
127                    return false;
128                }
129            }
130            catch (IOException e)
131            {
132                // invalid import, uri is out of date
133                return false;
134            }
135        }
136        
137        return true;
138    }
139
140    private RibbonManager _createRibbonManager() throws Exception
141    {
142        PluginsComponentManager ribbonServiceManager = new PluginsComponentManager(new WrapperComponentManager(_cocoonManager));
143        ribbonServiceManager.contextualize(_context);
144        ribbonServiceManager.service(_cocoonManager);
145        ribbonServiceManager.setLogger(getLogger());
146        ribbonServiceManager.addExtensionPoint("core-ui", RibbonManager.ROLE, RibbonManager.class, null, new ArrayList<>());
147        ribbonServiceManager.initialize();
148        
149        RibbonManager ribbonManager = (RibbonManager) ribbonServiceManager.lookup(RibbonManager.ROLE);
150        ribbonManager.initialize();
151
152        _ribbonServiceManagers.put(ribbonManager, ribbonServiceManager);
153        
154        _ribbonControlsManager.registerRibbonManager(ribbonManager);
155        _ribbonTabsManager.registerRibbonManager(ribbonManager);
156        return ribbonManager;
157    }
158    
159    /**
160     * Dispose of a RibbonManager that was previously retrieve with this helper
161     * @param ribbonManager The ribbon manager
162     */
163    public void dispose(RibbonManager ribbonManager)
164    {
165        if (ribbonManager != null)
166        {
167            _decreaseUsage(ribbonManager, false);
168        }
169    }
170
171    private synchronized void _increaseUsage(RibbonManager ribbonManager)
172    {
173        Integer usage = _ribbonManagerUsageCache.get(ribbonManager);
174        _ribbonManagerUsageCache.put(ribbonManager, usage != null ? usage + 1 : 1);
175    }
176    
177    @SuppressWarnings("null")
178    private synchronized void _decreaseUsage(RibbonManager ribbonManager, boolean forceRemove)
179    {
180        Integer usage = _ribbonManagerUsageCache.get(ribbonManager);
181        if (forceRemove || usage != null)
182        {
183            if (!forceRemove && usage > 1)
184            {
185                _ribbonManagerUsageCache.put(ribbonManager, usage - 1);
186            }
187            else
188            {
189                _ribbonManagerUsageCache.remove(ribbonManager);
190                if (!_ribbonManagerCacheValidity.containsKey(ribbonManager))
191                {
192                    _ribbonConfigurationCache.remove(ribbonManager);
193                    ThreadSafeComponentManager<Object> serviceManager = _ribbonServiceManagers.get(ribbonManager);
194                    _ribbonServiceManagers.remove(ribbonManager);
195                    _ribbonControlsManager.unregisterRibbonManager(ribbonManager);
196                    _ribbonTabsManager.unregisterRibbonManager(ribbonManager);
197                    serviceManager.dispose();
198                }
199            }
200        }
201    }
202
203    /**
204     * Get the ribbon configuration managed by the RibbonManager
205     * @param ribbonManager The ribbon manager
206     * @return The configuration, or null if no configuration was cached
207     */
208    public RibbonConfiguration getCachedConfiguration(RibbonManager ribbonManager)
209    {
210        return _ribbonConfigurationCache.get(ribbonManager);
211    }
212    
213    /**
214     * Add a ribbon configuration to the cache
215     * @param ribbonManager The ribbon manager
216     * @param configuration The configuration
217     * @param importsValidity The list of imports for this ribbon configuration and their validity
218     */
219    public void addCachedConfiguration(RibbonManager ribbonManager, RibbonConfiguration configuration, Map<String, Long> importsValidity)
220    {
221        _ribbonConfigurationCache.put(ribbonManager, configuration);
222        _ribbonManagerCacheValidity.put(ribbonManager, importsValidity);
223    }
224}