You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PluginMetaDataValidator.java 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /*
  2. * Copyright (c) 2006-2011 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.plugins;
  23. import com.dmdirc.updater.Version;
  24. import com.dmdirc.util.collections.MapList;
  25. import java.io.File;
  26. import java.util.ArrayList;
  27. import java.util.Collection;
  28. import java.util.Collections;
  29. import java.util.Map;
  30. /**
  31. * Validates that the values specified in a plugin's meta data are correct and
  32. * all requirements are met.
  33. *
  34. * @since 0.6.6
  35. */
  36. public class PluginMetaDataValidator {
  37. /** The metadata being validated. */
  38. private final PluginMetaData metadata;
  39. /** A collection of errors which have been identified. */
  40. private final Collection<String> errors
  41. = new ArrayList<String>();
  42. /**
  43. * Creates a new metadata validator.
  44. *
  45. * @param metadata The metadata to be validated
  46. */
  47. public PluginMetaDataValidator(final PluginMetaData metadata) {
  48. this.metadata = metadata;
  49. }
  50. /**
  51. * Validates the metadata file.
  52. *
  53. * @param plugins A map of known plugins to their short names
  54. * @param services A map of known services
  55. * @return A collection of errors that occurred (if any)
  56. */
  57. public Collection<String> validate(final Map<String, PluginMetaData> plugins,
  58. final MapList<String, String> services) {
  59. errors.clear();
  60. checkMetaData();
  61. checkRequirements(plugins, services);
  62. return Collections.unmodifiableCollection(errors);
  63. }
  64. /**
  65. * Checks that the metadata values are correct.
  66. */
  67. protected void checkMetaData() {
  68. if (!metadata.getVersion().isValid()) {
  69. errors.add("Missing or invalid 'version'");
  70. }
  71. if (metadata.getAuthor() == null || metadata.getAuthor().isEmpty()) {
  72. errors.add("Missing or invalid 'author'");
  73. }
  74. if (metadata.getName() == null || metadata.getName().isEmpty() || metadata.getName().indexOf(' ') != -1) {
  75. errors.add("Missing or invalid 'name'");
  76. }
  77. if (metadata.getMainClass() == null || metadata.getMainClass().isEmpty()) {
  78. errors.add("Missing or invalid 'mainclass'");
  79. }
  80. }
  81. /**
  82. * Checks that the plugin's requirements are met.
  83. *
  84. * @param plugins A map of known plugins by their short names
  85. * @param services A map of known services
  86. */
  87. protected void checkRequirements(final Map<String, PluginMetaData> plugins,
  88. final MapList<String, String> services) {
  89. checkOS(metadata.getRequirements().get("os"), System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
  90. checkFiles(metadata.getRequirements().get("files"));
  91. checkPlugins(plugins, metadata.getRequirements().get("plugins"));
  92. checkServices(services, metadata.getRequiredServices());
  93. }
  94. /**
  95. * Check if the services required by this plugin are available.
  96. *
  97. * @param knownServices A map of known services
  98. * @param services Required services
  99. */
  100. private void checkServices(final MapList<String, String> knownServices,
  101. final Collection<String> services) {
  102. if (services == null || services.isEmpty()) {
  103. return;
  104. }
  105. for (String requirement : services) {
  106. final String[] bits = requirement.split(" ", 2);
  107. final String name = bits[0];
  108. final String type = bits[1];
  109. if (!knownServices.containsKey(type)
  110. || (!name.equalsIgnoreCase("any")
  111. && !knownServices.containsValue(type, name))) {
  112. errors.add("Service " + name + " of type " + type
  113. + " not available");
  114. }
  115. }
  116. }
  117. /**
  118. * Checks to see if the OS requirements of the plugin are satisfied.
  119. * If the desired string is empty, the test passes.
  120. * Otherwise it is used as one to three colon-delimited regular expressions,
  121. * to test the name, version and architecture of the OS, respectively.
  122. * On failure, the requirementsError field will contain a user-friendly
  123. * error message.
  124. *
  125. * @param desired The desired OS requirements
  126. * @param actualName The actual name of the OS
  127. * @param actualVersion The actual version of the OS
  128. * @param actualArch The actual architecture of the OS
  129. */
  130. protected void checkOS(final String desired, final String actualName, final String actualVersion, final String actualArch) {
  131. if (desired == null || desired.isEmpty()) {
  132. return;
  133. }
  134. final String[] desiredParts = desired.split(":");
  135. if (!actualName.toLowerCase().matches(desiredParts[0])) {
  136. errors.add("Invalid OS. (Wanted: '" + desiredParts[0] + "', actual: '" + actualName + "')");
  137. } else if (desiredParts.length > 1 && !actualVersion.toLowerCase().matches(desiredParts[1])) {
  138. errors.add("Invalid OS version. (Wanted: '" + desiredParts[1] + "', actual: '" + actualVersion + "')");
  139. } else if (desiredParts.length > 2 && !actualArch.toLowerCase().matches(desiredParts[2])) {
  140. errors.add("Invalid OS architecture. (Wanted: '" + desiredParts[2] + "', actual: '" + actualArch + "')");
  141. }
  142. }
  143. /**
  144. * Checks to see if the file requirements of the plugin are satisfied.
  145. * If the desired string is empty, the test passes.
  146. * Otherwise it is passed to File.exists() to see if the file is valid.
  147. * Multiple files can be specified by using a "," to separate. And either/or
  148. * files can be specified using a "|" (eg /usr/bin/bash|/bin/bash)
  149. * If the test fails, the requirementsError field will contain a
  150. * user-friendly error message.
  151. *
  152. * @param desired The desired file requirements
  153. */
  154. protected void checkFiles(final String desired) {
  155. if (desired == null || desired.isEmpty()) {
  156. return;
  157. }
  158. for (String files : desired.split(",")) {
  159. final String[] filelist = files.split("\\|");
  160. boolean foundFile = false;
  161. for (String file : filelist) {
  162. if (new File(file).exists()) {
  163. foundFile = true;
  164. break;
  165. }
  166. }
  167. if (!foundFile) {
  168. errors.add("Required file '" + files + "' not found");
  169. }
  170. }
  171. }
  172. /**
  173. * Checks to see if the plugin requirements of the plugin are satisfied.
  174. * If the desired string is empty, the test passes.
  175. * Plugins should be specified as:
  176. * plugin1[:minversion[:maxversion]],plugin2[:minversion[:maxversion]]
  177. * Plugins will be attempted to be loaded if not loaded, else the test will
  178. * fail if the versions don't match, or the plugin isn't known.
  179. * If the test fails, the requirementsError field will contain a
  180. * user-friendly error message.
  181. *
  182. * @param plugins A map of known plugins by their short names
  183. * @param desired The desired file requirements
  184. */
  185. protected void checkPlugins(final Map<String, PluginMetaData> plugins,
  186. final String desired) {
  187. if (desired == null || desired.isEmpty()) {
  188. return;
  189. }
  190. for (String pluginName : desired.split(",")) {
  191. final String[] data = pluginName.split(":");
  192. final PluginMetaData target = plugins.get(data[0]);
  193. if (target == null) {
  194. errors.add("Required plugin '" + data[0] + "' was not found");
  195. }
  196. if (data.length > 1) {
  197. // Check plugin minimum version matches.
  198. if (target.getVersion().compareTo(new Version(data[1])) < 0) {
  199. errors.add("Plugin '" + data[0]
  200. + "' is too old (required version: " + data[1]
  201. + ", actual version: " + target.getVersion() + ")");
  202. }
  203. // Check plugin maximum version matches.
  204. if (data.length > 2 && target.getVersion().compareTo(
  205. new Version(data[2])) > 0) {
  206. errors.add("Plugin '" + data[0]
  207. + "' is too new (required version: " + data[2]
  208. + ", actual version: " + target.getVersion() + ")");
  209. }
  210. }
  211. }
  212. }
  213. }