View Javadoc
1   package com.atlassian.plugin.parsers;
2   
3   import com.atlassian.plugin.Application;
4   import com.atlassian.plugin.InstallationMode;
5   import com.atlassian.plugin.Plugin;
6   import com.google.common.collect.Maps;
7   import com.google.common.collect.Sets;
8   import org.apache.commons.lang3.StringUtils;
9   import org.dom4j.Attribute;
10  import org.dom4j.Element;
11  
12  import java.util.Collections;
13  import java.util.List;
14  import java.util.Map;
15  import java.util.Optional;
16  import java.util.Set;
17  import java.util.function.Function;
18  import java.util.function.Predicate;
19  import java.util.stream.Collectors;
20  import java.util.stream.Stream;
21  
22  import static com.atlassian.plugin.parsers.PluginDescriptorReader.elements;
23  import static com.google.common.base.Preconditions.checkArgument;
24  import static com.google.common.base.Preconditions.checkNotNull;
25  import static com.google.common.collect.ImmutableSet.copyOf;
26  import static java.util.Optional.empty;
27  import static java.util.Optional.ofNullable;
28  
29  /**
30   * Reads plugin information from a plugin descriptor.
31   *
32   * @see com.atlassian.plugin.parsers.PluginDescriptorReader#getPluginInformationReader()
33   * @since 3.0.0
34   */
35  public final class PluginInformationReader {
36      static final String PLUGIN_INFO = "plugin-info";
37      /**
38       * The default scan-folder
39       */
40      static final String DEFAULT_SCAN_FOLDER = "META-INF/atlassian";
41  
42      private final Element pluginInfo;
43      private final Set<Application> applications;
44      private final int pluginsVersion;
45  
46      PluginInformationReader(Element pluginInfo, Set<Application> applications, int pluginsVersion) {
47          this.pluginsVersion = pluginsVersion;
48          this.pluginInfo = pluginInfo;
49          this.applications = copyOf(checkNotNull(applications));
50      }
51  
52      public Optional<String> getDescription() {
53          return getDescriptionElement().map(Element::getTextTrim);
54      }
55  
56      public Optional<String> getDescriptionKey() {
57          return getDescriptionElement().map(description -> description.attributeValue("key"));
58      }
59  
60      public Optional<String> getVersion() {
61          return childElement("version").map(Element::getTextTrim);
62      }
63  
64      private Optional<Element> childElement(final String name) {
65          return pluginInfo == null ? empty() : ofNullable(pluginInfo.element(name));
66      }
67  
68      private Stream<Element> childElements(final String name) {
69          return pluginInfo == null ? Stream.empty() : pluginInfo.elements(name).stream();
70      }
71  
72      public Optional<String> getVendorName() {
73          return getVendorElement().map(vendor -> vendor.attributeValue("name"));
74      }
75  
76      public Optional<String> getVendorUrl() {
77          return getVendorElement().map(vendor -> vendor.attributeValue("url"));
78      }
79  
80      /**
81       * @deprecated in 5.0 for removal in 6.0 when support for scopes is removed
82       */
83      @Deprecated
84      public Optional<String> getScopeKey() {
85          final Optional<String> scopeKey = getScopeElement().map(el -> el.attributeValue("key"));
86  
87          checkArgument(!scopeKey.map(String::isEmpty).orElse(false), "Value of scope key can't be blank");
88          return scopeKey;
89      }
90  
91      public Map<String, String> getParameters() {
92          return Collections.unmodifiableMap(getParamElements()
93                  .collect(Collectors.toMap(param -> param.attribute("name").getData().toString(), Element::getText)));
94      }
95  
96      public Optional<Float> getMinVersion() {
97          return getApplicationVersionElement().flatMap(new GetAttributeFunction("min")).map(new ParseAttributeValueAsFloatFunction());
98      }
99  
100     public Optional<Float> getMaxVersion() {
101         return getApplicationVersionElement().flatMap(new GetAttributeFunction("max")).map(new ParseAttributeValueAsFloatFunction());
102     }
103 
104     public Optional<Float> getMinJavaVersion() {
105         return childElement("java-version").flatMap(new GetAttributeFunction("min")).map(new ParseAttributeValueAsFloatFunction());
106     }
107 
108     public Map<String, Optional<String>> getPermissions() {
109         return Collections.unmodifiableMap(getPermissionElements()
110                 .collect(Collectors.toMap(Element::getTextTrim,
111                         perm -> ofNullable(perm.attributeValue("installation-mode")))));
112     }
113 
114     public boolean hasAllPermissions() {
115         return getPermissions().isEmpty() && pluginsVersion < Plugin.VERSION_3;
116     }
117 
118     public Set<String> getPermissions(final InstallationMode installationMode) {
119         return copyOf(Maps.filterValues(getPermissions(), 
120                 permInstallMode -> permInstallMode.flatMap(InstallationMode::of)
121                         .map(installMode -> installMode.equals(installationMode))
122                         .orElse(true)).keySet());
123     }
124 
125     public Optional<String> getStartup() {
126         return childElement("startup").map(Element::getTextTrim);
127     }
128 
129     public Iterable<String> getModuleScanFolders() {
130         final Set<String> scanFolders = Sets.newLinkedHashSet();
131         return childElement("scan-modules")
132                 .map((Function<Element, Iterable<Element>>) scanModules -> {
133                     List<Element> elements = elements(scanModules, "folder");
134                     if (elements.isEmpty()) {
135                         scanFolders.add(DEFAULT_SCAN_FOLDER);
136                     }
137                     return elements;
138                 })
139                 .map((Function<Iterable<Element>, Iterable<String>>) folders -> {
140                     for (Element folder : folders) {
141                         scanFolders.add(folder.getTextTrim());
142                     }
143                     return scanFolders;
144                 })
145                 .orElseGet(Collections::emptyList);
146     }
147 
148     private Stream<Element> getPermissionElements() {
149         return streamOptional(childElement("permissions"))
150                 .flatMap(permissions -> elements(permissions, "permission").stream())
151                 .filter(new ElementWithForApplicationsPredicate(applications))
152                 .filter(element -> StringUtils.isNotBlank(element.getTextTrim()));
153     }
154 
155     private Optional<Element> getApplicationVersionElement() {
156         return childElement("application-version");
157     }
158 
159     private Stream<Element> getParamElements() {
160         return childElements("param").filter(param -> param.attribute("name") != null);
161     }
162 
163     private Optional<Element> getVendorElement() {
164         return childElement("vendor");
165     }
166 
167     private Optional<Element> getScopeElement() {
168         return childElement("scope");
169     }
170 
171     private Optional<Element> getDescriptionElement() {
172         return childElement("description");
173     }
174 
175     private static <T> Stream<T> streamOptional(Optional<T> value) {
176         return value.map(Stream::of).orElseGet(Stream::empty);
177     }
178 
179     private static final class ParseAttributeValueAsFloatFunction implements Function<Attribute, Float> {
180         @Override
181         public Float apply(Attribute attr) {
182             return Float.parseFloat(attr.getValue());
183         }
184     }
185 
186     private static final class GetAttributeFunction implements Function<Element, Optional<Attribute>> {
187         private final String name;
188 
189         private GetAttributeFunction(String name) {
190             this.name = name;
191         }
192 
193         @Override
194         public Optional<Attribute> apply(Element applicationVersion) {
195             return ofNullable(applicationVersion.attribute(name));
196         }
197     }
198 
199     private static final class ElementWithForApplicationsPredicate implements Predicate<Element> {
200         private final Set<Application> applications;
201 
202         private ElementWithForApplicationsPredicate(Set<Application> applications) {
203             this.applications = checkNotNull(applications);
204         }
205 
206         @Override
207         public boolean test(final Element el) {
208             final String appName = el.attributeValue("application");
209             return appName == null || applications.stream().anyMatch(app -> app != null && appName.equals(app.getKey()));
210         }
211     }
212 }