001/* 002 * Copyright 2018 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.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026import java.util.stream.Collectors; 027import java.util.stream.Stream; 028 029import org.apache.commons.collections4.ListUtils; 030 031import org.ametys.runtime.plugin.PluginsManager.InactivityCause; 032import org.ametys.runtime.plugin.PluginsManager.InactivityStatus; 033 034/** 035 * {@link FeatureActivator} which activates only passed included features and their dependencies. 036 */ 037public final class IncludePolicyFeatureActivator extends AbstractFeatureActivator 038{ 039 static String[] __KERNEL_DEPENDENCIES_NEEDED = new String[] 040 { 041 "core/core.observation", // CocoonWrapper needs ObservationManager 042 "core/core.migration" // CocoonWrapper needs MigrationEngine 043 }; 044 045 private Collection<IncludedFeature> _includedFeatures; 046 047 /** 048 * Constructs a new feature activator with an 'include' policy 049 * @param allPlugins all plugins 050 * @param includedFeatures the features to include 051 */ 052 public IncludePolicyFeatureActivator(Map<String, Plugin> allPlugins, Collection<IncludedFeature> includedFeatures) 053 { 054 super(allPlugins); 055 _includedFeatures = includedFeatures; 056 } 057 058 @Override 059 public PluginsInformation computeActiveFeatures(Map<String, String> componentsConfig, boolean safeMode) 060 { 061 _safeMode = safeMode; 062 Map<String, Feature> initialFeatures = new HashMap<>(); 063 Map<String, ExtensionPointDefinition> extensionPoints = new HashMap<>(); 064 Map<String, InactivityStatus> inactiveFeatures = new HashMap<>(); 065 Collection<PluginIssue> errors = new ArrayList<>(); 066 067 // Get actual plugin list, corresponding extension points and initial feature list 068 Map<String, Plugin> plugins = computeActivePlugins(Collections.emptySet(), initialFeatures, inactiveFeatures, extensionPoints, errors); 069 070 removeWrongPointReferences(initialFeatures, inactiveFeatures, extensionPoints, errors); 071 072 Map<String, Collection<IncomingDeactivation>> incomingDeactivations = computeIncomingDeactivations(initialFeatures); 073 074 // check consistency of given included features (it can lead to a paradox if one of them triggers by its dependencies the deactivation of another one) 075 new IncludedFeatureConsistencyChecker(_includedFeatures, initialFeatures, incomingDeactivations).checkConsistency(); 076 077 _correctedDependencies = new CorrectedDependencies(initialFeatures, incomingDeactivations, _logger); 078 079 // Process outgoing dependencies 080 Map<String, Feature> activeFeatures = processOutgoingDependencies(initialFeatures, inactiveFeatures, errors); 081 082 // Compute outgoing dependencies 083 Map<String, Collection<String>> outgoingDependencies = computeOutgoingDependencies(activeFeatures); 084 085 // Get included features, get its dependencies, remove unused features, deactivate features and finally remove dependencies becoming unused 086 List<String> includedFeatures = _getIncludedFeatures(); 087 List<String> dependencies = _getDependencies(includedFeatures, outgoingDependencies); 088 List<String> includedFeaturesAndDependencies = ListUtils.union(includedFeatures, dependencies); 089 _removeUnusedFeatures(activeFeatures, inactiveFeatures, includedFeaturesAndDependencies); 090 Map<String, Collection<IncomingDeactivation>> featuresToDeactivate = _getFeaturesToDeactivate(incomingDeactivations, includedFeaturesAndDependencies); 091 List<String> dependenciesNowUnused = _computeDependenciesNowUnused(featuresToDeactivate.keySet(), outgoingDependencies, includedFeatures, activeFeatures); 092 _removeDeactivatedFeatures(activeFeatures, inactiveFeatures, featuresToDeactivate, dependenciesNowUnused); 093 094 // Check uniqueness of extensions and components 095 Map<String, Map<String, ExtensionDefinition>> extensions = computeExtensions(activeFeatures, errors); 096 Map<String, ComponentDefinition> components = computeComponents(activeFeatures, componentsConfig, errors); 097 098 return new PluginsInformation(plugins, activeFeatures, inactiveFeatures, extensionPoints, extensions, components, errors); 099 } 100 101 private List<String> _getIncludedFeatures() 102 { 103 return Stream.concat( 104 Stream.of(__KERNEL_DEPENDENCIES_NEEDED), 105 _includedFeatures.stream() 106 .map(IncludedFeature::featureId) 107 ) 108 .collect(Collectors.toList()); 109 } 110 111 private List<String> _getDependencies(List<String> featuresToProcess, Map<String, Collection<String>> outgoingDependencies) 112 { 113 List<String> dependencies = new ArrayList<>(); 114 _fillRecursiveDependencies(featuresToProcess, outgoingDependencies, dependencies, false); 115 return dependencies; 116 } 117 118 private void _fillRecursiveDependencies(Collection<String> featuresToProcess, Map<String, Collection<String>> outgoingDependencies, List<String> dependencyListToFill, boolean addFeaturesToProcess) 119 { 120 for (String featureToProcess : featuresToProcess) 121 { 122 if (addFeaturesToProcess) 123 { 124 dependencyListToFill.add(featureToProcess); 125 } 126 Collection<String> deps = outgoingDependencies.get(featureToProcess); 127 if (deps != null) 128 { 129 _fillRecursiveDependencies(deps, outgoingDependencies, dependencyListToFill, true); 130 } 131 } 132 } 133 134 private void _removeUnusedFeatures( 135 Map<String, Feature> activeFeatures, 136 Map<String, InactivityStatus> inactiveFeatures, 137 Collection<String> featuresToKeep) 138 { 139 Set<String> ids = activeFeatures.keySet(); 140 Iterator<String> it = ids.iterator(); 141 while (it.hasNext()) 142 { 143 String id = it.next(); 144 145 if (!featuresToKeep.contains(id)) 146 { 147 _logger.info("Remove unused feature '{}'", id); 148 it.remove(); 149 if (!inactiveFeatures.containsKey(id)) 150 { 151 inactiveFeatures.put(id, new InactivityStatus(InactivityCause.UNUSED, null)); 152 } 153 } 154 } 155 } 156 157 private Map<String, Collection<IncomingDeactivation>> _getFeaturesToDeactivate(Map<String, Collection<IncomingDeactivation>> incomingDeactivations, List<String> includedFeatures) 158 { 159 Map<String, Collection<IncomingDeactivation>> result = new HashMap<>(); 160 for (String id : includedFeatures) 161 { 162 if (incomingDeactivations.containsKey(id)) 163 { 164 Collection<IncomingDeactivation> deactivatedBy = incomingDeactivations.get(id); 165 if (IncomingDeactivation.containsAny(includedFeatures, deactivatedBy)) 166 { 167 result.put(id, IncomingDeactivation.intersection(includedFeatures, deactivatedBy)); 168 } 169 } 170 } 171 return result; 172 } 173 174 private List<String> _computeDependenciesNowUnused( 175 Collection<String> featuresToDeactivate, 176 Map<String, Collection<String>> outgoingDependencies, 177 List<String> includedFeatures, 178 Map<String, Feature> allFeatures) 179 { 180 List<String> dependenciesNowUnused = new ArrayList<>(); 181 182 Map<String, Collection<String>> incomingDependencies = computeIncomingDependencies(allFeatures); 183 for (String featureToDeactivate : featuresToDeactivate) 184 { 185 Collection<String> dependencies = outgoingDependencies.get(featureToDeactivate); 186 // Check it is only needed by the feature which will be deactivated 187 for (String dependency : dependencies) 188 { 189 Collection<String> incomingDependency = incomingDependencies.get(dependency); 190 if (incomingDependency.size() == 1 && incomingDependency.iterator().next().equals(featureToDeactivate) && !includedFeatures.contains(dependency)) 191 { 192 dependenciesNowUnused.add(dependency); 193 } 194 } 195 } 196 197 return dependenciesNowUnused; 198 } 199 200 private void _removeDeactivatedFeatures(Map<String, Feature> activeFeatures, Map<String, InactivityStatus> inactiveFeatures, Map<String, Collection<IncomingDeactivation>> featuresToDeactivate, List<String> dependenciesNowUnused) 201 { 202 Iterator<String> it = activeFeatures.keySet().iterator(); 203 while (it.hasNext()) 204 { 205 String id = it.next(); 206 if (featuresToDeactivate.containsKey(id)) 207 { 208 Collection<IncomingDeactivation> deactivatedBy = featuresToDeactivate.get(id); 209 _logger.debug("Remove feature {} deactivated by features {}", id, deactivatedBy); 210 it.remove(); 211 InactivityCause cause = _getCause(deactivatedBy); 212 Collection<String> sources = deactivatedBy.stream().map(IncomingDeactivation::getFeatureId).toList(); 213 inactiveFeatures.put(id, new InactivityStatus(cause, sources)); 214 } 215 else if (dependenciesNowUnused.contains(id)) 216 { 217 _logger.debug("Remove unused dependency '{}'", id); 218 it.remove(); 219 if (!inactiveFeatures.containsKey(id)) 220 { 221 inactiveFeatures.put(id, new InactivityStatus(InactivityCause.UNUSED, null)); 222 } 223 } 224 } 225 } 226 227 @Override 228 public String shortDump(PluginsInformation pluginInfo) 229 { 230 return new LoadedFeaturesDump(this).shortDump(pluginInfo, _includedFeatures); 231 } 232 233 /** 234 * A feature to include by {@link IncludePolicyFeatureActivator} 235 */ 236 public static final class IncludedFeature 237 { 238 private String _featureId; 239 private String _cause; 240 241 private IncludedFeature(String featureId, String cause) 242 { 243 _featureId = featureId; 244 _cause = cause; 245 } 246 247 /** 248 * Creates an {@link IncludedFeature} 249 * @param featureId The id of the feature 250 * @param cause The cause of its inclusion 251 * @return the feature to include 252 */ 253 public static IncludedFeature of(String featureId, String cause) 254 { 255 return new IncludedFeature(featureId, cause); 256 } 257 258 /** 259 * Gets the feature id 260 * @return the feature id 261 */ 262 public String featureId() 263 { 264 return _featureId; 265 } 266 267 /** 268 * Gets the cause of the inclusion of the feature 269 * @return the cause of the inclusion of the feature 270 */ 271 public String cause() 272 { 273 return _cause; 274 } 275 276 @Override 277 public String toString() 278 { 279 return String.format("%s [included by %s]", _featureId, _cause); 280 } 281 } 282}