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}