View Javadoc

1   package com.atlassian.plugin.parsers;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.ModuleDescriptorFactory;
5   import com.atlassian.plugin.Plugin;
6   import com.atlassian.plugin.PluginInformation;
7   import com.atlassian.plugin.PluginParseException;
8   import com.atlassian.plugin.Resources;
9   import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
10  import com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory;
11  import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor;
12  import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptorFactory;
13  import com.atlassian.plugin.impl.UnloadablePluginFactory;
14  import com.atlassian.plugin.util.PluginUtils;
15  
16  import org.apache.commons.lang.Validate;
17  import org.dom4j.Document;
18  import org.dom4j.DocumentException;
19  import org.dom4j.Element;
20  import org.dom4j.io.SAXReader;
21  import org.slf4j.Logger;
22  import org.slf4j.LoggerFactory;
23  
24  import java.io.InputStream;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.Set;
30  
31  /**
32   * Provides access to the descriptor information retrieved from an XML InputStream.
33   * <p/>
34   * Uses the dom4j {@link SAXReader} to parse the XML stream into a document
35   * when the parser is constructed.
36   *
37   * @see XmlDescriptorParserFactory
38   */
39  public class XmlDescriptorParser implements DescriptorParser
40  {
41      private static final Logger log = LoggerFactory.getLogger(XmlDescriptorParser.class);
42  
43      private final Document document;
44      private final Set<String> applicationKeys;
45  
46      /**
47       * Constructs a parser with an already-constructed document
48       * @param source the source document
49       * @param applicationKeys the application key to filter modules with, null for all unspecified
50       * @throws PluginParseException if there is a problem reading the descriptor from the XML {@link InputStream}.
51       * @since 2.2.0
52       */
53      public XmlDescriptorParser(final Document source, final String... applicationKeys) throws PluginParseException
54      {
55          Validate.notNull(source, "XML descriptor source document cannot be null");
56          document = source;
57          if (applicationKeys == null)
58          {
59              this.applicationKeys = Collections.emptySet();
60          }
61          else
62          {
63              this.applicationKeys = new HashSet<String>(Arrays.asList(applicationKeys));
64          }
65      }
66  
67      /**
68       * Constructs a parser with a stream of an XML document for a specific application
69       * @param source The descriptor stream
70       * @param applicationKeys the application key to filter modules with, null for all unspecified
71       * @throws PluginParseException if there is a problem reading the descriptor from the XML {@link InputStream}.
72       * @since 2.2.0
73       */
74      public XmlDescriptorParser(final InputStream source, final String... applicationKeys) throws PluginParseException
75      {
76          Validate.notNull(source, "XML descriptor source cannot be null");
77          document = createDocument(source);
78          if (applicationKeys == null)
79          {
80              this.applicationKeys = Collections.emptySet();
81          }
82          else
83          {
84              this.applicationKeys = new HashSet<String>(Arrays.asList(applicationKeys));
85          }
86      }
87  
88      protected Document createDocument(final InputStream source) throws PluginParseException
89      {
90          final SAXReader reader = new SAXReader();
91          reader.setMergeAdjacentText(true);
92          try
93          {
94              return reader.read(source);
95          }
96          catch (final DocumentException e)
97          {
98              throw new PluginParseException("Cannot parse XML plugin descriptor", e);
99          }
100     }
101 
102     protected Document getDocument()
103     {
104         return document;
105     }
106 
107     public Plugin configurePlugin(final ModuleDescriptorFactory moduleDescriptorFactory, final Plugin plugin) throws PluginParseException
108     {
109         final Element pluginElement = getPluginElement();
110         plugin.setName(pluginElement.attributeValue("name"));
111         plugin.setKey(getKey());
112         plugin.setPluginsVersion(getPluginsVersion());
113         plugin.setSystemPlugin(isSystemPlugin());
114 
115         if (pluginElement.attributeValue("i18n-name-key") != null)
116         {
117             plugin.setI18nNameKey(pluginElement.attributeValue("i18n-name-key"));
118         }
119 
120         if (plugin.getKey().indexOf(":") > 0)
121         {
122             throw new PluginParseException("Plugin keys cannot contain ':'. Key is '" + plugin.getKey() + "'");
123         }
124 
125         if ("disabled".equalsIgnoreCase(pluginElement.attributeValue("state")))
126         {
127             plugin.setEnabledByDefault(false);
128         }
129         
130         plugin.setResources(Resources.fromXml(pluginElement));
131         
132         for (final Iterator i = pluginElement.elementIterator(); i.hasNext();)
133         {
134             final Element element = (Element) i.next();
135 
136             if ("plugin-info".equalsIgnoreCase(element.getName()))
137             {
138                 plugin.setPluginInformation(createPluginInformation(element));
139             }
140             else if (!"resource".equalsIgnoreCase(element.getName()))
141             {
142                 final ModuleDescriptor<?> moduleDescriptor = createModuleDescriptor(plugin, element, moduleDescriptorFactory);
143 
144                 // If we're not loading the module descriptor, null is returned, so we skip it
145                 if (moduleDescriptor == null)
146                 {
147                     continue;
148                 }
149 
150                 if (plugin.getModuleDescriptor(moduleDescriptor.getKey()) != null)
151                 {
152                     throw new PluginParseException("Found duplicate key '" + moduleDescriptor.getKey() + "' within plugin '" + plugin.getKey() + "'");
153                 }
154 
155                 plugin.addModuleDescriptor(moduleDescriptor);
156 
157                 // If we have any unloadable modules, also create an unloadable plugin, which will make it clear that there was a problem
158                 if (moduleDescriptor instanceof UnloadableModuleDescriptor)
159                 {
160                     log.error("There were errors loading the plugin '" + plugin.getName() + "'. The plugin has been disabled.");
161                     return UnloadablePluginFactory.createUnloadablePlugin(plugin);
162                 }
163             }
164         }
165         
166         return plugin;
167     }
168 
169     private Element getPluginElement()
170     {
171         return document.getRootElement();
172     }
173 
174     protected ModuleDescriptor<?> createModuleDescriptor(final Plugin plugin, final Element element, final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
175     {
176         final String name = element.getName();
177 
178         // Determine if this module descriptor is applicable for the current application
179         if (!PluginUtils.doesModuleElementApplyToApplication(element, applicationKeys))
180         {
181             log.debug("Ignoring module descriptor for this application: " + element.attributeValue("key"));
182             return null;
183         }
184 
185         ModuleDescriptor<?> moduleDescriptorDescriptor;
186 
187         // Try to retrieve the module descriptor
188         try
189         {
190             moduleDescriptorDescriptor = moduleDescriptorFactory.getModuleDescriptor(name);
191         }
192         // When there's a problem loading a module, return an UnrecognisedModuleDescriptor with error
193         catch (final Throwable e)
194         {
195             final UnrecognisedModuleDescriptor descriptor = UnrecognisedModuleDescriptorFactory.createUnrecognisedModuleDescriptor(plugin, element,
196                 e, moduleDescriptorFactory);
197 
198             log.error("There were problems loading the module '" + name + "' in plugin '" + plugin.getName() + "'. The module has been disabled.");
199             log.error(descriptor.getErrorText(), e);
200 
201             return descriptor;
202         }
203 
204         // When the module descriptor has been excluded, null is returned (PLUG-5)
205         if (moduleDescriptorDescriptor == null)
206         {
207             log.info("The module '" + name + "' in plugin '" + plugin.getName() + "' is in the list of excluded module descriptors, so not enabling.");
208             return null;
209         }
210 
211         // Once we have the module descriptor, create it using the given information
212         try
213         {
214             moduleDescriptorDescriptor.init(plugin, element);
215         }
216         // If it fails, return a dummy module that contains the error
217         catch (final Exception e)
218         {
219             final UnloadableModuleDescriptor descriptor = UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor(plugin, element, e,
220                 moduleDescriptorFactory);
221 
222             log.error("There were problems loading the module '" + name + "'. The module and its plugin have been disabled.");
223             log.error(descriptor.getErrorText(), e);
224 
225             return descriptor;
226         }
227 
228         return moduleDescriptorDescriptor;
229     }
230 
231     protected PluginInformation createPluginInformation(final Element element)
232     {
233         final PluginInformation pluginInfo = new PluginInformation();
234 
235         if (element.element("description") != null)
236         {
237             pluginInfo.setDescription(element.element("description").getTextTrim());
238             if (element.element("description").attributeValue("key") != null)
239             {
240                 pluginInfo.setDescriptionKey(element.element("description").attributeValue("key"));
241             }
242         }
243 
244         if (element.element("version") != null)
245         {
246             pluginInfo.setVersion(element.element("version").getTextTrim());
247         }
248 
249         if (element.element("vendor") != null)
250         {
251             final Element vendor = element.element("vendor");
252             pluginInfo.setVendorName(vendor.attributeValue("name"));
253             pluginInfo.setVendorUrl(vendor.attributeValue("url"));
254         }
255 
256         // initialize any parameters on the plugin xml definition
257         for (final Iterator<Element> iterator = element.elements("param").iterator(); iterator.hasNext();)
258         {
259             final Element param = iterator.next();
260 
261             // Retrieve the parameter info => name & text
262             if (param.attribute("name") != null)
263             {
264                 pluginInfo.addParameter(param.attribute("name").getData().toString(), param.getText());
265             }
266         }
267 
268         if (element.element("application-version") != null)
269         {
270             final Element ver = element.element("application-version");
271             if (ver.attribute("max") != null)
272             {
273                 pluginInfo.setMaxVersion(Float.parseFloat(ver.attributeValue("max")));
274             }
275             if (ver.attribute("min") != null)
276             {
277                 pluginInfo.setMinVersion(Float.parseFloat(ver.attributeValue("min")));
278             }
279         }
280 
281         if (element.element("java-version") != null)
282         {
283             pluginInfo.setMinJavaVersion(Float.valueOf(element.element("java-version").attributeValue("min")));
284         }
285 
286         return pluginInfo;
287     }
288 
289     public String getKey()
290     {
291         return getPluginElement().attributeValue("key");
292     }
293 
294     public int getPluginsVersion()
295     {
296         String val = getPluginElement().attributeValue("pluginsVersion");
297         if (val == null)
298         {
299             val = getPluginElement().attributeValue("plugins-version");
300         }
301         if (val != null)
302         {
303             try
304             {
305                 return Integer.parseInt(val);
306             }
307             catch (final NumberFormatException e)
308             {
309                 throw new RuntimeException("Could not parse pluginsVersion: " + e.getMessage(), e);
310             }
311         }
312         else
313         {
314             return 1;
315         }
316     }
317 
318     public PluginInformation getPluginInformation()
319     {
320         return createPluginInformation(getDocument().getRootElement().element("plugin-info"));
321     }
322 
323     public boolean isSystemPlugin()
324     {
325         return "true".equalsIgnoreCase(getPluginElement().attributeValue("system"));
326     }
327 }