View Javadoc

1   package com.atlassian.plugin.module;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.PluginParseException;
5   import com.atlassian.plugin.Plugin;
6   import org.apache.commons.lang.Validate;
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  import java.util.Map;
11  import java.util.HashMap;
12  import java.util.Set;
13  import java.util.Collection;
14  
15  /**
16   * The default implementation of a {@link ModuleFactory}.
17   * The module class name can contain a prefix and this prefix determines which {@link com.atlassian.plugin.module.ModuleFactory}
18   * is used to create the java class for this module descriptor.
19   * <p/>
20   * If no prefix is supplied it will use {@link ClassPrefixModuleFactory} to create the module class object.
21   * <p/>
22   * ModuleFactories are located via the following algorithm.  First, the prefixes registered during construction are searched, then
23   * any implementations in the plugin's container, if applicable.
24   * <p/>
25   * <i>Implementation note:</i>  The plugin's container is searched, instead of
26   * a general search for all OSGi services registered against {@link PrefixModuleFactory}, because the factories
27   * have to be available before any modules are created, and Spring DM, for example, ensures all required service
28   * references are available before creating the context, which is then interpreted as an enabled plugin.
29   *
30   * @Since 2.5.0
31   */
32  public class PrefixDelegatingModuleFactory implements ModuleFactory
33  {
34      Logger log = LoggerFactory.getLogger(PrefixDelegatingModuleFactory.class);
35      private final Map<String, ModuleFactory> delegateModuleFactories;
36  
37      public PrefixDelegatingModuleFactory(Set<PrefixModuleFactory> delegates)
38      {
39          Map<String, ModuleFactory> factories = new HashMap<String, ModuleFactory>();
40          for (PrefixModuleFactory factory : delegates)
41          {
42              factories.put(factory.getPrefix(), factory);
43          }
44          this.delegateModuleFactories = factories;
45      }
46  
47      public void addPrefixModuleFactory(PrefixModuleFactory prefixModuleFactory)
48      {
49          delegateModuleFactories.put(prefixModuleFactory.getPrefix(), prefixModuleFactory);
50      }
51  
52      /**
53       * Returns the module factory for a prefix, first using registered prefixes, then any from the plugin's container.
54       *
55       * @param moduleReference  The module reference
56       * @param moduleDescriptor The descriptor containing the module
57       * @return The instance, can return null
58       */
59      protected ModuleFactory getModuleFactoryForPrefix(final ModuleReference moduleReference, ModuleDescriptor<?> moduleDescriptor)
60      {
61          ModuleFactory moduleFactory = delegateModuleFactories.get(moduleReference.prefix);
62          if (moduleFactory == null)
63          {
64              Plugin plugin = moduleDescriptor.getPlugin();
65              if (plugin instanceof ContainerManagedPlugin)
66              {
67                  Collection<PrefixModuleFactory> containerFactories = ((ContainerManagedPlugin) plugin).getContainerAccessor().getBeansOfType(PrefixModuleFactory.class);
68                  for (PrefixModuleFactory prefixModuleFactory : containerFactories)
69                  {
70                      if (moduleReference.prefix.equals(prefixModuleFactory.getPrefix()))
71                      {
72                          moduleFactory = prefixModuleFactory;
73                          break;
74                      }
75                  }
76              }
77          }
78  
79          return moduleFactory;
80      }
81  
82  
83      public <T> T createModule(String className, final ModuleDescriptor<T> moduleDescriptor) throws PluginParseException
84      {
85          Validate.notNull(className, "The className cannot be null");
86          Validate.notNull(moduleDescriptor, "The moduleDescriptor cannot be null");
87  
88          final ModuleReference moduleReference = getBeanReference(className);
89  
90          Object result = null;
91  
92          final ModuleFactory moduleFactory = getModuleFactoryForPrefix(moduleReference, moduleDescriptor);
93          if (moduleFactory == null)
94          {
95              throw new PluginParseException("Failed to create a module. Prefix '" + moduleReference.prefix + "' not supported");
96          }
97          try
98          {
99              result = moduleFactory.createModule(moduleReference.beanIdentifier, moduleDescriptor);
100         }
101         catch (NoClassDefFoundError error)
102         {
103             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" + " http://confluence.atlassian.com/x/QRS-Cg .");
104             throw error;
105         }
106         catch (LinkageError error)
107         {
108             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" + " http://confluence.atlassian.com/x/yQEhCw .");
109             throw error;
110         }
111         catch (RuntimeException ex)
112         {
113             if (ex.getClass().getSimpleName().equals("UnsatisfiedDependencyException"))
114             {
115                 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. See http://confluence.atlassian.com/x/kgL3CQ " + " for more details.");
116             }
117             throw ex;
118         }
119 
120         if (result != null)
121         {
122             return (T) result;
123         }
124         else
125         {
126             throw new PluginParseException("Unable to create module instance from '" + className + "'");
127         }
128     }
129 
130 
131     private ModuleReference getBeanReference(String className)
132     {
133         String prefix = "class";
134         final int prefixIndex = className.indexOf(":");
135         if (prefixIndex != -1)
136         {
137             prefix = className.substring(0, prefixIndex);
138             className = className.substring(prefixIndex + 1);
139         }
140         return new ModuleReference(prefix, className);
141     }
142 
143     /**
144      * This is not to be used.  It is only for backwards compatibility with old code that uses
145      * {@link com.atlassian.plugin.PluginAccessor#getEnabledModulesByClass(Class)}.  This method can and will be
146      * removed without warning.
147      *
148      * @param name             The class name
149      * @param moduleDescriptor The module descriptor
150      * @param <T>              The module class type
151      * @return The module class
152      * @throws ModuleClassNotFoundException
153      * @deprecated Since 2.5.0
154      */
155     @Deprecated
156     public <T> Class<T> guessModuleClass(final String name, final ModuleDescriptor<T> moduleDescriptor) throws ModuleClassNotFoundException
157     {
158         Validate.notNull(name, "The class name cannot be null");
159         Validate.notNull(moduleDescriptor, "The module descriptor cannot be null");
160 
161         final ModuleReference moduleReference = getBeanReference(name);
162 
163         final ModuleFactory moduleFactory = getModuleFactoryForPrefix(moduleReference, moduleDescriptor);
164         Class<T> result = null;
165         if (moduleFactory instanceof ClassPrefixModuleFactory)
166         {
167             result = ((ClassPrefixModuleFactory) moduleFactory).getModuleClass(moduleReference.beanIdentifier, moduleDescriptor);
168         }
169 
170         return result;
171 
172     }
173 
174     private static class ModuleReference
175     {
176         public String prefix;
177         public String beanIdentifier;
178 
179         ModuleReference(String prefix, String beanIdentifier)
180         {
181             this.prefix = prefix;
182             this.beanIdentifier = beanIdentifier;
183         }
184     }
185 }