001/*
002 *  Copyright 2019 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.runtime.plugin;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.List;
021import java.util.Map;
022import java.util.concurrent.ConcurrentHashMap;
023import java.util.stream.Collectors;
024import java.util.stream.Stream;
025
026import org.apache.commons.collections4.CollectionUtils;
027import org.slf4j.Logger;
028
029import org.ametys.runtime.plugin.IncomingDeactivation.Type;
030
031class CorrectedDependencies
032{
033    private Map<String, Feature> _allFeatures;
034    private Map<Feature, Collection<String>> _correctedDependencies = new ConcurrentHashMap<>();
035    private Map<String, Collection<IncomingDeactivation>> _incomingDeactivations;
036    private Logger _logger;
037    
038    CorrectedDependencies(Map<String, Feature> allFeatures, Map<String, Collection<IncomingDeactivation>> incomingDeactivations, Logger logger)
039    {
040        _allFeatures = allFeatures;
041        _incomingDeactivations = incomingDeactivations;
042        _logger = logger;
043        _computeCorrectedDependencies();
044    }
045    
046    private void _computeCorrectedDependencies()
047    {
048        for (Feature feature : _allFeatures.values())
049        {
050            _correctedDependencies.put(feature, _computeCorrectedDependencies(feature));
051        }
052    }
053    
054    Collection<String> getCorrectedDependencies(Feature feature)
055    {
056        return _correctedDependencies.get(feature);
057    }
058    
059    // getDependencies taking account of deactivations
060    private Collection<String> _computeCorrectedDependencies(Feature feature)
061    {
062        Collection<String> dependencies = feature.getDependencies();
063        // If we have for instance feature 'overrider' which overrides feature 'overridden' 
064        // _incomingDeactivations={'overridden'=[IncomingDeactivation('overrider', OVERRIDDEN)], ...}
065        // 
066        // And if dependencies contains 'overridden', then it must be replaced with 'overrider'
067        List<String> correctedDependencies = dependencies
068                .stream()
069                .map(dependency -> _maybeReplaceDependencyByDeactivators(dependency, feature))
070                .flatMap(Collection::stream)
071                .collect(Collectors.toList());
072        return correctedDependencies;
073    }
074    
075    private Collection<String> _maybeReplaceDependencyByDeactivators(String dependency, Feature originalFeature)
076    {
077        Collection<IncomingDeactivation> deactivators = _incomingDeactivations.get(dependency);
078        if (CollectionUtils.isEmpty(deactivators))
079        {
080            // nobody deactivates or overrides it => keep the dependency
081            return Collections.singleton(dependency);
082        }
083        else if (_hasDeactivatorsAndNoOverride(deactivators))
084        {
085            _logger.debug("Dependency '{}' (of feature '{}') is deactivated by one or more feature(s) ({}). Nothing special made.", 
086                    dependency, originalFeature.getFeatureId(), deactivators);
087            return Collections.singleton(dependency);
088        }
089        else
090        {
091            // AbstractFeatureActivator._checkNoMultipleOverriders ensures that deactivators contains at most 1 overrider
092            // Thus here (with previous if statements), deactivators contains ONE AND ONLY ONE overrider
093            
094            // replace the dependency by its unique feature which overrides it
095            String overrider = _getOverride(deactivators);
096            _logger.info("Dependency '{}' (of feature '{}') is overridden by one feature, it is replaced by it: '{}'.", 
097                    dependency, originalFeature.getFeatureId(), overrider);
098            Collection<String> recursiveOverride = _maybeReplaceDependencyByDeactivators(overrider, _allFeatures.get(overrider));
099            if (CollectionUtils.isNotEmpty(recursiveOverride))
100            {
101                overrider = recursiveOverride.iterator().next();
102            }
103            return Collections.singleton(overrider);
104        }
105    }
106    
107    private String _getOverride(Collection<IncomingDeactivation> deactivators)
108    {
109        return _onlyOverriden(deactivators)
110                .findAny()
111                .map(IncomingDeactivation::getFeatureId)
112                .orElseThrow();
113    }
114    
115    private Stream<IncomingDeactivation> _onlyOverriden(Collection<IncomingDeactivation> deactivators)
116    {
117        return deactivators.stream()
118                .filter(deactivator -> deactivator.getType() == Type.OVERRIDDEN);
119    }
120    
121    private boolean _hasDeactivatorsAndNoOverride(Collection<IncomingDeactivation> deactivators)
122    {
123        return _onlyOverriden(deactivators)
124                .findAny()
125                .isEmpty();
126    }
127    
128    @Override
129    public String toString()
130    {
131        return _correctedDependencies.entrySet()
132                .stream()
133                .collect(Collectors.toMap(
134                    e -> e.getKey().getFeatureId(), 
135                    e -> e.getValue()))
136                .toString();
137    }
138}