1 package com.atlassian.plugin.parsers;
2
3 import com.atlassian.plugin.Application;
4 import com.atlassian.plugin.InstallationMode;
5 import com.atlassian.plugin.ModuleDescriptor;
6 import com.atlassian.plugin.ModuleDescriptorFactory;
7 import com.atlassian.plugin.Plugin;
8 import com.atlassian.plugin.PluginInformation;
9 import com.atlassian.plugin.PluginParseException;
10 import com.atlassian.plugin.PluginPermission;
11 import com.atlassian.plugin.Resources;
12 import com.atlassian.plugin.descriptors.UnloadableModuleDescriptor;
13 import com.atlassian.plugin.descriptors.UnrecognisedModuleDescriptor;
14 import com.atlassian.plugin.impl.UnloadablePluginFactory;
15 import com.atlassian.plugin.util.PluginUtils;
16
17 import com.google.common.base.Predicate;
18 import com.google.common.collect.ImmutableSet;
19 import com.google.common.collect.Iterables;
20 import org.apache.commons.lang.Validate;
21 import org.dom4j.Document;
22 import org.dom4j.DocumentException;
23 import org.dom4j.Element;
24 import org.dom4j.io.SAXReader;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import java.io.InputStream;
29 import java.util.Collections;
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.google.common.base.Preconditions.checkNotNull;
37 import static com.google.common.collect.Iterables.filter;
38
39
40
41
42
43
44
45
46
47 public class XmlDescriptorParser implements DescriptorParser
48 {
49 private static final Logger log = LoggerFactory.getLogger(XmlDescriptorParser.class);
50
51 private final Document document;
52 private final Set<Application> applications;
53
54
55
56
57
58
59
60
61 public XmlDescriptorParser(final Document source, final Application... applications) throws PluginParseException
62 {
63 Validate.notNull(source, "XML descriptor source document cannot be null");
64 this.document = source;
65 this.applications = applications == null ? Collections.<Application>emptySet() : ImmutableSet.copyOf(applications);
66 }
67
68
69
70
71
72
73
74
75 public XmlDescriptorParser(final InputStream source, final Application... applications) throws PluginParseException
76 {
77 Validate.notNull(source, "XML descriptor source cannot be null");
78 this.document = createDocument(source);
79 this.applications = applications == null ? Collections.<Application>emptySet() : ImmutableSet.copyOf(applications);
80 }
81
82 protected 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))
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 permissions.add(new PluginPermission(permission.getTextTrim(), InstallationMode.of(permission.attributeValue("installation-mode"))));
282 }
283 pluginInfo.setPermissions(permissions.build());
284 }
285 else if (getPluginsVersion() < 3)
286 {
287 pluginInfo.setPermissions(ImmutableSet.of(PluginPermission.ALL));
288 }
289
290 return pluginInfo;
291 }
292
293 public String getKey()
294 {
295 return getPluginElement().attributeValue("key");
296 }
297
298 public int getPluginsVersion()
299 {
300 String val = getPluginElement().attributeValue("pluginsVersion");
301 if (val == null)
302 {
303 val = getPluginElement().attributeValue("plugins-version");
304 }
305 if (val != null)
306 {
307 try
308 {
309 return Integer.parseInt(val);
310 }
311 catch (final NumberFormatException e)
312 {
313 throw new RuntimeException("Could not parse pluginsVersion: " + e.getMessage(), e);
314 }
315 }
316 else
317 {
318 return 1;
319 }
320 }
321
322 public PluginInformation getPluginInformation()
323 {
324 return createPluginInformation(getDocument().getRootElement().element("plugin-info"));
325 }
326
327 public boolean isSystemPlugin()
328 {
329 return "true".equalsIgnoreCase(getPluginElement().attributeValue("system"));
330 }
331
332 @SuppressWarnings("unchecked")
333 private List<Element> getElements(Element element, String name)
334 {
335 return element.elements(name);
336 }
337
338 private static final class ApplicationWithNamePredicate implements Predicate<Application>
339 {
340 private final String name;
341
342 public ApplicationWithNamePredicate(String name)
343 {
344 this.name = name;
345 }
346
347 @Override
348 public boolean apply(Application app)
349 {
350 return app.getKey().equals(name);
351 }
352 }
353
354 private static final class ElementWithForApplicationsPredicate implements Predicate<Element>
355 {
356 private final Set<Application> applications;
357
358 private ElementWithForApplicationsPredicate(Set<Application> applications)
359 {
360 this.applications = checkNotNull(applications);
361 }
362
363 @Override
364 public boolean apply(final Element el)
365 {
366 final String appName = el.attributeValue("application");
367 return appName == null || Iterables.any(applications, new ApplicationWithNamePredicate(appName));
368 }
369 }
370 }