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.math.BigDecimal; 019import java.math.RoundingMode; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Optional; 027import java.util.Set; 028import java.util.function.Supplier; 029import java.util.stream.Collectors; 030import java.util.stream.Stream; 031 032import org.apache.commons.collections.CollectionUtils; 033import org.apache.commons.lang3.ArrayUtils; 034 035import org.ametys.runtime.plugin.FeatureActivator.PluginsInformation; 036import org.ametys.runtime.plugin.IncludePolicyFeatureActivator.IncludedFeature; 037import org.ametys.runtime.plugin.PluginsManager.InactivityCause; 038import org.ametys.runtime.servlet.RuntimeConfig; 039 040import com.google.common.base.Predicates; 041 042class LoadedFeaturesDump 043{ 044 private AbstractFeatureActivator _featureActivator; 045 046 LoadedFeaturesDump(AbstractFeatureActivator featureActivator) 047 { 048 _featureActivator = featureActivator; 049 } 050 051 String fullDump(PluginsInformation pluginInfo) 052 { 053 Map<String, InactivityCause> inactiveFeatures = pluginInfo.getInactiveFeatures(); 054 Collection<PluginIssue> errors = pluginInfo.getErrors(); 055 056 Collection<String> excludedPlugins = RuntimeConfig.getInstance().getExcludedPlugins(); 057 StringBuilder sb = new StringBuilder(); 058 059 for (String pluginName : _featureActivator._allPlugins.keySet()) 060 { 061 Plugin plugin = _featureActivator._allPlugins.get(pluginName); 062 _fullDumpPlugin(sb, plugin, excludedPlugins, inactiveFeatures); 063 } 064 065 if (!errors.isEmpty()) 066 { 067 sb.append("\nErrors :\n"); 068 errors.forEach(issue -> sb.append(issue.toString()).append('\n')); 069 } 070 071 return sb.toString(); 072 } 073 074 private void _fullDumpPlugin(StringBuilder sb, Plugin plugin, Collection<String> excludedPlugins, Map<String, InactivityCause> inactiveFeatures) 075 { 076 String pluginName = plugin.getName(); 077 sb.append("Plugin ").append(pluginName); 078 079 if (excludedPlugins.contains(pluginName)) 080 { 081 sb.append(" *** excluded ***"); 082 } 083 084 sb.append('\n'); 085 086 Collection<String> configParameters = plugin.getConfigParameters().keySet(); 087 if (!CollectionUtils.isEmpty(configParameters)) 088 { 089 sb.append(" Config parameters : \n"); 090 configParameters.forEach(param -> sb.append(" ").append(param).append('\n')); 091 } 092 093 Collection<String> paramCheckers = plugin.getParameterCheckers().keySet(); 094 if (!CollectionUtils.isEmpty(paramCheckers)) 095 { 096 sb.append(" Parameters checkers : \n"); 097 paramCheckers.forEach(param -> sb.append(" ").append(param).append('\n')); 098 } 099 100 Collection<String> extensionPoints = plugin.getExtensionPoints(); 101 if (!CollectionUtils.isEmpty(extensionPoints)) 102 { 103 sb.append(" Extension points : \n"); 104 extensionPoints.forEach(point -> sb.append(" ").append(point).append('\n')); 105 } 106 107 Map<String, Feature> features = plugin.getFeatures(); 108 for (String featureId : features.keySet()) 109 { 110 Feature feature = features.get(featureId); 111 _fullDumpFeature(sb, feature, inactiveFeatures); 112 } 113 114 sb.append('\n'); 115 } 116 117 private void _fullDumpFeature(StringBuilder sb, Feature feature, Map<String, InactivityCause> inactiveFeatures) 118 { 119 String featureId = feature.getFeatureId(); 120 121 sb.append(" Feature ").append(featureId); 122 if (feature.isPassive()) 123 { 124 sb.append(" (passive)"); 125 } 126 127 if (feature.isSafe()) 128 { 129 sb.append(" (safe)"); 130 } 131 132 if (inactiveFeatures != null && inactiveFeatures.containsKey(featureId)) 133 { 134 sb.append(" *** inactive (").append(inactiveFeatures.get(featureId)).append(") ***"); 135 } 136 137 sb.append('\n'); 138 139 Collection<String> featureConfigParameters = feature.getConfigParameters().keySet(); 140 if (!CollectionUtils.isEmpty(featureConfigParameters)) 141 { 142 sb.append(" Config parameters : \n"); 143 featureConfigParameters.forEach(param -> sb.append(" ").append(param).append('\n')); 144 } 145 146 Collection<String> configParametersReferences = feature.getConfigParametersReferences(); 147 if (!CollectionUtils.isEmpty(configParametersReferences)) 148 { 149 sb.append(" Config parameters references : \n"); 150 configParametersReferences.forEach(param -> sb.append(" ").append(param).append('\n')); 151 } 152 153 Collection<String> featureParamCheckers = feature.getParameterCheckers().keySet(); 154 if (!CollectionUtils.isEmpty(featureParamCheckers)) 155 { 156 sb.append(" Parameters checkers : \n"); 157 featureParamCheckers.forEach(param -> sb.append(" ").append(param).append('\n')); 158 } 159 160 Map<String, String> componentsIds = feature.getComponentsIds(); 161 if (!componentsIds.isEmpty()) 162 { 163 sb.append(" Components : \n"); 164 165 for (String role : componentsIds.keySet()) 166 { 167 String id = componentsIds.get(role); 168 sb.append(" ").append(role).append(" : ").append(id).append('\n'); 169 } 170 171 sb.append('\n'); 172 } 173 174 Map<String, Collection<String>> extensionsIds = feature.getExtensionsIds(); 175 if (!extensionsIds.isEmpty()) 176 { 177 sb.append(" Extensions : \n"); 178 179 for (Entry<String, Collection<String>> extensionEntry : extensionsIds.entrySet()) 180 { 181 String point = extensionEntry.getKey(); 182 Collection<String> ids = extensionEntry.getValue(); 183 184 sb.append(" ").append(point).append(" :\n"); 185 ids.forEach(id -> sb.append(" ").append(id).append('\n')); 186 } 187 188 sb.append('\n'); 189 } 190 } 191 192 String shortDump(PluginsInformation pluginInfo, Collection<IncludedFeature> includedFeatures) 193 { 194 Collection<PluginIssue> errors = pluginInfo.getErrors(); 195 Map<String, InactivityCause> inactiveFeatures = pluginInfo.getInactiveFeatures(); 196 Map<String, Collection<String>> dependencies = _featureActivator.computeIncomingDependencies(pluginInfo.getFeatures()); 197 StringBuilder sb = new StringBuilder(); 198 199 String separatorLine = new String(new char[66]).replace('\0', '-') + "\n"; 200 sb.append(separatorLine); 201 _dumpLoaded(sb, separatorLine, pluginInfo); 202 sb.append(separatorLine); 203 204 List<String> pluginNames = _featureActivator._allPlugins.keySet() 205 .stream() 206 .sorted() 207 .collect(Collectors.toList()); 208 for (String pluginName : pluginNames) 209 { 210 Plugin plugin = _featureActivator._allPlugins.get(pluginName); 211 _shortDumpPlugin(sb, plugin, dependencies, includedFeatures, inactiveFeatures); 212 } 213 214 if (!errors.isEmpty()) 215 { 216 sb.append("\nErrors :\n"); 217 errors.forEach( 218 issue -> sb.append(issue.toString()).append('\n') 219 ); 220 } 221 222 sb.append(separatorLine); 223 224 return sb.toString(); 225 } 226 227 private void _dumpLoaded(StringBuilder sb, String separatorLine, PluginsInformation pluginInfo) 228 { 229 Map<String, Feature> loadedFeatures = pluginInfo.getFeatures(); 230 int nbLoadedFeatures = loadedFeatures.size(); 231 long nbLoadedComponents = loadedFeatures.values() 232 .parallelStream() 233 .map(Feature::getComponents) 234 .map(Map::values) 235 .flatMap(Collection::parallelStream) 236 .count(); 237 long nbLoadedExtensions = loadedFeatures.values() 238 .parallelStream() 239 .map(Feature::getExtensions) 240 .map(Map::values) 241 .flatMap(Collection::parallelStream) 242 .map(Map::values) 243 .flatMap(Collection::parallelStream) 244 .count(); 245 246 int nbInactiveFeatures = pluginInfo.getInactiveFeatures().size(); 247 Set<String> inactiveFeatureIds = pluginInfo.getInactiveFeatures().keySet(); 248 Supplier<Stream<Feature>> inactiveFeatures = () -> _featureActivator._allPlugins 249 .values() 250 .stream() 251 .map(Plugin::getFeatures) 252 .map(Map::values) 253 .flatMap(Collection::parallelStream) 254 .filter(Predicates.compose(inactiveFeatureIds::contains, Feature::getFeatureId)); 255 long nbInactiveComponents = inactiveFeatures.get() 256 .map(Feature::getComponentsIds) 257 .mapToInt(Map::size) 258 .sum(); 259 long nbInactiveExtensions = inactiveFeatures.get() 260 .map(Feature::getExtensionsIds) 261 .map(Map::values) 262 .flatMap(Collection::parallelStream) 263 .flatMap(Collection::parallelStream) 264 .count(); 265 266 _addCell(sb, "\\"); 267 _addCell(sb, "loaded"); 268 _addCell(sb, "inactive"); 269 _addCell(sb, "total"); 270 _addCell(sb, "load factor"); 271 sb.append("|\n"); 272 sb.append(separatorLine); 273 _addCell(sb, "features"); 274 _addCell(sb, String.valueOf(nbLoadedFeatures)); 275 _addCell(sb, String.valueOf(nbInactiveFeatures)); 276 int nbTotalFeatures = nbLoadedFeatures + nbInactiveFeatures; 277 _addCell(sb, String.valueOf(nbTotalFeatures)); 278 _addCell(sb, new BigDecimal((double) nbLoadedFeatures / (double) nbTotalFeatures).setScale(3, RoundingMode.HALF_UP)); 279 sb.append("|\n"); 280 sb.append(separatorLine); 281 _addCell(sb, "components"); 282 _addCell(sb, String.valueOf(nbLoadedComponents)); 283 _addCell(sb, String.valueOf(nbInactiveComponents)); 284 long nbTotalComponents = nbLoadedComponents + nbInactiveComponents; 285 _addCell(sb, String.valueOf(nbTotalComponents)); 286 _addCell(sb, new BigDecimal((double) nbLoadedComponents / (double) nbTotalComponents).setScale(3, RoundingMode.HALF_UP)); 287 sb.append("|\n"); 288 sb.append(separatorLine); 289 _addCell(sb, "extensions"); 290 _addCell(sb, String.valueOf(nbLoadedExtensions)); 291 _addCell(sb, String.valueOf(nbInactiveExtensions)); 292 long nbTotalExtensions = nbLoadedExtensions + nbInactiveExtensions; 293 _addCell(sb, String.valueOf(nbTotalExtensions)); 294 _addCell(sb, new BigDecimal((double) nbLoadedExtensions / (double) nbTotalExtensions).setScale(3, RoundingMode.HALF_UP)); 295 sb.append("|\n"); 296 } 297 298 private void _addCell(StringBuilder sb, Object content) 299 { 300 final int limit = 12; 301 String limitedContent = String.valueOf(content); 302 if (limitedContent.length() >= limit) 303 { 304 limitedContent = limitedContent.substring(0, limit); 305 } 306 else 307 { 308 int missing = limit - limitedContent.length(); 309 int half = missing / 2; 310 char[] characters = new char[limit]; 311 Arrays.fill(characters, ' '); 312 int startPosTarget = missing - half; 313 System.arraycopy(limitedContent.toCharArray(), 0, characters, startPosTarget, limitedContent.length()); 314 limitedContent = new String(characters); 315 } 316 sb.append("|"); 317 sb.append(limitedContent); 318 } 319 320 private void _shortDumpPlugin( 321 StringBuilder sb, 322 Plugin plugin, 323 Map<String, Collection<String>> dependencies, 324 Collection<IncludedFeature> includedFeatures, 325 Map<String, InactivityCause> inactiveFeatures) 326 { 327 String pluginName = plugin.getName(); 328 sb.append("Plugin ").append(pluginName); 329 330 sb.append("\n\n"); 331 332 Map<String, Feature> activeFeatures = plugin.getFeatures(); 333 List<String> featureIds = activeFeatures.keySet() 334 .stream() 335 .sorted() 336 .collect(Collectors.toList()); 337 for (String featureId : featureIds) 338 { 339 Feature feature = activeFeatures.get(featureId); 340 _shortDumpFeature(sb, feature, dependencies, includedFeatures, inactiveFeatures); 341 } 342 343 sb.append('\n'); 344 } 345 346 private void _shortDumpFeature( 347 StringBuilder sb, 348 Feature feature, 349 Map<String, Collection<String>> dependencies, 350 Collection<IncludedFeature> includedFeatures, 351 Map<String, InactivityCause> inactiveFeatures) 352 { 353 String featureId = feature.getFeatureId(); 354 355 if (!inactiveFeatures.containsKey(featureId)) 356 { 357 sb.append(" Feature ").append(featureId); 358 List<String> dependenciesOfFeature = new ArrayList<>(); 359 if (dependencies.containsKey(featureId)) 360 { 361 dependenciesOfFeature.addAll(dependencies.get(featureId)); 362 } 363 Optional<String> causes = _findIncludedFeatureCauses(featureId, includedFeatures); 364 if (causes.isPresent()) 365 { 366 dependenciesOfFeature.add(0, "INCLUDED_FEATURE BY " + causes.get()); 367 } 368 if (ArrayUtils.contains(IncludePolicyFeatureActivator.__KERNEL_DEPENDENCIES_NEEDED, featureId)) 369 { 370 dependenciesOfFeature.add(0, "KERNEL"); 371 } 372 373 if (!dependenciesOfFeature.isEmpty()) 374 { 375 sb.append("\n --> brought by ").append(_dependenciesToString(dependenciesOfFeature)); 376 } 377 378 sb.append("\n\n"); 379 } 380 } 381 382 private String _dependenciesToString(List<String> dependenciesOfFeature) 383 { 384 switch (dependenciesOfFeature.size()) 385 { 386 case 0: 387 case 1: 388 // display it on one line 389 return dependenciesOfFeature.toString(); 390 default: 391 // one item of the list perl ine to be readable (+ handle indentation) 392 final String indent1 = " "; 393 final String indent2 = indent1 + " "; 394 return "[\n" + indent2 + String.join(",\n" + indent2, dependenciesOfFeature) + "\n" + indent1 + "]"; 395 } 396 } 397 398 private Optional<String> _findIncludedFeatureCauses(String featureId, Collection<IncludedFeature> includedFeatures) 399 { 400 List<String> causes = includedFeatures.stream() 401 .filter(Predicates.compose(Predicates.equalTo(featureId), IncludedFeature::featureId)) 402 .map(IncludedFeature::cause) 403 .collect(Collectors.toList()); 404 switch (causes.size()) 405 { 406 case 0: 407 return Optional.empty(); 408 case 1: 409 return Optional.of(causes.get(0)); 410 default: 411 return Optional.of("{" + String.join(" & ", causes) + "}"); 412 } 413 } 414}