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  {
35      Logger log = LoggerFactory.getLogger(PrefixDelegatingModuleFactory.class);
36      private final Map<String, ModuleFactory> delegateModuleFactories;
37  
38      public PrefixDelegatingModuleFactory(Set<PrefixModuleFactory> delegates)
39      {
40          Map<String, ModuleFactory> factories = new HashMap<String, ModuleFactory>();
41          for (PrefixModuleFactory factory : delegates)
42          {
43              factories.put(factory.getPrefix(), factory);
44          }
45          this.delegateModuleFactories = factories;
46      }
47  
48      public void addPrefixModuleFactory(PrefixModuleFactory prefixModuleFactory)
49      {
50          delegateModuleFactories.put(prefixModuleFactory.getPrefix(), prefixModuleFactory);
51      }
52  
53      /**
54       * Returns the module factory for a prefix, first using registered prefixes, then any from the plugin's container.
55       *
56       * @param moduleReference  The module reference
57       * @param moduleDescriptor The descriptor containing the module
58       * @return The instance, can return null
59       */
60      protected ModuleFactory getModuleFactoryForPrefix(final ModuleReference moduleReference, ModuleDescriptor<?> moduleDescriptor)
61      {
62          ModuleFactory moduleFactory = delegateModuleFactories.get(moduleReference.prefix);
63          if (moduleFactory == null)
64          {
65              Plugin plugin = moduleDescriptor.getPlugin();
66              if (plugin instanceof ContainerManagedPlugin)
67              {
68                  Collection<PrefixModuleFactory> containerFactories = ((ContainerManagedPlugin) plugin).getContainerAccessor().getBeansOfType(PrefixModuleFactory.class);
69                  for (PrefixModuleFactory prefixModuleFactory : containerFactories)
70                  {
71                      if (moduleReference.prefix.equals(prefixModuleFactory.getPrefix()))
72                      {
73                          moduleFactory = prefixModuleFactory;
74                          break;
75                      }
76                  }
77              }
78          }
79  
80          return moduleFactory;
81      }
82  
83  
84      public <T> T createModule(String className, final ModuleDescriptor<T> moduleDescriptor) throws PluginParseException
85      {
86          checkNotNull(className, "The className cannot be null");
87          checkNotNull(moduleDescriptor, "The moduleDescriptor cannot be null");
88  
89          final ModuleReference moduleReference = getBeanReference(className);
90  
91          Object result = null;
92  
93          final ModuleFactory moduleFactory = getModuleFactoryForPrefix(moduleReference, moduleDescriptor);
94          if (moduleFactory == null)
95          {
96              throw new PluginParseException("Failed to create a module. Prefix '" + moduleReference.prefix + "' not supported");
97          }
98          try
99          {
100             result = moduleFactory.createModule(moduleReference.beanIdentifier, moduleDescriptor);
101         }
102         catch (NoClassDefFoundError error)
103         {
104             // TinyURL resolves to --> https://developer.atlassian.com/display/DOCS/NoClassDefFoundError
105             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 .");
106             throw error;
107         }
108         catch (LinkageError error)
109         {
110             // TinyURL resolves to --> https://developer.atlassian.com/display/DOCS/LinkageError
111             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 .");
112             throw error;
113         }
114         catch (RuntimeException ex)
115         {
116             if (ex.getClass().getSimpleName().equals("UnsatisfiedDependencyException"))
117             {
118                 // TinyURL resolves to --> https://developer.atlassian.com/display/DOCS/UnsatisfiedDependencyException+-+Error+creating+bean+with+name
119                 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.");
120             }
121             throw ex;
122         }
123 
124         if (result != null)
125         {
126             return (T) result;
127         }
128         else
129         {
130             throw new PluginParseException("Unable to create module instance from '" + className + "'");
131         }
132     }
133 
134 
135     private ModuleReference getBeanReference(String className)
136     {
137         String prefix = "class";
138         final int prefixIndex = className.indexOf(":");
139         if (prefixIndex != -1)
140         {
141             prefix = className.substring(0, prefixIndex);
142             className = className.substring(prefixIndex + 1);
143         }
144         return new ModuleReference(prefix, className);
145     }
146 
147     /**
148      * This is not to be used.  It is only for backwards compatibility with old code that uses
149      * {@link com.atlassian.plugin.PluginAccessor#getEnabledModulesByClass(Class)}.  This method can and will be
150      * removed without warning.
151      *
152      * @param name             The class name
153      * @param moduleDescriptor The module descriptor
154      * @param <T>              The module class type
155      * @return The module class
156      * @throws ModuleClassNotFoundException
157      * @deprecated Since 2.5.0
158      */
159     @Deprecated
160     public <T> Class<T> guessModuleClass(final String name, final ModuleDescriptor<T> moduleDescriptor) throws ModuleClassNotFoundException
161     {
162         checkNotNull(name, "The class name cannot be null");
163         checkNotNull(moduleDescriptor, "The module descriptor cannot be null");
164 
165         final ModuleReference moduleReference = getBeanReference(name);
166 
167         final ModuleFactory moduleFactory = getModuleFactoryForPrefix(moduleReference, moduleDescriptor);
168         Class<T> result = null;
169         if (moduleFactory instanceof ClassPrefixModuleFactory)
170         {
171             result = ((ClassPrefixModuleFactory) moduleFactory).getModuleClass(moduleReference.beanIdentifier, moduleDescriptor);
172         }
173 
174         return result;
175 
176     }
177 
178     private static class ModuleReference
179     {
180         public String prefix;
181         public String beanIdentifier;
182 
183         ModuleReference(String prefix, String beanIdentifier)
184         {
185             this.prefix = prefix;
186             this.beanIdentifier = beanIdentifier;
187         }
188     }
189 }