View Javadoc

1   package com.atlassian.plugin.osgi.factory;
2   
3   import com.atlassian.plugin.IllegalPluginStateException;
4   import com.atlassian.plugin.PluginException;
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.OsgiHeaderUtil;
10  import com.atlassian.plugin.util.resource.AlternativeDirectoryResourceLoader;
11  import com.google.common.base.Function;
12  import org.apache.felix.framework.resolver.Module;
13  import org.apache.felix.framework.resolver.Wire;
14  import org.osgi.framework.Bundle;
15  import org.osgi.service.packageadmin.PackageAdmin;
16  import org.osgi.util.tracker.ServiceTracker;
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  import java.io.InputStream;
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import java.net.URL;
24  import java.util.Collections;
25  import java.util.Dictionary;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import static com.atlassian.fugue.Option.option;
31  import static com.atlassian.fugue.Suppliers.alwaysFalse;
32  import static com.google.common.base.Preconditions.checkNotNull;
33  
34  /**
35   * Helper class that implements the methods for an OSGi plugin that has been installed
36   *
37   * @since 2.2.0
38   */
39  final class OsgiPluginInstalledHelper implements OsgiPluginHelper
40  {
41      private static final Logger logger = LoggerFactory.getLogger(OsgiPluginInstalledHelper.class);
42      private final ClassLoader bundleClassLoader;
43      private final Bundle bundle;
44      private final PackageAdmin packageAdmin;
45      private volatile ContainerAccessor containerAccessor;
46      private volatile ServiceTracker[] serviceTrackers;
47  
48      /**
49       * @param bundle The bundle
50       * @param packageAdmin The package admin
51       */
52      public OsgiPluginInstalledHelper(final Bundle bundle, final PackageAdmin packageAdmin)
53      {
54          this.bundle = checkNotNull(bundle);
55          this.packageAdmin = checkNotNull(packageAdmin);
56          bundleClassLoader = BundleClassLoaderAccessor.getClassLoader(bundle, new AlternativeDirectoryResourceLoader());
57      }
58  
59      public Bundle getBundle()
60      {
61          return bundle;
62      }
63  
64      public <T> Class<T> loadClass(final String clazz, final Class<?> callingClass) throws ClassNotFoundException
65      {
66          return BundleClassLoaderAccessor.loadClass(getBundle(), clazz);
67      }
68  
69      public URL getResource(final String name)
70      {
71          return bundleClassLoader.getResource(name);
72      }
73  
74      public InputStream getResourceAsStream(final String name)
75      {
76          return bundleClassLoader.getResourceAsStream(name);
77      }
78  
79      public ClassLoader getClassLoader()
80      {
81          return bundleClassLoader;
82      }
83  
84      public Bundle install()
85      {
86          logger.debug("Not installing OSGi plugin '{}' since it's already installed.", bundle.getSymbolicName());
87          throw new IllegalPluginStateException("Plugin '" + bundle.getSymbolicName() + "' has already been installed");
88      }
89  
90      public void onEnable(final ServiceTracker... serviceTrackers) throws OsgiContainerException
91      {
92          for (final ServiceTracker svc : checkNotNull(serviceTrackers))
93          {
94              svc.open();
95          }
96  
97          this.serviceTrackers = serviceTrackers;
98      }
99  
100     public void onDisable() throws OsgiContainerException
101     {
102         final ServiceTracker[] serviceTrackers = this.serviceTrackers; // cache a copy locally for multi-threaded goodness
103         if (serviceTrackers != null)
104         {
105             for (final ServiceTracker svc : serviceTrackers)
106             {
107                 svc.close();
108             }
109             this.serviceTrackers = null;
110         }
111         setPluginContainer(null);
112     }
113 
114     public void onUninstall() throws OsgiContainerException
115     {
116     }
117 
118     public Set<String> getRequiredPlugins()
119     {
120         /* A bundle must move from INSTALLED to RESOLVED before we can get its import */
121         if (bundle.getState() == Bundle.INSTALLED)
122         {
123             logger.debug("Bundle is in INSTALLED for {}", bundle.getSymbolicName());
124             if (!packageAdmin.resolveBundles(new Bundle[] { bundle }))
125             {
126                 // The likely cause of this is installing a plugin without installing everything that it requires.
127                 logger.error("Cannot determine required plugins, cannot resolve bundle '{}'", bundle.getSymbolicName());
128                 return Collections.emptySet();
129             }
130             logger.debug("Bundle state is now {}", bundle.getState());
131         }
132 
133         return getRequiredPluginKeys(bundle);
134     }
135 
136     /**
137      * This is taken from Felix itself and represents how it works out what other bundles are required by this
138      * particular bundle.  It replaces a previous implementation that, while technically solid, was extremely slow and
139      * showed up as a major method hot-spot in plugin reload times.
140      * <p/>
141      * While it uses reflection to gain access to "non public" methods, the speed trade-off makes it worthwhile
142      */
143     private Set<String> getRequiredPluginKeys(final Bundle bundle)
144     {
145         String errMsg = null;
146         final Set<String> list = new HashSet<String>();
147         try
148         {
149             //
150             // future versions of Felix make this available, we do the needful for now
151             // as the speed trade off is significant
152             //
153             final Method getModules = bundle.getClass().getDeclaredMethod("getModules");
154             getModules.setAccessible(true);
155             //noinspection unchecked
156             final List<Module> modules = (List<Module>) getModules.invoke(bundle);
157 
158             for (final Module module : modules)
159             {
160                 final List<Wire> wires = module.getWires();
161 
162                 for (final Wire wire : wires)
163                 {
164                     list.add(OsgiHeaderUtil.getPluginKey(wire.getExporter().getBundle()));
165                 }
166             }
167         }
168         catch (final InvocationTargetException ignored)
169         {
170             errMsg = "Completely unexpected InvocationTargetException during Felix Bundle getModules().  Has the underlying Felix changed since this code was written?";
171         }
172         catch (final NoSuchMethodException ignored)
173         {
174             errMsg = "Completely unexpected NoSuchMethodException during Felix Bundle getModules().  Has the underlying Felix changed since this code was written?";
175         }
176         catch (final IllegalAccessException ignored)
177         {
178             errMsg = "Completely unexpected IllegalAccessException during Felix Bundle getModules().  Has the underlying Felix changed since this code was written?";
179         }
180         //
181         // we know the code works with the version of Felix that was in play when this code was written
182         // if this subsequently changes then we want to fail fast.  So we throw this exception to indicate this.
183         if (errMsg != null)
184         {
185             throw new IllegalStateException(errMsg);
186         }
187 
188         return list;
189     }
190 
191     public void setPluginContainer(final Object container)
192     {
193         if (container == null)
194         {
195             containerAccessor = null;
196         }
197         else if (container instanceof ContainerAccessor)
198         {
199             containerAccessor = (ContainerAccessor) container;
200         }
201         else
202         {
203             containerAccessor = new DefaultSpringContainerAccessor(container);
204         }
205     }
206 
207     public ContainerAccessor getContainerAccessor()
208     {
209         return containerAccessor;
210     }
211 
212     /**
213      * @throws IllegalPluginStateException if the plugin container is not initialized
214      */
215     public ContainerAccessor getRequiredContainerAccessor() throws IllegalPluginStateException
216     {
217         if (containerAccessor == null)
218         {
219             throw new IllegalStateException("Cannot create object because the plugin container is unavailable for bundle '"
220                     + bundle.getSymbolicName() + "'");
221         }
222         return containerAccessor;
223     }
224 
225     public boolean isRemotePlugin()
226     {
227         return option(getBundle().getHeaders()).fold(alwaysFalse(), new Function<Dictionary, Boolean>()
228         {
229             @Override
230             public Boolean apply(final Dictionary headers)
231             {
232                 return headers.get(OsgiPlugin.REMOTE_PLUGIN_KEY) != null;
233             }
234         });
235     }
236 }