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.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Map.Entry;
023
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029/**
030 * Dependencies manager, able to compute the full chain of dependencies. 
031 */
032public class ClientSideElementDependenciesManager
033{
034    /** Logger */
035    protected Logger _logger = LoggerFactory.getLogger(ClientSideElementDependenciesManager.class);
036    
037    private Map<String, List<String>> _dependencies;
038    private ServiceManager _manager;
039
040    /**
041     * Default constructor for the dependencies manager.
042     * @param manager The service manager, used to resolve dependencies.
043     */
044    public ClientSideElementDependenciesManager(ServiceManager manager)
045    {
046        _dependencies = new HashMap<>();
047        _manager = manager;
048    }
049    
050    /**
051     * Register a new dependency
052     * @param extensionPoint The dependency extension point
053     * @param extensionId The dependency extension
054     */
055    public void register(String extensionPoint, String extensionId)
056    {
057        if (!_dependencies.containsKey(extensionPoint))
058        {
059            _dependencies.put(extensionPoint, new ArrayList<>());
060        }
061        
062        List<String> extensions = _dependencies.get(extensionPoint);
063        
064        if (!extensions.contains(extensionId))
065        {
066            extensions.add(extensionId);
067        }
068    }
069    
070    /**
071     * Register a new dependency to a client side element
072     * @param element The client side element
073     */
074    public void register(ClientSideElement element)
075    {
076        Map<String, List<String>> dependencies = element.getDependencies();
077        
078        for (Entry<String, List<String>> entry : dependencies.entrySet())
079        {
080            String extensionPoint = entry.getKey();
081            
082            for (String extensionId : entry.getValue())
083            {
084                if (_logger.isDebugEnabled())
085                {
086                    _logger.debug("Register dependency : The extension '" + element.getId() + "' depends on '" + extensionId + "' of extension point '" + extensionPoint + "')");
087                }
088                
089                register(extensionPoint, extensionId);
090            }
091        }
092    }
093    
094    /**
095     * Compute the chain of dependencies
096     * @return The list of dynamic dependencies calculated from the registered dependencies.
097     * @throws ServiceException If an error occurs with the list of ExtensionPoints
098     */
099    public Map<String, List<ClientSideElement>> computeDependencies() throws ServiceException
100    {
101        Map<String, List<ClientSideElement>> computedDependencies = new HashMap<>();
102        computeDependencies(computedDependencies, _dependencies, new ArrayList<>());
103        return computedDependencies;
104    }
105    
106    /**
107     * Recursively Compute the chain of dependency.
108     * @param computedDependencies The list of dependencies that have already been computed from the chain. This map is filled with the full dependencies chain.
109     * @param dependenciesToProcess The list of extensions to parse, mapped by extension points, that can have additional dependencies.
110     * @param knownElements The list of elements that were already handled (to avoid infinite loop)
111     * @throws ServiceException If an error occurs
112     */
113    private void computeDependencies(Map<String, List<ClientSideElement>> computedDependencies, Map<String, List<String>> dependenciesToProcess, List<ClientSideElement> knownElements) throws ServiceException
114    {
115        for (Entry<String, List<String>> dependencyToProcess : dependenciesToProcess.entrySet())
116        {
117            String extensionPointId = dependencyToProcess.getKey();
118            @SuppressWarnings("unchecked")
119            AbstractClientSideExtensionPoint<ClientSideElement> extensionPoint = (AbstractClientSideExtensionPoint<ClientSideElement>) _manager.lookup(extensionPointId);
120            
121            if (!computedDependencies.containsKey(extensionPointId))
122            {
123                computedDependencies.put(extensionPointId, new ArrayList<>());
124            }
125            
126            for (String extensionIdToProcess : dependencyToProcess.getValue())
127            {
128                for (ClientSideElement extension : extensionPoint.findDependency(extensionIdToProcess))
129                {
130                    if (extension != null && !knownElements.contains(extension))
131                    {
132                        // only process extension once
133                        knownElements.add(extension);
134                        
135                        Map<String, List<String>> dependencies = extension.getDependencies();
136                        
137                        if (_logger.isDebugEnabled())
138                        {
139                            _logger.debug("Computing dependencies : For extension point '" + extensionPointId + "', the extension : '" + extensionIdToProcess + "' requires the following dependencies : " + dependencies.toString());
140                        }
141                        
142                        // Fill the computedDependencies list recursively, with the dependency's own dependencies.
143                        computeDependencies(computedDependencies, dependencies, knownElements);
144    
145                        computedDependencies.get(extensionPointId).add(extension);
146                    }
147                }
148            }
149        }
150    }
151}