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