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}