View Javadoc

1   package com.atlassian.plugin.parsers;
2   
3   import com.atlassian.fugue.Option;
4   import com.atlassian.plugin.Application;
5   import com.atlassian.plugin.InstallationMode;
6   import com.atlassian.plugin.ModuleDescriptor;
7   import com.atlassian.plugin.ModuleDescriptorFactory;
8   import com.atlassian.plugin.Plugin;
9   import com.atlassian.plugin.PluginInformation;
10  import com.atlassian.plugin.PluginParseException;
11  import com.atlassian.plugin.PluginPermission;
12  import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
13  import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor;
14  import com.atlassian.plugin.impl.UnloadablePluginFactory;
15  import com.google.common.base.Function;
16  import com.google.common.collect.ImmutableSet;
17  import org.apache.commons.lang.StringUtils;
18  import org.dom4j.Document;
19  import org.dom4j.DocumentException;
20  import org.dom4j.Element;
21  import org.dom4j.io.SAXReader;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  import java.io.InputStream;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import static com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor;
30  import static com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptorFactory.createUnrecognisedModuleDescriptor;
31  import static com.google.common.base.Preconditions.checkNotNull;
32  import static com.google.common.collect.ImmutableList.copyOf;
33  import static com.google.common.collect.Iterables.transform;
34  
35  /**
36   * Provides access to the descriptor information retrieved from an XML InputStream.
37   * <p/>
38   * Uses the dom4j {@link SAXReader} to parse the XML stream into a document
39   * when the parser is constructed.
40   *
41   * @see XmlDescriptorParserFactory
42   */
43  public class XmlDescriptorParser implements DescriptorParser
44  {
45      private static final Logger log = LoggerFactory.getLogger(XmlDescriptorParser.class);
46  
47      private final PluginDescriptorReader descriptorReader;
48  
49      /**
50       * Constructs a parser with an already-constructed document
51       *
52       * @param source the source document
53       * @param applications the application key to filter modules with, null for all unspecified
54       * @throws PluginParseException if there is a problem reading the descriptor from the XML {@link InputStream}.
55       * @since 2.2.0
56       */
57      public XmlDescriptorParser(final Document source, final Set<Application> applications) throws PluginParseException
58      {
59          this.descriptorReader = new PluginDescriptorReader(checkNotNull(source, "XML descriptor source document cannot be null"), checkNotNull(applications));
60      }
61  
62      /**
63       * Constructs a parser with a stream of an XML document for a specific application
64       *
65       * @param source The descriptor stream
66       * @param applications the application key to filter modules with, null for all unspecified
67       * @throws PluginParseException if there is a problem reading the descriptor from the XML {@link InputStream}.
68       * @since 2.2.0
69       */
70      public XmlDescriptorParser(final InputStream source, final Set<Application> applications) throws PluginParseException
71      {
72          this(createDocument(checkNotNull(source, "XML descriptor source cannot be null")), applications);
73      }
74  
75      protected static Document createDocument(final InputStream source) throws PluginParseException
76      {
77          final SAXReader reader = new SAXReader();
78          reader.setMergeAdjacentText(true);
79          try
80          {
81              return reader.read(source);
82          }
83          catch (final DocumentException e)
84          {
85              throw new PluginParseException("Cannot parse XML plugin descriptor", e);
86          }
87      }
88  
89      protected Document getDocument()
90      {
91          return descriptorReader.getDescriptor();
92      }
93  
94      public Plugin configurePlugin(final ModuleDescriptorFactory moduleDescriptorFactory, final Plugin plugin) throws PluginParseException
95      {
96          plugin.setName(descriptorReader.getPluginName());
97          plugin.setKey(getKey());
98          plugin.setPluginsVersion(getPluginsVersion());
99          plugin.setSystemPlugin(isSystemPlugin());
100         plugin.setI18nNameKey(descriptorReader.getI18nPluginNameKey().getOrElse(plugin.getI18nNameKey()));
101 
102         if (plugin.getKey().indexOf(":") > 0)
103         {
104             throw new PluginParseException("Plugin keys cannot contain ':'. Key is '" + plugin.getKey() + "'");
105         }
106 
107         plugin.setEnabledByDefault(descriptorReader.isEnabledByDefault());
108         plugin.setResources(descriptorReader.getResources());
109         plugin.setPluginInformation(createPluginInformation());
110 
111         for (Element module : descriptorReader.getModules(plugin.getInstallationMode()))
112         {
113             final ModuleDescriptor<?> moduleDescriptor = createModuleDescriptor(plugin, module, moduleDescriptorFactory);
114 
115             // If we're not loading the module descriptor, null is returned, so we skip it
116             if (moduleDescriptor == null)
117             {
118                 continue;
119             }
120 
121             if (plugin.getModuleDescriptor(moduleDescriptor.getKey()) != null)
122             {
123                 throw new PluginParseException("Found duplicate key '" + moduleDescriptor.getKey() + "' within plugin '" + plugin.getKey() + "'");
124             }
125 
126             plugin.addModuleDescriptor(moduleDescriptor);
127 
128             // If we have any unloadable modules, also create an unloadable plugin, which will make it clear that there was a problem
129             if (moduleDescriptor instanceof UnloadableModuleDescriptor)
130             {
131                 log.error("There were errors loading the plugin '" + plugin.getName() + "'. The plugin has been disabled.");
132                 return UnloadablePluginFactory.createUnloadablePlugin(plugin);
133             }
134         }
135         return plugin;
136     }
137 
138     protected ModuleDescriptor<?> createModuleDescriptor(final Plugin plugin, final Element element, final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
139     {
140         final String name = element.getName();
141 
142         final ModuleDescriptor<?> moduleDescriptorDescriptor;
143 
144         // Try to retrieve the module descriptor
145         try
146         {
147             moduleDescriptorDescriptor = moduleDescriptorFactory.getModuleDescriptor(name);
148         }
149         // When there's a problem loading a module, return an UnrecognisedModuleDescriptor with error
150         catch (final Throwable e)
151         {
152             final UnrecognisedModuleDescriptor descriptor = createUnrecognisedModuleDescriptor(plugin, element, e, moduleDescriptorFactory);
153 
154             log.error("There were problems loading the module '{}' in plugin '{}'. The module has been disabled.", name, plugin.getName());
155             log.error(descriptor.getErrorText(), e);
156 
157             return descriptor;
158         }
159 
160         // When the module descriptor has been excluded, null is returned (PLUG-5)
161         if (moduleDescriptorDescriptor == null)
162         {
163             log.info("The module '{}' in plugin '{}' is in the list of excluded module descriptors, so not enabling.", name, plugin.getName());
164             return null;
165         }
166 
167         // Once we have the module descriptor, create it using the given information
168         try
169         {
170             moduleDescriptorDescriptor.init(plugin, element);
171         }
172         // If it fails, return a dummy module that contains the error
173         catch (final Exception e)
174         {
175             final UnloadableModuleDescriptor descriptor = createUnloadableModuleDescriptor(plugin, element, e, moduleDescriptorFactory);
176 
177             log.error("There were problems loading the module '{}'. The module and its plugin have been disabled.", name);
178             log.error(descriptor.getErrorText(), e);
179 
180             return descriptor;
181         }
182 
183         return moduleDescriptorDescriptor;
184     }
185 
186     protected PluginInformation createPluginInformation()
187     {
188         final PluginInformationReader pluginInformationReader = descriptorReader.getPluginInformationReader();
189 
190         final PluginInformation pluginInfo = new PluginInformation();
191         pluginInfo.setDescription(pluginInformationReader.getDescription().getOrElse(pluginInfo.getDescription()));
192         pluginInfo.setDescriptionKey(pluginInformationReader.getDescriptionKey().getOrElse(pluginInfo.getDescriptionKey()));
193         pluginInfo.setVersion(pluginInformationReader.getVersion().getOrElse(pluginInfo.getVersion()));
194         pluginInfo.setVendorName(pluginInformationReader.getVendorName().getOrElse(pluginInfo.getVendorName()));
195         pluginInfo.setVendorUrl(pluginInformationReader.getVendorUrl().getOrElse(pluginInfo.getVendorUrl()));
196         for (Map.Entry<String, String> param : pluginInformationReader.getParameters().entrySet())
197         {
198             pluginInfo.addParameter(param.getKey(), param.getValue());
199         }
200         pluginInfo.setMinVersion(pluginInformationReader.getMinVersion().getOrElse(pluginInfo.getMinVersion()));
201         pluginInfo.setMaxVersion(pluginInformationReader.getMaxVersion().getOrElse(pluginInfo.getMaxVersion()));
202         pluginInfo.setMinJavaVersion(pluginInformationReader.getMinJavaVersion().getOrElse(pluginInfo.getMinJavaVersion()));
203 
204         final Map<String, Option<String>> readPermissions = pluginInformationReader.getPermissions();
205         if (pluginInformationReader.hasAllPermissions())
206         {
207             pluginInfo.setPermissions(ImmutableSet.of(PluginPermission.ALL));
208         }
209         else
210         {
211             final ImmutableSet.Builder<PluginPermission> permissions = ImmutableSet.builder();
212             for (Map.Entry<String, Option<String>> permission : readPermissions.entrySet())
213             {
214                 final String permissionKey = permission.getKey();
215                 final Option<String> readInstallationMode = permission.getValue();
216                 final Option<InstallationMode> installationMode = InstallationMode.of(readInstallationMode.getOrNull());
217                 if (StringUtils.isNotBlank(readInstallationMode.getOrNull()) && !installationMode.isDefined())
218                 {
219                     log.warn("The parsed installation mode '{}' for permission '{}' didn't match any of the valid values: {}",
220                             new Object[]{readInstallationMode, permission.getKey(),
221                                     transform(copyOf(InstallationMode.values()), new Function<InstallationMode, String>()
222                                     {
223                                         @Override
224                                         public String apply(InstallationMode im)
225                                         {
226                                             return im.getKey();
227                                         }
228                                     })});
229                 }
230 
231                 permissions.add(new PluginPermission(permissionKey, installationMode));
232             }
233             pluginInfo.setPermissions(permissions.build());
234         }
235 
236         return pluginInfo;
237     }
238 
239     public String getKey()
240     {
241         return descriptorReader.getPluginKey();
242     }
243 
244     public int getPluginsVersion()
245     {
246         return descriptorReader.getPluginsVersion();
247     }
248 
249     public PluginInformation getPluginInformation()
250     {
251         return createPluginInformation();
252     }
253 
254     public boolean isSystemPlugin()
255     {
256         return descriptorReader.isSystemPlugin();
257     }
258 }