/atlassian-plugins-core/src/main/java/com/atlassian/plugin/metadata/ClasspathFilePluginMetadata.java

https://bitbucket.org/purewind/atlassian-plugins · Java · 153 lines · 105 code · 20 blank · 28 comment · 13 complexity · 1d39ea662c918d07b5d3bd306bc8363c MD5 · raw file

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