View Javadoc
1   package com.atlassian.plugin.util;
2   
3   import com.atlassian.plugin.Application;
4   import com.atlassian.plugin.InstallationMode;
5   import com.google.common.base.MoreObjects;
6   import com.google.common.base.Predicate;
7   import com.google.common.collect.ImmutableList;
8   import com.google.common.collect.Iterables;
9   import org.apache.commons.lang3.StringUtils;
10  import org.dom4j.Element;
11  
12  import java.util.List;
13  import java.util.Optional;
14  import java.util.Set;
15  
16  import static com.google.common.base.Preconditions.checkNotNull;
17  import static com.google.common.base.Preconditions.checkState;
18  import static com.google.common.collect.Iterables.filter;
19  import static com.google.common.collect.Iterables.transform;
20  import static com.google.common.collect.Lists.newArrayList;
21  import static java.util.Objects.requireNonNull;
22  
23  /**
24   * Represents a list of {@link ModuleRestrict}. This represents the restricts
25   * as used in the plugin descriptor:
26   * <code><br>
27   * &nbsp;&nbsp;&lt;module key="module-key"><br>
28   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;restrict ...>...&lt;/restrict><br>
29   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;restrict ...>...&lt;/restrict><br>
30   * &nbsp;&nbsp;&lt;/module>
31   * </code>
32   *
33   * @since 3.0
34   */
35  final class ModuleRestricts {
36      final Iterable<ModuleRestrict> restricts;
37  
38      private ModuleRestricts() {
39          this(ImmutableList.of());
40      }
41  
42      private ModuleRestricts(Iterable<ModuleRestrict> restricts) {
43          this.restricts = ImmutableList.copyOf(restricts);
44      }
45  
46      static ModuleRestricts parse(Element moduleElement) {
47          final String applicationKeys = moduleElement.attributeValue("application");
48          if (applicationKeys != null) {
49              return parseApplicationsFromAttribute(applicationKeys);
50          } else if (!moduleElement.elements("restrict").isEmpty()) {
51              @SuppressWarnings("unchecked")
52              final List<Element> restrict = moduleElement.elements("restrict");
53              return parseApplicationsFromRestrictElements(restrict);
54          } else {
55              return new ModuleRestricts();
56          }
57      }
58  
59      private static ModuleRestricts parseApplicationsFromRestrictElements(List<Element> restrictElements) {
60          return new ModuleRestricts(Iterables.transform(restrictElements, restrictElement -> {
61              final String application = restrictElement.attributeValue("application");
62              checkState(application != null, "No application defined for 'restrict' element.");
63              return new ModuleRestrict(application, parseInstallationMode(restrictElement).orElse(null),
64                      parseVersionRange(restrictElement));
65          }));
66      }
67  
68      private static Optional<InstallationMode> parseInstallationMode(Element restrictElement) {
69          return InstallationMode.of(restrictElement.attributeValue("mode"));
70      }
71  
72      private static VersionRange parseVersionRange(Element restrictElement) {
73          final String version = restrictElement.attributeValue("version");
74          if (version != null) {
75              return VersionRange.parse(version);
76          } else {
77              final List<Element> versionElements = restrictElement.elements("version");
78              if (!versionElements.isEmpty()) {
79                  VersionRange range = VersionRange.empty();
80                  for (Element versionElement : versionElements) {
81                      range = range.or(VersionRange.parse(versionElement.getText()));
82                  }
83                  return range;
84              } else {
85                  return VersionRange.all();
86              }
87          }
88      }
89  
90      private static ModuleRestricts parseApplicationsFromAttribute(String applicationKeys) {
91          final String[] keys = applicationKeys.split("\\s*,[,\\s]*");
92          final Iterable<ModuleRestrict> restricts = transform(filter(newArrayList(keys),
93                  new IsNotBlankPredicate()), ModuleRestrict::new);
94  
95          return new ModuleRestricts(restricts);
96      }
97  
98      public boolean isValidFor(Set<Application> applications, InstallationMode mode) {
99          if (Iterables.isEmpty(restricts)) {
100             return true;
101         }
102 
103         for (Application application : applications) {
104             if (Iterables.any(restricts, new RestrictMatchesApplication(application, mode))) {
105                 return true;
106             }
107         }
108         return false;
109     }
110 
111     @Override
112     public String toString() {
113         return restricts.toString();
114     }
115 
116     static final class ModuleRestrict {
117         final String application;
118         final InstallationMode mode;
119         final VersionRange version;
120 
121         ModuleRestrict(String application) {
122             this(application, null);
123         }
124 
125         ModuleRestrict(String application, InstallationMode mode) {
126             this(application, mode, VersionRange.all());
127         }
128 
129         ModuleRestrict(String application, InstallationMode mode, VersionRange version) {
130             this.application = requireNonNull(application, "application");
131             this.mode = mode;
132             this.version = requireNonNull(version);
133         }
134 
135         @Override
136         public String toString() {
137             return MoreObjects.toStringHelper("restrict")
138                     .add("application", application)
139                     .add("mode", mode)
140                     .add("range", version)
141                     .toString();
142         }
143     }
144 
145     private static final class IsNotBlankPredicate implements Predicate<String> {
146         @Override
147         public boolean apply(String input) {
148             return StringUtils.isNotBlank(input);
149         }
150     }
151 
152     private static final class RestrictMatchesApplication implements Predicate<ModuleRestrict> {
153         private final Application app;
154         private final InstallationMode installationMode;
155 
156         public RestrictMatchesApplication(Application app, InstallationMode installationMode) {
157             this.app = checkNotNull(app);
158             this.installationMode = checkNotNull(installationMode);
159         }
160 
161         @Override
162         public boolean apply(ModuleRestrict restrict) {
163             return restrict.application.equals(app.getKey())
164                     && isInstallModeValid(restrict.mode)
165                     && restrict.version.isInRange(app.getVersion());
166         }
167 
168         private boolean isInstallModeValid(InstallationMode mode) {
169             return mode == null || mode.equals(installationMode);
170         }
171     }
172 }