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.apache.commons.logging.Log;
18  import org.apache.commons.logging.LogFactory;
19  import org.dom4j.Document;
20  import org.dom4j.DocumentException;
21  import org.dom4j.Element;
22  import org.dom4j.io.SAXReader;
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 Log log = LogFactory.getLog(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          try
92          {
93              return reader.read(source);
94          }
95          catch (final DocumentException e)
96          {
97              throw new PluginParseException("Cannot parse XML plugin descriptor", e);
98          }
99      }
100 
101     protected Document getDocument()
102     {
103         return document;
104     }
105 
106     public Plugin configurePlugin(final ModuleDescriptorFactory moduleDescriptorFactory, final Plugin plugin) throws PluginParseException
107     {
108 
109         final Element pluginElement = getPluginElement();
110         plugin.setName(pluginElement.attributeValue("name"));
111         plugin.setKey(getKey());
112         plugin.setPluginsVersion(getPluginsVersion());
113 
114         if (pluginElement.attributeValue("i18n-name-key") != null)
115         {
116             plugin.setI18nNameKey(pluginElement.attributeValue("i18n-name-key"));
117         }
118 
119         if (plugin.getKey().indexOf(":") > 0)
120         {
121             throw new PluginParseException("Plugin keys cannot contain ':'. Key is '" + plugin.getKey() + "'");
122         }
123 
124         if ("disabled".equalsIgnoreCase(pluginElement.attributeValue("state")))
125         {
126             plugin.setEnabledByDefault(false);
127         }
128 
129         for (final Iterator i = pluginElement.elementIterator(); i.hasNext();)
130         {
131             final Element element = (Element) i.next();
132 
133             if ("plugin-info".equalsIgnoreCase(element.getName()))
134             {
135                 plugin.setPluginInformation(createPluginInformation(element));
136             }
137             else if (!"resource".equalsIgnoreCase(element.getName()))
138             {
139                 final ModuleDescriptor<?> moduleDescriptor = createModuleDescriptor(plugin, element, moduleDescriptorFactory);
140 
141                 // If we're not loading the module descriptor, null is returned, so we skip it
142                 if (moduleDescriptor == null)
143                 {
144                     continue;
145                 }
146 
147                 if (plugin.getModuleDescriptor(moduleDescriptor.getKey()) != null)
148                 {
149                     throw new PluginParseException("Found duplicate key '" + moduleDescriptor.getKey() + "' within plugin '" + plugin.getKey() + "'");
150                 }
151 
152                 plugin.addModuleDescriptor(moduleDescriptor);
153 
154                 // If we have any unloadable modules, also create an unloadable plugin, which will make it clear that there was a problem
155                 if (moduleDescriptor instanceof UnloadableModuleDescriptor)
156                 {
157                     log.error("There were errors loading the plugin '" + plugin.getName() + "'. The plugin has been disabled.");
158                     return UnloadablePluginFactory.createUnloadablePlugin(plugin);
159                 }
160             }
161         }
162 
163         plugin.setResources(Resources.fromXml(pluginElement));
164 
165         return plugin;
166     }
167 
168     private Element getPluginElement()
169     {
170         return document.getRootElement();
171     }
172 
173     protected ModuleDescriptor<?> createModuleDescriptor(final Plugin plugin, final Element element, final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
174     {
175         final String name = element.getName();
176 
177         // Determine if this module descriptor is applicable for the current application
178         if (!PluginUtils.doesModuleElementApplyToApplication(element, applicationKeys))
179         {
180             log.debug("Ignoring module descriptor for this application: " + element.attributeValue("key"));
181             return null;
182         }
183 
184         ModuleDescriptor<?> moduleDescriptorDescriptor;
185 
186         // Try to retrieve the module descriptor
187         try
188         {
189             moduleDescriptorDescriptor = moduleDescriptorFactory.getModuleDescriptor(name);
190         }
191         // When there's a problem loading a module, return an UnrecognisedModuleDescriptor with error
192         catch (final Throwable e)
193         {
194             final UnrecognisedModuleDescriptor descriptor = UnrecognisedModuleDescriptorFactory.createUnrecognisedModuleDescriptor(plugin, element,
195                 e, moduleDescriptorFactory);
196 
197             log.error("There were problems loading the module '" + name + "' in plugin '" + plugin.getName() + "'. The module has been disabled.");
198             log.error(descriptor.getErrorText(), e);
199 
200             return descriptor;
201         }
202 
203         // When the module descriptor has been excluded, null is returned (PLUG-5)
204         if (moduleDescriptorDescriptor == null)
205         {
206             log.info("The module '" + name + "' in plugin '" + plugin.getName() + "' is in the list of excluded module descriptors, so not enabling.");
207             return null;
208         }
209 
210         // Once we have the module descriptor, create it using the given information
211         try
212         {
213             moduleDescriptorDescriptor.init(plugin, element);
214         }
215         // If it fails, return a dummy module that contains the error
216         catch (final Exception e)
217         {
218             final UnloadableModuleDescriptor descriptor = UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor(plugin, element, e,
219                 moduleDescriptorFactory);
220 
221             log.error("There were problems loading the module '" + name + "'. The module and its plugin have been disabled.");
222             log.error(descriptor.getErrorText(), e);
223 
224             return descriptor;
225         }
226 
227         return moduleDescriptorDescriptor;
228     }
229 
230     protected PluginInformation createPluginInformation(final Element element)
231     {
232         final PluginInformation pluginInfo = new PluginInformation();
233 
234         if (element.element("description") != null)
235         {
236             pluginInfo.setDescription(element.element("description").getTextTrim());
237             if (element.element("description").attributeValue("key") != null)
238             {
239                 pluginInfo.setDescriptionKey(element.element("description").attributeValue("key"));
240             }
241         }
242 
243         if (element.element("version") != null)
244         {
245             pluginInfo.setVersion(element.element("version").getTextTrim());
246         }
247 
248         if (element.element("vendor") != null)
249         {
250             final Element vendor = element.element("vendor");
251             pluginInfo.setVendorName(vendor.attributeValue("name"));
252             pluginInfo.setVendorUrl(vendor.attributeValue("url"));
253         }
254 
255         // initialize any parameters on the plugin xml definition
256         for (final Iterator<Element> iterator = element.elements("param").iterator(); iterator.hasNext();)
257         {
258             final Element param = iterator.next();
259 
260             // Retrieve the parameter info => name & text
261             if (param.attribute("name") != null)
262             {
263                 pluginInfo.addParameter(param.attribute("name").getData().toString(), param.getText());
264             }
265         }
266 
267         if (element.element("application-version") != null)
268         {
269             final Element ver = element.element("application-version");
270             if (ver.attribute("max") != null)
271             {
272                 pluginInfo.setMaxVersion(Float.parseFloat(ver.attributeValue("max")));
273             }
274             if (ver.attribute("min") != null)
275             {
276                 pluginInfo.setMinVersion(Float.parseFloat(ver.attributeValue("min")));
277             }
278         }
279 
280         if (element.element("java-version") != null)
281         {
282             pluginInfo.setMinJavaVersion(Float.valueOf(element.element("java-version").attributeValue("min")));
283         }
284 
285         return pluginInfo;
286     }
287 
288     public String getKey()
289     {
290         return getPluginElement().attributeValue("key");
291     }
292 
293     public int getPluginsVersion()
294     {
295         String val = getPluginElement().attributeValue("pluginsVersion");
296         if (val == null)
297         {
298             val = getPluginElement().attributeValue("plugins-version");
299         }
300         if (val != null)
301         {
302             return Integer.parseInt(val);
303         }
304         else
305         {
306             return 1;
307         }
308     }
309 
310     public PluginInformation getPluginInformation()
311     {
312         return createPluginInformation(getDocument().getRootElement().element("plugin-info"));
313     }
314 
315     public boolean isSystemPlugin()
316     {
317         return "true".equalsIgnoreCase(getPluginElement().attributeValue("system"));
318     }
319 }