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.Resources;
13 import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
14 import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor;
15 import com.atlassian.plugin.impl.UnloadablePluginFactory;
16 import com.atlassian.plugin.util.PluginUtils;
17 import com.google.common.base.Function;
18 import com.google.common.base.Predicate;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterables;
21 import org.apache.commons.lang.StringUtils;
22 import org.dom4j.Document;
23 import org.dom4j.DocumentException;
24 import org.dom4j.Element;
25 import org.dom4j.io.SAXReader;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import java.io.InputStream;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Set;
33
34 import static com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor;
35 import static com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptorFactory.createUnrecognisedModuleDescriptor;
36 import static com.atlassian.plugin.parsers.XmlDescriptorParserUtils.removeAllNamespaces;
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.filter;
40 import static com.google.common.collect.Iterables.transform;
41
42
43
44
45
46
47
48
49
50 public class XmlDescriptorParser implements DescriptorParser
51 {
52 private static final Logger log = LoggerFactory.getLogger(XmlDescriptorParser.class);
53
54 private final Document document;
55 private final Set<Application> applications;
56
57
58
59
60
61
62
63
64 public XmlDescriptorParser(final Document source, final Set<Application> applications) throws PluginParseException
65 {
66 this.document = removeAllNamespaces(checkNotNull(source, "XML descriptor source document cannot be null"));
67 this.applications = ImmutableSet.copyOf(checkNotNull(applications));
68 }
69
70
71
72
73
74
75
76
77 public XmlDescriptorParser(final InputStream source, final Set<Application> applications) throws PluginParseException
78 {
79 this(createDocument(checkNotNull(source, "XML descriptor source cannot be null")), applications);
80 }
81
82 protected static Document createDocument(final InputStream source) throws PluginParseException
83 {
84 final SAXReader reader = new SAXReader();
85 reader.setMergeAdjacentText(true);
86 try
87 {
88 return reader.read(source);
89 }
90 catch (final DocumentException e)
91 {
92 throw new PluginParseException("Cannot parse XML plugin descriptor", e);
93 }
94 }
95
96 protected Document getDocument()
97 {
98 return document;
99 }
100
101 public Plugin configurePlugin(final ModuleDescriptorFactory moduleDescriptorFactory, final Plugin plugin) throws PluginParseException
102 {
103 final Element pluginElement = getPluginElement();
104 plugin.setName(pluginElement.attributeValue("name"));
105 plugin.setKey(getKey());
106 plugin.setPluginsVersion(getPluginsVersion());
107 plugin.setSystemPlugin(isSystemPlugin());
108
109 if (pluginElement.attributeValue("i18n-name-key") != null)
110 {
111 plugin.setI18nNameKey(pluginElement.attributeValue("i18n-name-key"));
112 }
113
114 if (plugin.getKey().indexOf(":") > 0)
115 {
116 throw new PluginParseException("Plugin keys cannot contain ':'. Key is '" + plugin.getKey() + "'");
117 }
118
119 if ("disabled".equalsIgnoreCase(pluginElement.attributeValue("state")))
120 {
121 plugin.setEnabledByDefault(false);
122 }
123
124 plugin.setResources(Resources.fromXml(pluginElement));
125
126 for (final Iterator i = pluginElement.elementIterator(); i.hasNext(); )
127 {
128 final Element element = (Element) i.next();
129
130 if ("plugin-info".equalsIgnoreCase(element.getName()))
131 {
132 plugin.setPluginInformation(createPluginInformation(element));
133 }
134 else if (!"resource".equalsIgnoreCase(element.getName()))
135 {
136 final ModuleDescriptor<?> moduleDescriptor = createModuleDescriptor(plugin, element, moduleDescriptorFactory);
137
138
139 if (moduleDescriptor == null)
140 {
141 continue;
142 }
143
144 if (plugin.getModuleDescriptor(moduleDescriptor.getKey()) != null)
145 {
146 throw new PluginParseException("Found duplicate key '" + moduleDescriptor.getKey() + "' within plugin '" + plugin.getKey() + "'");
147 }
148
149 plugin.addModuleDescriptor(moduleDescriptor);
150
151
152 if (moduleDescriptor instanceof UnloadableModuleDescriptor)
153 {
154 log.error("There were errors loading the plugin '" + plugin.getName() + "'. The plugin has been disabled.");
155 return UnloadablePluginFactory.createUnloadablePlugin(plugin);
156 }
157 }
158 }
159
160 return plugin;
161 }
162
163 private Element getPluginElement()
164 {
165 return document.getRootElement();
166 }
167
168 protected ModuleDescriptor<?> createModuleDescriptor(final Plugin plugin, final Element element, final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException
169 {
170 final String name = element.getName();
171
172
173 if (!PluginUtils.doesModuleElementApplyToApplication(element, applications, plugin.getInstallationMode()))
174 {
175 log.debug("Ignoring module descriptor for this application: " + element.attributeValue("key"));
176 return null;
177 }
178
179 ModuleDescriptor<?> moduleDescriptorDescriptor;
180
181
182 try
183 {
184 moduleDescriptorDescriptor = moduleDescriptorFactory.getModuleDescriptor(name);
185 }
186
187 catch (final Throwable e)
188 {
189 final UnrecognisedModuleDescriptor descriptor = createUnrecognisedModuleDescriptor(plugin, element, e, moduleDescriptorFactory);
190
191 log.error("There were problems loading the module '" + name + "' in plugin '" + plugin.getName() + "'. The module has been disabled.");
192 log.error(descriptor.getErrorText(), e);
193
194 return descriptor;
195 }
196
197
198 if (moduleDescriptorDescriptor == null)
199 {
200 log.info("The module '" + name + "' in plugin '" + plugin.getName() + "' is in the list of excluded module descriptors, so not enabling.");
201 return null;
202 }
203
204
205 try
206 {
207 moduleDescriptorDescriptor.init(plugin, element);
208 }
209
210 catch (final Exception e)
211 {
212 final UnloadableModuleDescriptor descriptor = createUnloadableModuleDescriptor(plugin, element, e, moduleDescriptorFactory);
213
214 log.error("There were problems loading the module '" + name + "'. The module and its plugin have been disabled.");
215 log.error(descriptor.getErrorText(), e);
216
217 return descriptor;
218 }
219
220 return moduleDescriptorDescriptor;
221 }
222
223 protected PluginInformation createPluginInformation(final Element element)
224 {
225 final PluginInformation pluginInfo = new PluginInformation();
226
227 if (element.element("description") != null)
228 {
229 pluginInfo.setDescription(element.element("description").getTextTrim());
230 if (element.element("description").attributeValue("key") != null)
231 {
232 pluginInfo.setDescriptionKey(element.element("description").attributeValue("key"));
233 }
234 }
235
236 if (element.element("version") != null)
237 {
238 pluginInfo.setVersion(element.element("version").getTextTrim());
239 }
240
241 if (element.element("vendor") != null)
242 {
243 final Element vendor = element.element("vendor");
244 pluginInfo.setVendorName(vendor.attributeValue("name"));
245 pluginInfo.setVendorUrl(vendor.attributeValue("url"));
246 }
247
248
249 for (Element param : getElements(element, "param"))
250 {
251
252 if (param.attribute("name") != null)
253 {
254 pluginInfo.addParameter(param.attribute("name").getData().toString(), param.getText());
255 }
256 }
257
258 if (element.element("application-version") != null)
259 {
260 final Element ver = element.element("application-version");
261 if (ver.attribute("max") != null)
262 {
263 pluginInfo.setMaxVersion(Float.parseFloat(ver.attributeValue("max")));
264 }
265 if (ver.attribute("min") != null)
266 {
267 pluginInfo.setMinVersion(Float.parseFloat(ver.attributeValue("min")));
268 }
269 }
270
271 if (element.element("java-version") != null)
272 {
273 pluginInfo.setMinJavaVersion(Float.valueOf(element.element("java-version").attributeValue("min")));
274 }
275
276 if (element.element("permissions") != null)
277 {
278 ImmutableSet.Builder<PluginPermission> permissions = ImmutableSet.builder();
279 for (Element permission : filter(getElements(element.element("permissions"), "permission"), new ElementWithForApplicationsPredicate(applications)))
280 {
281 final String trimmedPermission = permission.getTextTrim();
282 if (StringUtils.isNotBlank(trimmedPermission))
283 {
284 final String parsedInstallationMode = permission.attributeValue("installation-mode");
285 final Option<InstallationMode> installationMode = InstallationMode.of(parsedInstallationMode);
286 if (StringUtils.isNotBlank(parsedInstallationMode) && !installationMode.isDefined())
287 {
288 log.warn("The parsed installation mode '{}' for permission '{}' didn't match any of the valid values: {}",
289 new Object[]{parsedInstallationMode, trimmedPermission,
290 transform(copyOf(InstallationMode.values()), new Function<InstallationMode, String>()
291 {
292 @Override
293 public String apply(InstallationMode im)
294 {
295 return im.getKey();
296 }
297 })});
298 }
299
300 permissions.add(new PluginPermission(trimmedPermission, installationMode));
301 }
302 else
303 {
304 log.warn("Plugin {} has blank permission.", getKey());
305 }
306 }
307 pluginInfo.setPermissions(permissions.build());
308 }
309 else if (getPluginsVersion() < Plugin.VERSION_3)
310 {
311 pluginInfo.setPermissions(ImmutableSet.of(PluginPermission.ALL));
312 }
313
314 return pluginInfo;
315 }
316
317 public String getKey()
318 {
319 return getPluginElement().attributeValue("key");
320 }
321
322 public int getPluginsVersion()
323 {
324 String val = getPluginElement().attributeValue("pluginsVersion");
325 if (val == null)
326 {
327 val = getPluginElement().attributeValue("plugins-version");
328 }
329 if (val != null)
330 {
331 try
332 {
333 return Integer.parseInt(val);
334 }
335 catch (final NumberFormatException e)
336 {
337 throw new RuntimeException("Could not parse pluginsVersion: " + e.getMessage(), e);
338 }
339 }
340 else
341 {
342 return 1;
343 }
344 }
345
346 public PluginInformation getPluginInformation()
347 {
348 return createPluginInformation(getDocument().getRootElement().element("plugin-info"));
349 }
350
351 public boolean isSystemPlugin()
352 {
353 return Boolean.valueOf(getPluginElement().attributeValue("system"));
354 }
355
356 @SuppressWarnings("unchecked")
357 private List<Element> getElements(Element element, String name)
358 {
359 return element.elements(name);
360 }
361
362 private static final class ApplicationWithNamePredicate implements Predicate<Application>
363 {
364 private final String name;
365
366 public ApplicationWithNamePredicate(String name)
367 {
368 this.name = name;
369 }
370
371 @Override
372 public boolean apply(Application app)
373 {
374 return app.getKey().equals(name);
375 }
376 }
377
378 private static final class ElementWithForApplicationsPredicate implements Predicate<Element>
379 {
380 private final Set<Application> applications;
381
382 private ElementWithForApplicationsPredicate(Set<Application> applications)
383 {
384 this.applications = checkNotNull(applications);
385 }
386
387 @Override
388 public boolean apply(final Element el)
389 {
390 final String appName = el.attributeValue("application");
391 return appName == null || Iterables.any(applications, new ApplicationWithNamePredicate(appName));
392 }
393 }
394 }