View Javadoc

1   package com.atlassian.plugin.osgi.bridge.external;
2   
3   import com.atlassian.plugin.PluginException;
4   import org.osgi.framework.BundleContext;
5   import org.osgi.framework.InvalidSyntaxException;
6   import org.osgi.framework.ServiceEvent;
7   import org.osgi.framework.ServiceListener;
8   import org.osgi.framework.ServiceReference;
9   import org.slf4j.Logger;
10  import org.slf4j.LoggerFactory;
11  import org.springframework.beans.factory.FactoryBean;
12  import org.springframework.beans.factory.InitializingBean;
13  
14  import java.lang.reflect.InvocationHandler;
15  import java.lang.reflect.InvocationTargetException;
16  import java.lang.reflect.Method;
17  import java.lang.reflect.Proxy;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  import static org.osgi.framework.ServiceEvent.REGISTERED;
21  
22  /**
23   * Simple factory bean to resolve host components.  Since we know host components won't change during the bundle's
24   * lifetime, we can use a direct reference instead of the fancy proxy stuff from Spring DM.
25   *
26   * @since 2.2.0
27   */
28  public class HostComponentFactoryBean implements FactoryBean, InitializingBean
29  {
30      private BundleContext bundleContext;
31      private String filter;
32      private Object service;
33      private Class<?>[] interfaces;
34  
35      @Override
36      public Object getObject() throws Exception
37      {
38          return findService();
39      }
40  
41      @Override
42      public Class getObjectType()
43      {
44          return (findService() != null ? findService().getClass() : null);
45      }
46  
47      @Override
48      public boolean isSingleton()
49      {
50          return true;
51      }
52  
53      public void setBundleContext(BundleContext bundleContext)
54      {
55          this.bundleContext = bundleContext;
56      }
57  
58      /**
59       * Sets the OSGi service filter.
60       *
61       * @param filter OSGi filter describing the importing OSGi service
62       */
63      public void setFilter(String filter)
64      {
65          this.filter = filter;
66      }
67  
68      public void setInterfaces(Class<?>[] interfaces)
69      {
70          this.interfaces = interfaces;
71      }
72  
73      /**
74       * Finds a service, if the bundle context is available.
75       *
76       * @return The service, null if not found or the bundle context isn't available yet
77       * @throws com.atlassian.plugin.PluginException
78       *          If either 0 or more than 1 service reference is found
79       */
80      private Object findService() throws PluginException
81      {
82          return service;
83      }
84  
85      /**
86       * Wraps the service in a dynamic proxy that ensures the service reference is still valid
87       *
88       * @return A proxy that wraps the service
89       */
90      private Object createHostComponentProxy()
91      {
92          // we use the bundleContext's classloader since it was loaded from the main webapp
93          return Proxy.newProxyInstance(bundleContext.getClass().getClassLoader(), interfaces, new DynamicServiceInvocationHandler(
94                  bundleContext, filter));
95      }
96  
97      @Override
98      public void afterPropertiesSet() throws Exception
99      {
100         checkNotNull(bundleContext);
101         checkNotNull(interfaces);
102         service = createHostComponentProxy();
103     }
104 
105     /**
106      * InvocationHandler for a dynamic proxy that ensures all methods are executed with the
107      * object class's class loader as the context class loader.
108      */
109     static class DynamicServiceInvocationHandler implements InvocationHandler
110     {
111         private static final Logger log = LoggerFactory.getLogger(DynamicServiceInvocationHandler.class);
112         private volatile Object service;
113         private final String filter;
114 
115 
116         DynamicServiceInvocationHandler(final BundleContext bundleContext, final String filter)
117         {
118             this.filter = filter;
119             try
120             {
121                 ServiceReference[] refs = bundleContext.getServiceReferences((String) null, filter);
122                 if (refs != null && refs.length > 0)
123                 {
124                     service = bundleContext.getService(refs[0]);
125                 }
126 
127                 bundleContext.addServiceListener(new ServiceListener()
128                 {
129                     @Override
130                     public void serviceChanged(ServiceEvent serviceEvent)
131                     {
132                         // We ignore unregistered services as we want to continue to use the old one during a transition
133                         if (REGISTERED == serviceEvent.getType())
134                         {
135                             if (log.isDebugEnabled())
136                             {
137                                 log.debug("Updating the host component matching filter: " + filter);
138                             }
139                             service = bundleContext.getService(serviceEvent.getServiceReference());
140                         }
141                     }
142                 }, filter);
143             }
144             catch (InvalidSyntaxException e)
145             {
146                 throw new IllegalArgumentException("Invalid filter string: " + filter, e);
147             }
148         }
149 
150         @Override
151         public Object invoke(final Object o, final Method method, final Object[] objects) throws Throwable
152         {
153             if (service == null)
154             {
155                 throw new IllegalStateException("Unable to locate host component with filter: " + filter);
156             }
157             try
158             {
159                 return method.invoke(service, objects);
160             }
161             catch (final InvocationTargetException e)
162             {
163                 throw e.getTargetException();
164             }
165         }
166     }
167 }