View Javadoc
1   package com.atlassian.plugin.osgi.util;
2   
3   import com.atlassian.plugin.PluginDependencies;
4   import com.atlassian.plugin.module.ContainerAccessor;
5   import org.osgi.framework.Bundle;
6   import org.osgi.framework.namespace.PackageNamespace;
7   import org.osgi.framework.wiring.BundleRevision;
8   import org.osgi.framework.wiring.BundleRevisions;
9   import org.osgi.framework.wiring.BundleWire;
10  
11  import java.util.Collection;
12  
13  /**
14   * Collection of util operation on different OSGi bits that results in Plugins
15   * items
16   */
17  public final class OsgiPluginUtil {
18      // PluginDependencies is immutable...
19      private static final PluginDependencies EMPTY_DEPS = new PluginDependencies();
20  
21      /**
22       * Creates null implementation of ContainerAccessor interface
23       * <p>
24       * That implementation used when plugin doesn't export access to any internal IoC. In that case a try
25       * to access container means some logical bug in the design, for example reference some instance ID in
26       * module type definition, so it makes sense to fail fast with reasonable explanation rather then return
27       * some empty state which will masks real problem.
28       *
29       * @param pluginKey plugin identifier to be written in container exceptions
30       * @return Null implementation of ContainerAccessor interface which is throwing exceptions on each
31       * call
32       */
33      public static ContainerAccessor createNonExistingPluginContainer(final String pluginKey) {
34          return new ContainerAccessor() {
35  
36              @Override
37              public <T> T createBean(Class<T> clazz) {
38                  throw new UnsupportedOperationException(String.format("Plugin '%s' has no container", pluginKey));
39              }
40  
41              @Override
42              public <T> T injectBean(T bean) {
43                  throw new UnsupportedOperationException(String.format("Plugin '%s' has no container", pluginKey));
44              }
45  
46              @Override
47              public <T> T getBean(String id) {
48                  throw new UnsupportedOperationException(String.format("Plugin '%s' has no container", pluginKey));
49              }
50  
51              @Override
52              public <T> Collection<T> getBeansOfType(Class<T> interfaceClass) {
53                  throw new UnsupportedOperationException(String.format("Plugin '%s' has no container", pluginKey));
54              }
55          };
56      }
57  
58      /**
59       * Determines which plugin keys are dependencies based on tracing the wires, categorising them as mandatory,
60       * optional or dynamic.
61       * <p>
62       * Bundle must be in RESOLVED state at least. If Bundle is in INSTALLED or UNINSTALLED states wires are
63       * not existing and method returns empty dependencies set
64       *
65       * {@link PackageNamespace#RESOLUTION_OPTIONAL} and {@link PackageNamespace#RESOLUTION_DYNAMIC} are mapped to
66       * optional and dynamic, respectively. Any others are mapped to mandatory.
67       *
68       * @return not null, possibly empty
69       * @since 4.0
70       */
71      public static PluginDependencies getDependencies(final Bundle bundle) {
72          int state = bundle.getState();
73          if (state == Bundle.INSTALLED || state == Bundle.UNINSTALLED) {
74              return EMPTY_DEPS;
75          }
76  
77          // TODO: replace with bundle.adapt(BundleWiring.class) as that is the way recommended by 
78          // OSGi itself
79          final PluginDependencies.Builder depsBuilder = PluginDependencies.builder();
80          if (bundle instanceof BundleRevisions) {
81              for (final BundleRevision bundleRevision : ((BundleRevisions) bundle).getRevisions()) {
82                  for (final BundleWire requiredWire : bundleRevision.getWiring().getRequiredWires(null)) {
83                      final String pluginKey = OsgiHeaderUtil.getPluginKey(requiredWire.getProviderWiring().getBundle());
84                      String resolutionDirective = requiredWire.getRequirement().getDirectives().get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE);
85  
86                      if (resolutionDirective == null) {
87                          // mandatory by default
88                          resolutionDirective = PackageNamespace.RESOLUTION_MANDATORY;
89                      }
90  
91                      switch (resolutionDirective) {
92                          case PackageNamespace.RESOLUTION_OPTIONAL:
93                              depsBuilder.withOptional(pluginKey);
94                              break;
95                          case PackageNamespace.RESOLUTION_DYNAMIC:
96                              depsBuilder.withDynamic(pluginKey);
97                              break;
98                          default:
99                              // mandatory if not specified
100                             depsBuilder.withMandatory(pluginKey);
101                             break;
102                     }
103                 }
104             }
105         }
106 
107         return depsBuilder.build();
108     }
109 }