1   package com.atlassian.plugin.osgi.bridge.external;
2   
3   import org.springframework.beans.factory.FactoryBean;
4   import org.springframework.beans.factory.InitializingBean;
5   import org.springframework.osgi.context.BundleContextAware;
6   import org.osgi.framework.BundleContext;
7   import org.osgi.framework.ServiceReference;
8   import org.osgi.framework.InvalidSyntaxException;
9   import org.osgi.framework.ServiceListener;
10  import org.osgi.framework.Filter;
11  import org.osgi.framework.ServiceEvent;
12  import static org.osgi.framework.ServiceEvent.*;
13  import org.apache.commons.lang.Validate;
14  import org.apache.commons.logging.LogFactory;
15  import org.apache.commons.logging.Log;
16  import com.atlassian.plugin.PluginException;
17  
18  import java.util.Arrays;
19  import java.util.List;
20  import java.lang.reflect.Proxy;
21  import java.lang.reflect.InvocationHandler;
22  import java.lang.reflect.Method;
23  import java.lang.reflect.InvocationTargetException;
24  
25  /**
26   * Simple factory bean to resolve host components.  Since we know host components won't change during the bundle's
27   * lifetime, we can use a direct reference instead of the fancy proxy stuff from Spring DM.
28   *
29   * @since 2.2.0
30   */
31  public class HostComponentFactoryBean implements FactoryBean, BundleContextAware, InitializingBean
32  {
33      private BundleContext bundleContext;
34      private String filter;
35      private Object service;
36      private Class<?>[] interfaces;
37  
38      public Object getObject() throws Exception
39      {
40          return findService();
41      }
42  
43      public Class getObjectType()
44      {
45          return (findService() != null ? findService().getClass() : null);
46      }
47  
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      public void afterPropertiesSet() throws Exception
98      {
99          Validate.notNull(bundleContext);
100         Validate.notNull(interfaces);
101         service = createHostComponentProxy();
102     }
103 
104     /**
105      * InvocationHandler for a dynamic proxy that ensures all methods are executed with the
106      * object class's class loader as the context class loader.
107      */
108     static class DynamicServiceInvocationHandler implements InvocationHandler
109     {
110         private static final Log log = LogFactory.getLog(DynamicServiceInvocationHandler.class);
111         private volatile Object service;
112 
113 
114         DynamicServiceInvocationHandler(final BundleContext bundleContext, final String filter)
115         {
116             try
117             {
118                 ServiceReference[] refs = bundleContext.getServiceReferences(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             try
149             {
150                 return method.invoke(service, objects);
151             }
152             catch (final InvocationTargetException e)
153             {
154                 throw e.getTargetException();
155             }
156         }
157     }
158 }