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