View Javadoc
1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.IllegalPluginStateException;
4   import com.atlassian.plugin.PluginDependencies;
5   import com.atlassian.plugin.module.ContainerAccessor;
6   import com.atlassian.plugin.osgi.container.OsgiContainerException;
7   import com.atlassian.plugin.osgi.spring.DefaultSpringContainerAccessor;
8   import com.atlassian.plugin.osgi.util.BundleClassLoaderAccessor;
9   import com.atlassian.plugin.osgi.util.OsgiPluginUtil;
10  import com.atlassian.plugin.util.resource.AlternativeDirectoryResourceLoader;
11  import org.osgi.framework.Bundle;
12  import org.osgi.service.packageadmin.PackageAdmin;
13  import org.osgi.util.tracker.ServiceTracker;
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import javax.annotation.Nonnull;
18  import java.io.InputStream;
19  import java.net.URL;
20  
21  import static com.google.common.base.Preconditions.checkNotNull;
22  import static java.util.Optional.ofNullable;
23  
24  /**
25   * Helper class that implements the methods for an OSGi plugin that has been installed
26   *
27   * @since 2.2.0
28   */
29  final class OsgiPluginInstalledHelper implements OsgiPluginHelper {
30      private static final Logger logger = LoggerFactory.getLogger(OsgiPluginInstalledHelper.class);
31      private final ClassLoader bundleClassLoader;
32      private final Bundle bundle;
33      private final PackageAdmin packageAdmin;
34      private volatile ContainerAccessor containerAccessor;
35      private volatile ServiceTracker[] serviceTrackers;
36  
37      /**
38       * @param bundle       The bundle
39       * @param packageAdmin The package admin
40       */
41      public OsgiPluginInstalledHelper(final Bundle bundle, final PackageAdmin packageAdmin) {
42          this.bundle = checkNotNull(bundle);
43          this.packageAdmin = checkNotNull(packageAdmin);
44          bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
45      }
46  
47      public Bundle getBundle() {
48          return bundle;
49      }
50  
51      public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException {
52          return BundleClassLoaderAccessor.loadClass(getBundle(), clazz);
53      }
54  
55      public URL getResource(final String name) {
56          return bundleClassLoader.getResource(name);
57      }
58  
59      public InputStream getResourceAsStream(final String name) {
60          return bundleClassLoader.getResourceAsStream(name);
61      }
62  
63      public ClassLoader getClassLoader() {
64          return bundleClassLoader;
65      }
66  
67      public Bundle install() {
68          logger.debug("Not installing OSGi plugin '{}' since it's already installed.", bundle.getSymbolicName());
69          throw new IllegalPluginStateException("Plugin '" + bundle.getSymbolicName() + "' has already been installed");
70      }
71  
72      public void onEnable(final ServiceTracker... serviceTrackers) throws OsgiContainerException {
73          for (final ServiceTracker svc : checkNotNull(serviceTrackers)) {
74              svc.open();
75          }
76  
77          this.serviceTrackers = serviceTrackers;
78      }
79  
80      public void onDisable() throws OsgiContainerException {
81          final ServiceTracker[] serviceTrackers = this.serviceTrackers; // cache a copy locally for multi-threaded goodness
82          if (serviceTrackers != null) {
83              for (final ServiceTracker svc : serviceTrackers) {
84                  svc.close();
85              }
86              this.serviceTrackers = null;
87          }
88          setPluginContainer(null);
89      }
90  
91      public void onUninstall() throws OsgiContainerException {
92      }
93  
94      @Nonnull
95      @Override
96      public PluginDependencies getDependencies() {
97          if (availableForTraversal()) {
98              return OsgiPluginUtil.getDependencies(bundle);
99          } else {
100             return new PluginDependencies(null, null, null);
101         }
102     }
103 
104     @Deprecated
105     // TODO: It doesn't look good to try to resolve bundle here. It seems mask some lifecycle troubles,
106     // my gut feeling is that we'd better to be sure that where ever dependencies need Bundle is resolved
107     // already.
108     //
109     // However it seems to be quite a big move to fix right now, so just deprecate that method to come 
110     // back to that later.
111     private boolean availableForTraversal() {
112         /* A bundle must move from INSTALLED to RESOLVED before we can get its import */
113         if (bundle.getState() == Bundle.INSTALLED) {
114             logger.debug("Bundle is in INSTALLED for {}", bundle.getSymbolicName());
115             if (!packageAdmin.resolveBundles(new Bundle[]{bundle})) {
116                 // The likely cause of this is installing a plugin without installing everything that it requires.
117                 logger.error("Cannot determine required plugins, cannot resolve bundle '{}'", bundle.getSymbolicName());
118                 return false;
119             }
120             logger.debug("Bundle state is now {}", bundle.getState());
121         }
122         return true;
123     }
124 
125     public void setPluginContainer(final Object container) {
126         if (container == null) {
127             containerAccessor = null;
128         } else if (container instanceof ContainerAccessor) {
129             containerAccessor = (ContainerAccessor) container;
130         } else {
131             containerAccessor = new DefaultSpringContainerAccessor(container);
132         }
133     }
134 
135     public ContainerAccessor getContainerAccessor() {
136         return containerAccessor;
137     }
138 
139     /**
140      * @throws IllegalPluginStateException if the plugin container is not initialized
141      */
142     public ContainerAccessor getRequiredContainerAccessor() throws IllegalPluginStateException {
143         if (containerAccessor == null) {
144             throw new IllegalStateException("Cannot create object because the plugin container is unavailable for bundle '"
145                     + bundle.getSymbolicName() + "'");
146         }
147         return containerAccessor;
148     }
149 
150     public boolean isRemotePlugin() {
151         return ofNullable(getBundle().getHeaders())
152                 .map(headers -> headers.get(OsgiPlugin.REMOTE_PLUGIN_KEY) != null)
153                 .orElse(false);
154     }
155 }