View Javadoc
1   package com.atlassian.plugin.spring.scanner.runtime.impl;
2   
3   import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsDevService;
4   import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
5   import com.atlassian.plugin.spring.scanner.annotation.export.ModuleType;
6   import com.atlassian.plugin.spring.scanner.util.CommonConstants;
7   import com.google.common.collect.ImmutableList;
8   import com.google.common.collect.ImmutableMap;
9   import com.google.common.collect.Iterables;
10  import org.osgi.framework.Bundle;
11  import org.osgi.framework.BundleContext;
12  import org.osgi.framework.ServiceRegistration;
13  import org.slf4j.Logger;
14  import org.slf4j.LoggerFactory;
15  import org.springframework.aop.support.AopUtils;
16  import org.springframework.beans.BeansException;
17  import org.springframework.beans.factory.InitializingBean;
18  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
19  import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
20  
21  import java.lang.annotation.Annotation;
22  import java.util.Hashtable;
23  import java.util.List;
24  
25  import static com.atlassian.plugin.spring.scanner.runtime.impl.util.AnnotationIndexReader.getIndexFilesForProfiles;
26  import static com.atlassian.plugin.spring.scanner.runtime.impl.util.AnnotationIndexReader.readAllIndexFilesForProduct;
27  import static com.atlassian.plugin.spring.scanner.runtime.impl.util.AnnotationIndexReader.splitProfiles;
28  
29  /**
30   * A BeanPostProcessor that exports OSGi services for beans annotated with both a *Component annotation and the
31   * ExportAsService annotation This essentially does the same thing as the "public=true" on an atlassian-plugin.xml
32   * component entry.
33   * <p>
34   * This is implemented as a BeanPostProcessor because we need to service to come and go as the bean is
35   * created/destroyed
36   */
37  public class ServiceExporterBeanPostProcessor implements DestructionAwareBeanPostProcessor, InitializingBean {
38      public static final String OSGI_SERVICE_SUFFIX = "_osgiService";
39      static final String ATLASSIAN_DEV_MODE_PROP = "atlassian.dev.mode";
40  
41      private static final Logger log = LoggerFactory.getLogger(ServiceExporterBeanPostProcessor.class);
42  
43      private final boolean isDevMode = Boolean.parseBoolean(System.getProperty(ATLASSIAN_DEV_MODE_PROP, "false"));
44      private final BundleContext bundleContext;
45      private ConfigurableListableBeanFactory beanFactory;
46      private String profileName;
47      private final ExportedSeviceManager serviceManager;
48  
49      private ImmutableMap<String, String[]> exports;
50  
51      public ServiceExporterBeanPostProcessor(final BundleContext bundleContext, final ConfigurableListableBeanFactory beanFactory) {
52          this(bundleContext, beanFactory, new ExportedSeviceManager());
53      }
54  
55      ServiceExporterBeanPostProcessor(final BundleContext bundleContext, final ConfigurableListableBeanFactory beanFactory,
56                                       ExportedSeviceManager serviceManager) {
57          this.bundleContext = bundleContext;
58          this.beanFactory = beanFactory;
59          this.profileName = null;
60          this.serviceManager = serviceManager;
61      }
62  
63      // Used to inject profileName
64      @SuppressWarnings("UnusedDeclaration")
65      public void setProfileName(final String profileName) {
66          this.profileName = profileName;
67      }
68  
69      @Override
70      public void afterPropertiesSet() {
71          final ImmutableMap.Builder<String, String[]> exportBuilder = ImmutableMap.builder();
72  
73          final Bundle bundle = bundleContext.getBundle();
74  
75          final String[] profileNames = splitProfiles(profileName);
76          parseExportsForExportFile(exportBuilder, CommonConstants.COMPONENT_EXPORT_KEY, profileNames, bundle);
77          if (isDevMode) {
78              parseExportsForExportFile(exportBuilder, CommonConstants.COMPONENT_DEV_EXPORT_KEY, profileNames, bundle);
79          }
80          exports = exportBuilder.build();
81      }
82  
83      private void parseExportsForExportFile(
84              final ImmutableMap.Builder<String, String[]> exportBuilder, final String exportFileName,
85              final String[] profileNames, final Bundle bundle) {
86          final String[] defaultInterfaces = {};
87          for (final String fileToRead : getIndexFilesForProfiles(profileNames, exportFileName)) {
88              final List<String> exportData = readAllIndexFilesForProduct(fileToRead, bundle, bundleContext);
89              for (final String export : exportData) {
90                  final String[] targetAndInterfaces = export.split("#");
91                  final String target = targetAndInterfaces[0];
92                  final String[] interfaces = (targetAndInterfaces.length > 1)
93                          ? targetAndInterfaces[1].split(",")
94                          : defaultInterfaces;
95                  exportBuilder.put(target, interfaces);
96              }
97          }
98      }
99  
100     @Override
101     public void postProcessBeforeDestruction(final Object bean, final String beanName) throws BeansException {
102         if (serviceManager.hasService(bean)) {
103             serviceManager.unregisterService(bundleContext, bean);
104 
105             final String serviceName = getServiceName(beanName);
106             if (beanFactory.containsBean(serviceName)) {
107                 final Object serviceBean = beanFactory.getBean(serviceName);
108 
109                 if (null != serviceBean) {
110                     beanFactory.destroyBean(serviceName, serviceBean);
111                 }
112             }
113         }
114     }
115 
116     @Override
117     public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
118         return bean;
119     }
120 
121     @Override
122     public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
123         Class<?>[] interfaces = {};
124         // If the actual bean is some proxy object because of Spring AOP, this extracts the actual declared class of the bean.
125         // The answer will only be different from getClass() when AOP is in play - in such case this will retrieve
126         // the real class used in the code ignoring AOP proxy
127         final Class<?> beanTargetClass = AopUtils.getTargetClass(bean);
128         final String beanClassName = beanTargetClass.getName();
129 
130         if (exports.containsKey(beanClassName) || isPublicComponent(beanTargetClass)) {
131             if (exports.containsKey(beanClassName)) {
132                 // We need to turn the string interface names from the exports file into actual Class objects. We do this by
133                 // asking the bean's class loader for each interface by name. We could walking the class hierarchy looking for
134                 // the ones we want, but that seems a bit more roundabout.
135                 final ImmutableList.Builder<Class<?>> interfaceBuilder = ImmutableList.builder();
136                 final ClassLoader beanClassLoader = bean.getClass().getClassLoader();
137                 final String[] interfaceNames = exports.get(beanClassName);
138                 for (final String interfaceName : interfaceNames) {
139                     try {
140                         final Class interfaceClass = beanClassLoader.loadClass(interfaceName);
141                         interfaceBuilder.add(interfaceClass);
142                     } catch (final ClassNotFoundException ecnf) {
143                         log.warn("Cannot find class for export '" + interfaceName + "' of bean '" + beanName + "': " + ecnf);
144                         // And drop it - there's not much else we can do
145                     }
146                 }
147                 interfaces = Iterables.toArray(interfaceBuilder.build(), Class.class);
148             } else if (hasAnnotation(beanTargetClass, ModuleType.class)) {
149                 interfaces = beanTargetClass.getAnnotation(ModuleType.class).value();
150             } else if (hasAnnotation(beanTargetClass, ExportAsService.class)) {
151                 interfaces = beanTargetClass.getAnnotation(ExportAsService.class).value();
152             } else if (hasAnnotation(beanTargetClass, ExportAsDevService.class)) {
153                 interfaces = beanTargetClass.getAnnotation(ExportAsDevService.class).value();
154             }
155 
156             //if they didn't specify any interfaces, calculate them
157             if (interfaces.length < 1) {
158                 // This only gets direct interfaces, not inherited ones, which is documented behaviour.
159                 // It's desireable because it reduces brittleness with respect to superclass changes.
160                 interfaces = beanTargetClass.getInterfaces();
161 
162                 //if we still don't have any, just export with the classname (yes, OSGi allows this.
163                 if (interfaces.length < 1) {
164                     interfaces = new Class<?>[]{beanTargetClass};
165                 }
166             }
167 
168             try {
169                 final ServiceRegistration serviceRegistration = serviceManager.registerService(
170                         bundleContext, bean, beanName, new Hashtable<String, Object>(), interfaces);
171                 final String serviceName = getServiceName(beanName);
172                 beanFactory.initializeBean(serviceRegistration, serviceName);
173             } catch (final Exception e) {
174                 log.error("Unable to register bean '" + beanName + "' as an OSGi exported service", e);
175             }
176         }
177 
178         return bean;
179     }
180 
181     private boolean isPublicComponent(final Class beanTargetClass) {
182         return hasAnnotation(beanTargetClass, ModuleType.class)
183                 || hasAnnotation(beanTargetClass, ExportAsService.class)
184                 || (hasAnnotation(beanTargetClass, ExportAsDevService.class) && isDevMode);
185     }
186 
187     private boolean hasAnnotation(final Class beanTargetClass, final Class<? extends Annotation> annotationClass) {
188         return beanTargetClass.isAnnotationPresent(annotationClass);
189     }
190 
191     private String getServiceName(final String beanName) {
192         return beanName + OSGI_SERVICE_SUFFIX;
193     }
194 
195 }