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.descriptors.UnloadableModuleDescriptor;
12 import com.atlassian.plugin.impl.UnloadablePluginFactory;
13 import com.atlassian.security.xml.SecureXmlParserFactory;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
16 import org.apache.commons.lang3.StringUtils;
17 import org.dom4j.Document;
18 import org.dom4j.Element;
19 import org.dom4j.Node;
20 import org.dom4j.io.DOMReader;
21 import org.dom4j.io.SAXReader;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24 import org.xml.sax.InputSource;
25 import org.xml.sax.SAXException;
26
27 import javax.xml.parsers.DocumentBuilder;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.Optional;
33 import java.util.Set;
34
35 import static com.atlassian.plugin.descriptors.UnloadableModuleDescriptorFactory.createUnloadableModuleDescriptor;
36 import static com.atlassian.plugin.parsers.XmlDescriptorParserUtils.newModuleDescriptor;
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
43
44
45
46
47
48
49 public class XmlDescriptorParser implements DescriptorParser {
50 private static final Logger log = LoggerFactory.getLogger(XmlDescriptorParser.class);
51
52 private final PluginDescriptorReader descriptorReader;
53
54
55
56
57
58
59
60
61
62 public XmlDescriptorParser(final Document source, final Set<Application> applications) throws PluginParseException {
63 this.descriptorReader = new PluginDescriptorReader(checkNotNull(source, "XML descriptor source document cannot be null"), checkNotNull(applications));
64 }
65
66
67
68
69
70
71
72
73
74 public XmlDescriptorParser(final InputStream source, final Set<Application> applications) throws PluginParseException {
75 this(createDocument(checkNotNull(source, "XML descriptor source cannot be null")), applications);
76 }
77
78
79
80
81
82
83
84
85
86
87 public XmlDescriptorParser(final InputStream source, final Iterable<InputStream> supplementalSources, final Set<Application> applications) throws PluginParseException {
88 checkNotNull(source, "XML descriptor source cannot be null");
89 checkNotNull(supplementalSources, "Supplemental XML descriptors cannot be null");
90 Document mainDescriptor = createDocument(source);
91 final Iterable<Document> supplementalDocs = Iterables.transform(supplementalSources, XmlDescriptorParser::createDocument);
92 mainDescriptor = mergeDocuments(mainDescriptor, supplementalDocs);
93 descriptorReader = new PluginDescriptorReader(mainDescriptor, checkNotNull(applications));
94 }
95
96 protected static Document createDocument(final InputStream source) throws PluginParseException {
97
98 final DocumentBuilder documentBuilder = SecureXmlParserFactory.newNamespaceAwareDocumentBuilder();
99 try {
100 org.w3c.dom.Document document = documentBuilder.parse(new InputSource(source));
101 document.normalize();
102 return (new DOMReader()).read(document);
103 } catch (final IOException | SAXException e) {
104 throw new PluginParseException("Cannot parse XML plugin descriptor", e);
105 }
106 }
107
108 protected static Document mergeDocuments(final Document mainDocument, final Iterable<Document> supplementalDocuments) {
109 Element mainRootElement = mainDocument.getRootElement();
110 for (Document supplementalDocument : supplementalDocuments) {
111 Element supplementaryRoot = supplementalDocument.getRootElement();
112 for (Iterator<Node> iter = supplementaryRoot.content().iterator(); iter.hasNext(); ) {
113 Node node = iter.next();
114 iter.remove();
115 mainRootElement.add(node);
116 }
117 }
118 return mainDocument;
119 }
120
121 protected Document getDocument() {
122 return descriptorReader.getDescriptor();
123 }
124
125 public Plugin configurePlugin(final ModuleDescriptorFactory moduleDescriptorFactory, final Plugin plugin) throws PluginParseException {
126 plugin.setName(descriptorReader.getPluginName());
127 plugin.setKey(getKey());
128 plugin.setPluginsVersion(getPluginsVersion());
129 plugin.setSystemPlugin(isSystemPlugin());
130 plugin.setI18nNameKey(descriptorReader.getI18nPluginNameKey().orElseGet(plugin::getI18nNameKey));
131
132 if (plugin.getKey().indexOf(":") > 0) {
133 throw new PluginParseException("Plugin keys cannot contain ':'. Key is '" + plugin.getKey() + "'");
134 }
135
136 plugin.setEnabledByDefault(descriptorReader.isEnabledByDefault());
137 plugin.setResources(descriptorReader.getResources());
138 plugin.setPluginInformation(createPluginInformation());
139
140 for (Element module : descriptorReader.getModules(plugin.getInstallationMode())) {
141 final ModuleDescriptor<?> moduleDescriptor = createModuleDescriptor(plugin, module, moduleDescriptorFactory);
142
143
144 if (moduleDescriptor == null) {
145 continue;
146 }
147
148 if (plugin.getModuleDescriptor(moduleDescriptor.getKey()) != null) {
149 throw new PluginParseException("Found duplicate key '" + moduleDescriptor.getKey() + "' within plugin '" + plugin.getKey() + "'");
150 }
151
152 plugin.addModuleDescriptor(moduleDescriptor);
153
154
155 if (moduleDescriptor instanceof UnloadableModuleDescriptor) {
156 log.error("There were errors loading the plugin '" + plugin.getName() + "'. The plugin has been disabled.");
157 return UnloadablePluginFactory.createUnloadablePlugin(plugin);
158 }
159 }
160 return plugin;
161 }
162
163 @Override
164 public ModuleDescriptor<?> addModule(final ModuleDescriptorFactory moduleDescriptorFactory, final Plugin plugin, final Element module) {
165 return XmlDescriptorParserUtils.addModule(moduleDescriptorFactory, plugin, module);
166 }
167
168 protected ModuleDescriptor<?> createModuleDescriptor(final Plugin plugin, final Element element, final ModuleDescriptorFactory moduleDescriptorFactory) throws PluginParseException {
169 final String name = element.getName();
170
171 final ModuleDescriptor<?> moduleDescriptor = newModuleDescriptor(plugin, element, moduleDescriptorFactory);
172
173
174 if (moduleDescriptor == null) {
175 log.info("The module '{}' in plugin '{}' is in the list of excluded module descriptors, so not enabling.", name, plugin.getName());
176 return null;
177 }
178
179
180 try {
181 moduleDescriptor.init(plugin, element);
182 }
183
184 catch (final Exception e) {
185 final UnloadableModuleDescriptor descriptor = createUnloadableModuleDescriptor(plugin, element, e, moduleDescriptorFactory);
186
187 log.error("There were problems loading the module '{}'. The module and its plugin have been disabled.", name);
188 log.error(descriptor.getErrorText(), e);
189
190 return descriptor;
191 }
192
193 return moduleDescriptor;
194 }
195
196 protected PluginInformation createPluginInformation() {
197 final PluginInformationReader pluginInformationReader = descriptorReader.getPluginInformationReader();
198
199 final PluginInformation pluginInfo = new PluginInformation();
200 pluginInfo.setDescription(pluginInformationReader.getDescription().orElseGet(pluginInfo::getDescription));
201 pluginInfo.setDescriptionKey(pluginInformationReader.getDescriptionKey().orElseGet(pluginInfo::getDescriptionKey));
202 pluginInfo.setVersion(pluginInformationReader.getVersion().orElseGet(pluginInfo::getVersion));
203 pluginInfo.setVendorName(pluginInformationReader.getVendorName().orElseGet(pluginInfo::getVendorName));
204 pluginInfo.setVendorUrl(pluginInformationReader.getVendorUrl().orElseGet(pluginInfo::getVendorUrl));
205 pluginInfo.setScopeKey(pluginInformationReader.getScopeKey());
206
207 for (Map.Entry<String, String> param : pluginInformationReader.getParameters().entrySet()) {
208 pluginInfo.addParameter(param.getKey(), param.getValue());
209 }
210 pluginInfo.setMinJavaVersion(pluginInformationReader.getMinJavaVersion().orElseGet(pluginInfo::getMinJavaVersion));
211 pluginInfo.setStartup(pluginInformationReader.getStartup().orElseGet(pluginInfo::getStartup));
212 pluginInfo.setModuleScanFolders(pluginInformationReader.getModuleScanFolders());
213
214 final Map<String, Optional<String>> readPermissions = pluginInformationReader.getPermissions();
215 if (pluginInformationReader.hasAllPermissions()) {
216 pluginInfo.setPermissions(ImmutableSet.of(PluginPermission.ALL));
217 } else {
218 final ImmutableSet.Builder<PluginPermission> permissions = ImmutableSet.builder();
219 for (Map.Entry<String, Optional<String>> permission : readPermissions.entrySet()) {
220 final String permissionKey = permission.getKey();
221 final Optional<String> readInstallationMode = permission.getValue();
222 final Optional<InstallationMode> installationMode = readInstallationMode.flatMap(InstallationMode::of);
223 if (StringUtils.isNotBlank(readInstallationMode.orElse(null)) && !installationMode.isPresent()) {
224 log.warn("The parsed installation mode '{}' for permission '{}' didn't match any of the valid values: {}",
225 readInstallationMode, permission.getKey(),
226 transform(copyOf(InstallationMode.values()), InstallationMode::getKey));
227 }
228
229 permissions.add(new PluginPermission(permissionKey, installationMode.orElse(null)));
230 }
231 pluginInfo.setPermissions(permissions.build());
232 }
233
234 return pluginInfo;
235 }
236
237 public String getKey() {
238 return descriptorReader.getPluginKey();
239 }
240
241 public int getPluginsVersion() {
242 return descriptorReader.getPluginsVersion();
243 }
244
245 public PluginInformation getPluginInformation() {
246 return createPluginInformation();
247 }
248
249 public boolean isSystemPlugin() {
250 return descriptorReader.isSystemPlugin();
251 }
252 }