View Javadoc
1   package com.atlassian.plugin.module;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import com.atlassian.plugin.PluginParseException;
6   import org.slf4j.Logger;
7   import org.slf4j.LoggerFactory;
8   
9   import java.util.Collection;
10  import java.util.HashMap;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import static com.google.common.base.Preconditions.checkNotNull;
15  
16  /**
17   * The default implementation of a {@link ModuleFactory}.
18   * The module class name can contain a prefix and this prefix determines which {@link com.atlassian.plugin.module.ModuleFactory}
19   * is used to create the java class for this module descriptor.
20   * <p/>
21   * If no prefix is supplied it will use {@link ClassPrefixModuleFactory} to create the module class object.
22   * <p/>
23   * ModuleFactories are located via the following algorithm.  First, the prefixes registered during construction are searched, then
24   * any implementations in the plugin's container, if applicable.
25   * <p/>
26   * <i>Implementation note:</i>  The plugin's container is searched, instead of
27   * a general search for all OSGi services registered against {@link PrefixModuleFactory}, because the factories
28   * have to be available before any modules are created, and Spring DM, for example, ensures all required service
29   * references are available before creating the context, which is then interpreted as an enabled plugin.
30   *
31   * @Since 2.5.0
32   */
33  public class PrefixDelegatingModuleFactory implements ModuleFactory {
34      Logger log = LoggerFactory.getLogger(PrefixDelegatingModuleFactory.class);
35      private final Map<String, ModuleFactory> delegateModuleFactories;
36  
37      public PrefixDelegatingModuleFactory(Set<PrefixModuleFactory> delegates) {
38          Map<String, ModuleFactory> factories = new HashMap<String, ModuleFactory>();
39          for (PrefixModuleFactory factory : delegates) {
40              factories.put(factory.getPrefix(), factory);
41          }
42          this.delegateModuleFactories = factories;
43      }
44  
45      public void addPrefixModuleFactory(PrefixModuleFactory prefixModuleFactory) {
46          delegateModuleFactories.put(prefixModuleFactory.getPrefix(), prefixModuleFactory);
47      }
48  
49      /**
50       * Returns the module factory for a prefix, first using registered prefixes, then any from the plugin's container.
51       *
52       * @param moduleReference  The module reference
53       * @param moduleDescriptor The descriptor containing the module
54       * @return The instance, can return null
55       */
56      protected ModuleFactory getModuleFactoryForPrefix(final ModuleReference moduleReference, ModuleDescriptor<?> moduleDescriptor) {
57          ModuleFactory moduleFactory = delegateModuleFactories.get(moduleReference.prefix);
58          if (moduleFactory == null) {
59              Plugin plugin = moduleDescriptor.getPlugin();
60              if (plugin instanceof ContainerManagedPlugin) {
61                  Collection<PrefixModuleFactory> containerFactories = ((ContainerManagedPlugin) plugin).getContainerAccessor().getBeansOfType(PrefixModuleFactory.class);
62                  for (PrefixModuleFactory prefixModuleFactory : containerFactories) {
63                      if (moduleReference.prefix.equals(prefixModuleFactory.getPrefix())) {
64                          moduleFactory = prefixModuleFactory;
65                          break;
66                      }
67                  }
68              }
69          }
70  
71          return moduleFactory;
72      }
73  
74  
75      public <T> T createModule(String className, final ModuleDescriptor<T> moduleDescriptor) throws PluginParseException {
76          checkNotNull(className, "The className cannot be null");
77          checkNotNull(moduleDescriptor, "The moduleDescriptor cannot be null");
78  
79          final ModuleReference moduleReference = getBeanReference(className);
80  
81          Object result = null;
82  
83          final ModuleFactory moduleFactory = getModuleFactoryForPrefix(moduleReference, moduleDescriptor);
84          if (moduleFactory == null) {
85              throw new PluginParseException("Failed to create a module. Prefix '" + moduleReference.prefix + "' not supported");
86          }
87          try {
88              result = moduleFactory.createModule(moduleReference.beanIdentifier, moduleDescriptor);
89          } catch (NoClassDefFoundError error) {
90              // TinyURL resolves to --> https://developer.atlassian.com/display/DOCS/NoClassDefFoundError
91              log.error("Detected an error (NoClassDefFoundError) instantiating the module for plugin '" + moduleDescriptor.getPlugin().getKey() + "'" + " for module '" + moduleDescriptor.getKey() + "': " + error.getMessage() + ".  This error is usually caused by your" + " plugin using a imported component class that itself relies on other packages in the product. You can probably fix this by" + " adding the missing class's package to your <Import-Package> instructions; for more details on how to fix this, see https://developer.atlassian.com/display/DOCS/NoClassDefFoundError .");
92              throw error;
93          } catch (LinkageError error) {
94              // TinyURL resolves to --> https://developer.atlassian.com/display/DOCS/LinkageError
95              log.error("Detected an error (LinkageError) instantiating the module for plugin '" + moduleDescriptor.getPlugin().getKey() + "'" + " for module '" + moduleDescriptor.getKey() + "': " + error.getMessage() + ".  This error is usually caused by your" + " plugin including copies of libraries in META-INF/lib unnecessarily. For more details on how to fix this, see" + " https://developer.atlassian.com/x/EgAN .");
96              throw error;
97          } catch (RuntimeException ex) {
98              if (ex.getClass().getSimpleName().equals("UnsatisfiedDependencyException")) {
99                  // TinyURL resolves to --> https://developer.atlassian.com/display/DOCS/UnsatisfiedDependencyException+-+Error+creating+bean+with+name
100                 log.error("Detected an error instantiating the module via Spring. This usually means that you haven't created a " + "<component-import> for the interface you're trying to use. https://developer.atlassian.com/x/TAEr " + " for more details.");
101             }
102             throw ex;
103         }
104 
105         if (result != null) {
106             return (T) result;
107         } else {
108             throw new PluginParseException("Unable to create module instance from '" + className + "'");
109         }
110     }
111 
112 
113     private ModuleReference getBeanReference(String className) {
114         String prefix = "class";
115         final int prefixIndex = className.indexOf(":");
116         if (prefixIndex != -1) {
117             prefix = className.substring(0, prefixIndex);
118             className = className.substring(prefixIndex + 1);
119         }
120         return new ModuleReference(prefix, className);
121     }
122 
123     /**
124      * This is not to be used.  It is only for backwards compatibility with old code that uses
125      * {@link com.atlassian.plugin.PluginAccessor#getEnabledModulesByClass(Class)}.  This method can and will be
126      * removed without warning.
127      *
128      * @param name             The class name
129      * @param moduleDescriptor The module descriptor
130      * @param <T>              The module class type
131      * @return The module class
132      * @throws ModuleClassNotFoundException
133      * @deprecated Since 2.5.0
134      */
135     @Deprecated
136     public <T> Class<T> guessModuleClass(final String name, final ModuleDescriptor<T> moduleDescriptor) throws ModuleClassNotFoundException {
137         checkNotNull(name, "The class name cannot be null");
138         checkNotNull(moduleDescriptor, "The module descriptor cannot be null");
139 
140         final ModuleReference moduleReference = getBeanReference(name);
141 
142         final ModuleFactory moduleFactory = getModuleFactoryForPrefix(moduleReference, moduleDescriptor);
143         Class<T> result = null;
144         if (moduleFactory instanceof ClassPrefixModuleFactory) {
145             result = ((ClassPrefixModuleFactory) moduleFactory).getModuleClass(moduleReference.beanIdentifier, moduleDescriptor);
146         }
147 
148         return result;
149 
150     }
151 
152     private static class ModuleReference {
153         public String prefix;
154         public String beanIdentifier;
155 
156         ModuleReference(String prefix, String beanIdentifier) {
157             this.prefix = prefix;
158             this.beanIdentifier = beanIdentifier;
159         }
160     }
161 }