| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.dm.shell; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.Dictionary; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.StringTokenizer; |
| |
| import org.apache.felix.dm.Component; |
| import org.apache.felix.dm.ComponentDeclaration; |
| import org.apache.felix.dm.ComponentDependencyDeclaration; |
| import org.apache.felix.dm.DependencyManager; |
| import org.apache.felix.dm.diagnostics.CircularDependency; |
| import org.apache.felix.dm.diagnostics.DependencyGraph; |
| import org.apache.felix.dm.diagnostics.DependencyGraph.ComponentState; |
| import org.apache.felix.dm.diagnostics.DependencyGraph.DependencyState; |
| import org.apache.felix.dm.diagnostics.MissingDependency; |
| import org.apache.felix.service.command.CommandSession; |
| import org.apache.felix.service.command.Descriptor; |
| import org.apache.felix.service.command.Parameter; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Filter; |
| import org.osgi.framework.InvalidSyntaxException; |
| |
| /** |
| * Shell command for showing all services and dependencies that are managed |
| * by the dependency manager. |
| * |
| * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| */ |
| @Descriptor("Commands used to dump all existing Dependency Manager components") |
| public class DMCommand { |
| /** |
| * Bundle context used to create OSGi filters. |
| */ |
| private final BundleContext m_context; |
| |
| /** |
| * Comparator used to compare component declarations based on their bundle ids |
| */ |
| private static final ComponentDeclarationComparator COMPONENT_DECLARATION_COMPARATOR = new ComponentDeclarationComparator(); |
| |
| /** |
| * Constant used by the wtf command, when listing missing services. |
| */ |
| private static final String SERVICE = "service"; |
| |
| /** |
| * Constant used by the wtf command, when listing missing configurations. |
| */ |
| private static final String CONFIGURATION = "configuration"; |
| |
| /** |
| * Constant used by the wtf command, when listing missing resource dependencies |
| */ |
| private static final String RESOURCE = "resource"; |
| |
| /** |
| * Constant used by the wtf command, when listing missing bundle dependencies |
| */ |
| private static final String BUNDLE = "bundle"; |
| |
| /** |
| * Name of a specific gogo shell variable, which may be used to configure "compact" mode. |
| * Example: g! dependencymanager.compact=true |
| */ |
| private final static String ENV_COMPACT = "dependencymanager.compact"; |
| |
| /** |
| * Name of a specific gogo shell variable, which may be used to configure an OSGi filter, normally |
| * passed to the "dm services" option. It is used to display only some service providing components |
| * matching the given filter. The filter can contain an "objectClass" option. |
| * Example: |
| * g! dependencymanager.services="(protocol=http)" |
| * g! dependencymanager.services="(&(objectClass=foo.Bar)(protocol=http))" |
| */ |
| private final static String ENV_SERVICES = "dependencymanager.services"; |
| |
| /** |
| * Name of a specific gogo shell variable, which may be used to configure a filter on the |
| * component implementation class name. |
| * The value of this shell variable may contain multiple regex (space separated), and each regex can |
| * be negated using "!". |
| * Example: g! dependencymanager.components="foo.bar.* ga.bu.zo.*" |
| */ |
| private final static String ENV_COMPONENTS = "dependencymanager.components"; |
| |
| /** |
| * Constructor. |
| */ |
| public DMCommand(BundleContext context) { |
| m_context = context; |
| } |
| |
| /** |
| * Dependency Manager "dm" command. We use gogo annotations, in order to automate documentation, |
| * and also to automatically manage optional flags/options and parameters ordering. |
| * |
| * @param session the gogo command session, used to get some variables declared in the shell |
| * This parameter is automatically passed by the gogo runtime. |
| * @param nodeps false means that dependencies are not displayed |
| * @param compact true means informations are displayed in a compact format. This parameter can also be |
| * set using the "dependencymanager.compact" gogo shell variable. |
| * @param notavail only unregistered components / unavailable dependencies are displayed |
| * @param stats true means some statistics are displayed |
| * @param services an osgi filter used to filter on some given osgi service properties. This parameter can also be |
| * set using the "dependencymanager.services" gogo shell variable. |
| * @param components a regular expression to match either component implementation class names. This parameter can also be |
| * set using the "dependencymanager.components" gogo shell variable. |
| * @param componentIds only components matching one of the specified components ids are displayed |
| * @param bundleIds a list of bundle ids or symbolic names, used to filter on some given bundles |
| */ |
| @Descriptor("List dependency manager components") |
| public void dm( |
| CommandSession session, |
| |
| @Descriptor("Hides component dependencies") |
| @Parameter(names = {"nodeps", "nd"}, presentValue = "true", absentValue = "false") |
| boolean nodeps, |
| |
| @Descriptor("Displays components using a compact form") |
| @Parameter(names = {"compact", "cp"}, presentValue = "true", absentValue = "") |
| String compact, |
| |
| @Descriptor("Only displays unavailable components") |
| @Parameter(names = {"notavail", "na"}, presentValue = "true", absentValue = "false") |
| boolean notavail, |
| |
| @Descriptor("Detects where are the root failures") |
| @Parameter(names = {"wtf"}, presentValue = "true", absentValue = "false") |
| boolean wtf, |
| |
| @Descriptor("Displays components statistics") |
| @Parameter(names = {"stats", "stat", "st"}, presentValue = "true", absentValue = "false") |
| boolean stats, |
| |
| @Descriptor("<OSGi filter used to filter some service properties>") |
| @Parameter(names = {"services", "s"}, absentValue = "") |
| String services, |
| |
| @Descriptor("<Regex(s) used to filter on component implementation class names (comma separated), can be negated using \"!\" prefix>") |
| @Parameter(names = {"components", "c"}, absentValue = "") |
| String components, |
| |
| @Descriptor("<List of component identifiers to display (comma separated)>") |
| @Parameter(names = {"componentIds", "cid", "ci"}, absentValue = "") |
| String componentIds, |
| |
| @Descriptor("<List of bundle ids or bundle symbolic names to display (comma separated)>") |
| @Parameter(names = {"bundleIds", "bid", "bi", "b"}, absentValue = "") |
| String bundleIds, |
| |
| @Descriptor("<Max number of top components to display (0=all)> This command displays components callbacks (init/start) times>") |
| @Parameter(names = {"top"}, absentValue = "-1") |
| int top) throws Throwable |
| { |
| |
| boolean comp = Boolean.parseBoolean(getParam(session, ENV_COMPACT, compact)); |
| services = getParam(session, ENV_SERVICES, services); |
| String[] componentsRegex = getParams(session, ENV_COMPONENTS, components); |
| ArrayList<String> bids = new ArrayList<String>(); // list of bundle ids or bundle symbolic names |
| ArrayList<Long> cids = new ArrayList<Long>(); // list of component ids |
| |
| // Parse and check componentIds option |
| StringTokenizer tok = new StringTokenizer(componentIds, ", "); |
| while (tok.hasMoreTokens()) { |
| try { |
| cids.add(Long.parseLong(tok.nextToken())); |
| } catch (NumberFormatException e) { |
| System.out.println("Invalid value for componentIds option"); |
| return; |
| } |
| } |
| |
| // Parse services filter |
| Filter servicesFilter = null; |
| try { |
| if (services != null) { |
| servicesFilter = m_context.createFilter(services); |
| } |
| } catch (InvalidSyntaxException e) { |
| System.out.println("Invalid services OSGi filter: " + services); |
| e.printStackTrace(System.err); |
| return; |
| } |
| |
| // Parse and check bundleIds option |
| tok = new StringTokenizer(bundleIds, ", "); |
| while (tok.hasMoreTokens()) { |
| bids.add(tok.nextToken()); |
| } |
| |
| if (top != -1) { |
| showTopComponents(top); |
| return; |
| } |
| |
| if (wtf) { |
| wtf(); |
| return; |
| } |
| |
| DependencyGraph graph = null; |
| if(notavail) { |
| graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.ALL_UNAVAILABLE); |
| } else { |
| graph = DependencyGraph.getGraph(ComponentState.ALL, DependencyState.ALL); |
| } |
| |
| List<ComponentDeclaration> allComponents = graph.getAllComponents(); |
| Collections.sort(allComponents, COMPONENT_DECLARATION_COMPARATOR); |
| long numberOfComponents = 0; |
| long numberOfDependencies = 0; |
| long lastBundleId = -1; |
| |
| for(ComponentDeclaration cd : allComponents) { |
| Bundle bundle = cd.getBundleContext().getBundle(); |
| if(!matchBundle(bundle, bids)) { |
| continue; |
| } |
| |
| Component component = (Component)cd; |
| String name = cd.getName(); |
| if (!mayDisplay(component, servicesFilter, componentsRegex, cids)) { |
| continue; |
| } |
| |
| numberOfComponents++; |
| long bundleId = bundle.getBundleId(); |
| if(lastBundleId != bundleId) { |
| lastBundleId = bundleId; |
| if (comp) { |
| System.out.println("[" + bundleId + "] " + compactName(bundle.getSymbolicName())); |
| } else { |
| System.out.println("[" + bundleId + "] " + bundle.getSymbolicName()); |
| } |
| } |
| if (comp) { |
| System.out.print(" [" + cd.getId() + "] " + compactName(name) + " " |
| + compactState(ComponentDeclaration.STATE_NAMES[cd.getState()])); |
| } else { |
| System.out.println(" [" + cd.getId() + "] " + name + " " |
| + ComponentDeclaration.STATE_NAMES[cd.getState()]); |
| } |
| |
| if(!nodeps) { |
| List<ComponentDependencyDeclaration> dependencies = graph.getDependecies(cd); |
| if(!dependencies.isEmpty()) { |
| numberOfDependencies += dependencies.size(); |
| if (comp) { |
| System.out.print('('); |
| } |
| for(int j = 0; j < dependencies.size(); j ++) { |
| ComponentDependencyDeclaration dep = dependencies.get(j); |
| |
| String depName = dep.getName(); |
| String depType = dep.getType(); |
| int depState = dep.getState(); |
| |
| if (comp) { |
| if (j > 0) { |
| System.out.print(' '); |
| } |
| System.out.print(compactName(depName) + " " + compactState(depType) + " " |
| + compactState(ComponentDependencyDeclaration.STATE_NAMES[depState])); |
| } else { |
| System.out.println(" " + depName + " " + depType + " " |
| + ComponentDependencyDeclaration.STATE_NAMES[depState]); |
| } |
| |
| } |
| if (comp) { |
| System.out.print(')'); |
| } |
| } |
| } |
| if (comp) { |
| System.out.println(); |
| } |
| } |
| |
| if(stats) { |
| System.out.println("Statistics:"); |
| System.out.println(" - Dependency managers: " + DependencyManager.getDependencyManagers().size()); |
| System.out.println(" - Components: " + numberOfComponents); |
| if (!nodeps) { |
| System.out.println(" - Dependencies: " + numberOfDependencies); |
| } |
| } |
| |
| } |
| |
| /** |
| * Displays components callbacks (init/start/stop/destroy) elapsed time. |
| * The components are sorted (the most time consuming components are displayed first). |
| * @param max the max number of components to display (0 means all components) |
| */ |
| private void showTopComponents(int max) { |
| List<Component> components = new ArrayList<>(); |
| for (DependencyManager manager : DependencyManager.getDependencyManagers()) { |
| components.addAll(manager.getComponents()); |
| } |
| Collections.sort(components, new Comparator<Component>() { |
| @Override |
| public int compare(Component c1, Component c2) { |
| Map<String, Long> c1Times = c1.getComponentDeclaration().getCallbacksTime(); |
| Map<String, Long> c2Times = c2.getComponentDeclaration().getCallbacksTime(); |
| Long c1Start = c1Times.get("start"); |
| Long c2Start = c2Times.get("start"); |
| if (c1Start != null) { |
| if (c2Start != null) { |
| return c1Start > c2Start ? 1 : -1; |
| } else { |
| return 1; |
| } |
| } else { |
| if (c2Start != null) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| } |
| }); |
| |
| Collections.reverse(components); |
| |
| System.out.printf("%-100s %10s %10s%n%n", "Top components (sorted by start duration time)", "[init time]", "[start time]"); |
| |
| if (components.size() > 0) { |
| System.out.println(); |
| |
| max = max == 0 ? components.size() : Math.min(components.size(), max); |
| for (int i = 0 ; i < components.size() && i < max; i++) { |
| ComponentDeclaration decl = components.get(i).getComponentDeclaration(); |
| System.out.printf("%-100s %10d %10d%n", decl.getClassName(), |
| decl.getCallbacksTime().get("init"), decl.getCallbacksTime().get("start")); |
| } |
| } |
| } |
| |
| private boolean matchBundle(Bundle bundle, List<String> ids) { |
| if (ids.size() == 0) { |
| return true; |
| } |
| |
| for (int i = 0; i < ids.size(); i ++) { |
| String id = ids.get(i); |
| try { |
| Long longId = Long.valueOf(id); |
| if (longId == bundle.getBundleId()) { |
| return true; |
| } |
| } catch (NumberFormatException e) { |
| // must match symbolic name |
| if (id.equals(bundle.getSymbolicName())) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the value of a command arg parameter, or from the gogo shell if the parameter is not passed to |
| * the command. |
| */ |
| private String getParam(CommandSession session, String param, String value) { |
| if (value != null && value.length() > 0) { |
| return value; |
| } |
| Object shellParamValue = session.get(param); |
| return shellParamValue != null ? shellParamValue.toString() : null; |
| } |
| |
| /** |
| * Returns the value of a command arg parameter, or from the gogo shell if the parameter is not passed to |
| * the command. The parameter value is meant to be a list of values separated by a blank or a comma. |
| * The values are split and returned as an array. |
| */ |
| private String[] getParams(CommandSession session, String name, String value) { |
| String values = null; |
| if (value == null || value.length() == 0) { |
| value = (String) session.get(name); |
| if (value != null) { |
| values = value; |
| } |
| } else { |
| values = value; |
| } |
| if (values == null) { |
| return new String[0]; |
| } |
| return values.trim().split(", "); |
| } |
| |
| /** |
| * Checks if a component can be displayed. We make a logical OR between the three following conditions: |
| * |
| * - the component service properties are matching a given service filter ("services" option) |
| * - the component implementation class name is matching some regex ("components" option) |
| * - the component declaration name is matching some regex ("names" option) |
| * |
| * If some component ids are provided, then the component must also match one of them. |
| */ |
| private boolean mayDisplay(Component component, Filter servicesFilter, String[] components, List<Long> componentIds) { |
| // Check component id |
| if (componentIds.size() > 0) { |
| long componentId = ((ComponentDeclaration) component).getId(); |
| if (componentIds.indexOf(componentId) == -1) { |
| return false; |
| } |
| } |
| |
| if (servicesFilter == null && components.length == 0) { |
| return true; |
| } |
| |
| // Check component service properties |
| boolean servicesMatches = servicesMatches(component, servicesFilter); |
| |
| // Check components regexs, which may match component implementation class name |
| boolean componentsMatches = componentMatches(((ComponentDeclaration) component).getClassName(), components); |
| |
| // Logical OR between service properties match and component service/impl match. |
| return servicesMatches || componentsMatches; |
| } |
| |
| /** |
| * Checks if a given filter is matching some service properties possibly provided by a component |
| */ |
| private boolean servicesMatches(Component component, Filter servicesFilter) { |
| boolean match = false; |
| if (servicesFilter != null) { |
| String[] services = ((ComponentDeclaration) component).getServices(); |
| if (services != null) { |
| Dictionary<String, Object> properties = component.getServiceProperties(); |
| if (properties == null) { |
| properties = new Hashtable<String, Object>(); |
| } |
| if (properties.get(Constants.OBJECTCLASS) == null) { |
| properties.put(Constants.OBJECTCLASS, services); |
| } |
| match = servicesFilter.match(properties); |
| } |
| } |
| return match; |
| } |
| |
| /** |
| * Checks if the component implementation class name (or some possible provided services) are matching |
| * some regular expressions. |
| */ |
| private boolean componentMatches(String description, String[] names) { |
| for (int i = 0; i < names.length; i ++) { |
| String name = names[i]; |
| boolean not = false; |
| if (name.startsWith("!")) { |
| name = name.substring(1); |
| not = true; |
| } |
| boolean match = false; |
| |
| if (description.matches(name)) { |
| match = true; |
| } |
| |
| if (not) { |
| match = !match; |
| } |
| |
| if (match) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Compact names that look like state strings. State strings consist of |
| * one or more words. Each word will be shortened to the first letter, |
| * all letters concatenated and uppercased. |
| */ |
| private String compactState(String input) { |
| StringBuffer output = new StringBuffer(); |
| StringTokenizer st = new StringTokenizer(input); |
| while (st.hasMoreTokens()) { |
| output.append(st.nextToken().toUpperCase().charAt(0)); |
| } |
| return output.toString(); |
| } |
| |
| /** |
| * Compacts names that look like fully qualified class names. All packages |
| * will be shortened to the first letter, except for the last one. So |
| * something like "org.apache.felix.MyClass" will become "o.a.f.MyClass". |
| */ |
| private String compactName(String input) { |
| StringBuffer output = new StringBuffer(); |
| int lastIndex = 0; |
| for (int i = 0; i < input.length(); i++) { |
| char c = input.charAt(i); |
| switch (c) { |
| case '.' : |
| output.append(input.charAt(lastIndex)); |
| output.append('.'); |
| lastIndex = i + 1; |
| break; |
| case ' ' : |
| case ',' : |
| if (lastIndex < i) { |
| output.append(input.substring(lastIndex, i)); |
| } |
| output.append(c); |
| lastIndex = i + 1; |
| break; |
| default: |
| } |
| } |
| if (lastIndex < input.length()) { |
| output.append(input.substring(lastIndex)); |
| } |
| return output.toString(); |
| } |
| |
| public void wtf() { |
| |
| DependencyGraph graph = DependencyGraph.getGraph(ComponentState.UNREGISTERED, DependencyState.REQUIRED_UNAVAILABLE); |
| List<ComponentDeclaration> unregisteredComponents = graph.getAllComponents(); |
| |
| if(unregisteredComponents.isEmpty()) { |
| System.out.println("No unregistered components found"); |
| } else { |
| String message = unregisteredComponents.size() + " unregistered components found"; |
| System.out.println(message); |
| System.out.println("----------------------------------------------------".substring(0, message.length())); |
| } |
| |
| listResolvedBundles(); |
| listInstalledBundles(); |
| |
| List<CircularDependency> circularDependencies = graph.getCircularDependencies(); |
| if(!circularDependencies.isEmpty()) { |
| System.out.println("Circular dependencies:"); |
| printCircularDependencies(circularDependencies); |
| } |
| |
| List<MissingDependency> missingConfigDependencies = graph.getMissingDependencies(CONFIGURATION); |
| if(!missingConfigDependencies.isEmpty()) { |
| System.out.println("The following configuration(s) are missing: "); |
| printMissingDependencies(missingConfigDependencies); |
| } |
| |
| List<MissingDependency> missingServiceDependencies = graph.getMissingDependencies(SERVICE); |
| if(!missingServiceDependencies.isEmpty()) { |
| System.out.println("The following service(s) are missing: "); |
| printMissingDependencies(missingServiceDependencies); |
| } |
| |
| |
| List<MissingDependency> missingResourceDependencies = graph.getMissingDependencies(RESOURCE); |
| if(!missingResourceDependencies.isEmpty()) { |
| System.out.println("The following resource(s) are missing: "); |
| printMissingDependencies(missingResourceDependencies); |
| } |
| |
| List<MissingDependency> missingBundleDependencies = graph.getMissingDependencies(BUNDLE); |
| if(!missingBundleDependencies.isEmpty()) { |
| System.out.println("The following bundle(s) are missing: "); |
| printMissingDependencies(missingBundleDependencies); |
| } |
| |
| List<MissingDependency> missingCustomDependencies = graph.getMissingCustomDependencies(); |
| if(!missingCustomDependencies.isEmpty()) { |
| System.out.println("The following custom dependency(ies) are missing: "); |
| printMissingCustomDependencies(missingCustomDependencies); |
| } |
| } |
| |
| private void printCircularDependencies(List<CircularDependency> circularDependencies) { |
| for(CircularDependency c : circularDependencies) { |
| System.out.print(" *"); |
| for(ComponentDeclaration cd : c.getComponents()) { |
| System.out.print(" -> " + cd.getName()); |
| } |
| System.out.println(); |
| } |
| } |
| |
| private void printMissingDependencies(List<MissingDependency> missingDependencies) { |
| for(MissingDependency m : missingDependencies) { |
| System.out.println(" * " + m.getName() + " for bundle " + m.getBundleName()); |
| } |
| } |
| |
| private void printMissingCustomDependencies(List<MissingDependency> missingDependencies) { |
| for(MissingDependency m : missingDependencies) { |
| System.out.println(" * " + m.getName() + "(" + m.getType() + ")" + " for bundle " + m.getBundleName()); |
| } |
| } |
| |
| private void listResolvedBundles() { |
| boolean areResolved = false; |
| for (Bundle b : m_context.getBundles()) { |
| if (b.getState() == Bundle.RESOLVED && !isFragment(b)) { |
| areResolved = true; |
| break; |
| } |
| } |
| if (areResolved) { |
| System.out.println("Please note that the following bundles are in the RESOLVED state:"); |
| for (Bundle b : m_context.getBundles()) { |
| if (b.getState() == Bundle.RESOLVED && !isFragment(b)) { |
| System.out.println(" * [" + b.getBundleId() + "] " + b.getSymbolicName()); |
| } |
| } |
| } |
| } |
| |
| private void listInstalledBundles() { |
| boolean areResolved = false; |
| for (Bundle b : m_context.getBundles()) { |
| if (b.getState() == Bundle.INSTALLED) { |
| areResolved = true; |
| break; |
| } |
| } |
| if (areResolved) { |
| System.out.println("Please note that the following bundles are in the INSTALLED state:"); |
| for (Bundle b : m_context.getBundles()) { |
| if (b.getState() == Bundle.INSTALLED) { |
| System.out.println(" * [" + b.getBundleId() + "] " + b.getSymbolicName()); |
| } |
| } |
| } |
| } |
| |
| private boolean isFragment(Bundle b) { |
| Dictionary<String, String> headers = b.getHeaders(); |
| return headers.get("Fragment-Host") != null; |
| } |
| |
| public static class ComponentDeclarationComparator implements Comparator<ComponentDeclaration> { |
| @Override |
| public int compare(ComponentDeclaration cd1, ComponentDeclaration cd2) { |
| long id1 = cd1.getBundleContext().getBundle().getBundleId(); |
| long id2 = cd2.getBundleContext().getBundle().getBundleId(); |
| if(id1 == id2) { |
| // sort by component id |
| long cid1 = cd1.getId(); |
| long cid2 = cd2.getId(); |
| return cid1 > cid2 ? 1 : -1; |
| } |
| return id1 > id2 ? 1 : -1; |
| } |
| } |
| } |