View Javadoc

1   package com.atlassian.plugin.metadata;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import com.google.common.collect.ImmutableSet;
6   import org.apache.commons.io.IOUtils;
7   import org.apache.commons.lang.StringUtils;
8   
9   import java.io.IOException;
10  import java.io.InputStream;
11  import java.net.URL;
12  import java.util.ArrayList;
13  import java.util.Collection;
14  import java.util.Enumeration;
15  import java.util.List;
16  import java.util.Set;
17  
18  import static org.apache.commons.io.IOUtils.readLines;
19  
20  /**
21   * Looks on the classpath for three files named: <ul> <li>application-provided-plugins.txt - used to list the plugin
22   * keys of all plugins that are provided by the host application</li> <li>application-required-plugins.txt - used to
23   * list the plugin keys that are considered required for the application to function correctly</li>
24   * <li>application-required-modules.txt - used to list the module keys that are considered required for the application
25   * to function correctly</li> </ul> Note that all files in that package space with those names will be included.
26   * <p/>
27   * All files contents will be used to inform this implementation of plugin keys. This will read the contents all
28   * instances of those files into the structures of this class.
29   * <p/>
30   * The values will determine the plugin metadata for this implementation.
31   *
32   * @since 2.6
33   */
34  public class ClasspathFilePluginMetadata implements PluginMetadata, RequiredPluginProvider
35  {
36      final static String APPLICATION_PROVIDED_PLUGINS_FILENAME = "application-provided-plugins.txt";
37      final static String APPLICATION_REQUIRED_PLUGINS_FILENAME = "application-required-plugins.txt";
38      final static String APPLICATION_REQUIRED_MODULES_FILENAME = "application-required-modules.txt";
39  
40      private final Set<String> providedPluginKeys;
41      private final Set<String> requiredPluginKeys;
42      private final Set<String> requiredModuleKeys;
43      private final ClassLoader classLoader;
44  
45      public ClasspathFilePluginMetadata()
46      {
47          this(ClasspathFilePluginMetadata.class.getClassLoader());
48      }
49  
50      ClasspathFilePluginMetadata(ClassLoader classLoader)
51      {
52          this.classLoader = classLoader;
53          this.providedPluginKeys = getStringsFromFile(APPLICATION_PROVIDED_PLUGINS_FILENAME);
54          this.requiredPluginKeys = getStringsFromFile(APPLICATION_REQUIRED_PLUGINS_FILENAME);
55          this.requiredModuleKeys = getStringsFromFile(APPLICATION_REQUIRED_MODULES_FILENAME);
56      }
57  
58      public boolean applicationProvided(final Plugin plugin)
59      {
60          return providedPluginKeys.contains(plugin.getKey());
61      }
62  
63      public boolean required(final Plugin plugin)
64      {
65          return requiredPluginKeys.contains(plugin.getKey());
66      }
67  
68      public boolean required(final ModuleDescriptor<?> module)
69      {
70          return requiredModuleKeys.contains(module.getCompleteKey());
71      }
72  
73      public Set<String> getRequiredPluginKeys()
74      {
75          return requiredPluginKeys;
76      }
77  
78      public Set<String> getRequiredModuleKeys()
79      {
80          return requiredModuleKeys;
81      }
82  
83      private Set<String> getStringsFromFile(final String fileName)
84      {
85          final ImmutableSet.Builder<String> stringsFromFiles = ImmutableSet.builder();
86  
87          final Collection<InputStream> fileInputStreams = getInputStreamsForFilename(fileName);
88          try
89          {
90              for (InputStream fileInputStream : fileInputStreams)
91              {
92                  if (fileInputStream != null)
93                  {
94                      try
95                      {
96                          @SuppressWarnings ("unchecked")
97                          final List<String> lines = readLines(fileInputStream);
98  
99                          // Make sure that we trim the strings that we read from the file and filter out comments and blank lines
100                         // NOTE: You could use a filter and a transformation but then you need either a private class
101                         // or a single enum which causes WAY TOO MUCH debate.
102                         for (String line : lines)
103                         {
104                             final String processedLine = processedLine(line);
105                             if (processedLine != null)
106                             {
107                                 stringsFromFiles.add(processedLine);
108                             }
109                         }
110                     }
111                     catch (final IOException e)
112                     {
113                         throw new RuntimeException(e);
114                     }
115                 }
116             }
117         }
118         finally
119         {
120             for (InputStream fileInputStream : fileInputStreams)
121             {
122                 IOUtils.closeQuietly(fileInputStream);
123             }
124         }
125         return stringsFromFiles.build();
126     }
127 
128     /**
129      * Checks the input to see if it meets the rules we want to enforce about valid lines. If it does not meet the
130      * criteria then null is returned, otherwise a trimmed version of the passed in string is returned.
131      *
132      * @param rawLine the string of data we are processing
133      * @return if rawLine does not meet the criteria then null is returned, otherwise a trimmed version of rawLine is
134      *         returned.
135      */
136     private String processedLine(String rawLine)
137     {
138         if (rawLine == null)
139         {
140             return null;
141         }
142 
143         final String trimmedLine = rawLine.trim();
144         // Lets not include blank lines
145         if (StringUtils.isBlank(trimmedLine))
146         {
147             return null;
148         }
149 
150         // Lets not include comments
151         if (trimmedLine.startsWith("#"))
152         {
153             return null;
154         }
155         return trimmedLine;
156     }
157 
158     Collection<InputStream> getInputStreamsForFilename(final String fileName)
159     {
160         final Collection<InputStream> inputStreams = new ArrayList<InputStream>();
161         final Class<ClasspathFilePluginMetadata> clazz = ClasspathFilePluginMetadata.class;
162         final String resourceName = clazz.getPackage().getName().replace(".", "/") + "/" + fileName;
163         try
164         {
165             final Enumeration<URL> urlEnumeration = classLoader.getResources(resourceName);
166             while (urlEnumeration.hasMoreElements())
167             {
168                 inputStreams.add(urlEnumeration.nextElement().openStream());
169             }
170         }
171         catch (final IOException e)
172         {
173             // Close what we had opened before, one bad apple ruins the batch
174             for (InputStream inputStream : inputStreams)
175             {
176                 IOUtils.closeQuietly(inputStream);
177             }
178             throw new RuntimeException(e);
179         }
180         return inputStreams;
181     }
182 
183 }