View Javadoc

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