View Javadoc

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