1 11 12 package org.jivesoftware.messenger.container; 13 14 import org.dom4j.Attribute; 15 import org.dom4j.Document; 16 import org.dom4j.Element; 17 import org.dom4j.io.SAXReader; 18 import org.jivesoftware.admin.AdminConsole; 19 import org.jivesoftware.messenger.XMPPServer; 20 import org.jivesoftware.util.JiveGlobals; 21 import org.jivesoftware.util.Log; 22 import org.jivesoftware.util.Version; 23 24 import java.io.File ; 25 import java.io.FileFilter ; 26 import java.io.FileOutputStream ; 27 import java.io.InputStream ; 28 import java.util.*; 29 import java.util.concurrent.ScheduledExecutorService ; 30 import java.util.concurrent.ScheduledThreadPoolExecutor ; 31 import java.util.concurrent.TimeUnit ; 32 import java.util.concurrent.ConcurrentHashMap ; 33 import java.util.jar.JarEntry ; 34 import java.util.jar.JarFile ; 35 import java.util.zip.ZipFile ; 36 37 49 public class PluginManager { 50 51 private File pluginDirectory; 52 private Map <String , Plugin> plugins; 53 private Map <Plugin, PluginClassLoader> classloaders; 54 private Map <Plugin, File > pluginDirs; 55 private boolean setupMode = !(Boolean.valueOf(JiveGlobals.getXMLProperty("setup")).booleanValue()); 56 private ScheduledExecutorService executor = null; 57 private Map <Plugin, PluginDevEnvironment> pluginDevelopment; 58 private Map <Plugin, List<String >> parentPluginMap; 59 private Map <Plugin, String > childPluginMap; 60 private Set<String > devPlugins; 61 62 67 public PluginManager(File pluginDir) { 68 this.pluginDirectory = pluginDir; 69 plugins = new ConcurrentHashMap <String , Plugin>(); 70 pluginDirs = new HashMap <Plugin, File >(); 71 classloaders = new HashMap <Plugin, PluginClassLoader>(); 72 pluginDevelopment = new HashMap <Plugin, PluginDevEnvironment>(); 73 parentPluginMap = new HashMap <Plugin, List<String >>(); 74 childPluginMap = new HashMap <Plugin, String >(); 75 devPlugins = new HashSet<String >(); 76 } 77 78 81 public void start() { 82 executor = new ScheduledThreadPoolExecutor (1); 83 executor.scheduleWithFixedDelay(new PluginMonitor(), 0, 15, TimeUnit.SECONDS); 84 } 85 86 89 public void shutdown() { 90 if (executor != null) { 92 executor.shutdown(); 93 } 94 for (Plugin plugin : plugins.values()) { 96 plugin.destroyPlugin(); 97 } 98 plugins.clear(); 99 pluginDirs.clear(); 100 classloaders.clear(); 101 pluginDevelopment.clear(); 102 childPluginMap.clear(); 103 } 104 105 110 public Collection<Plugin> getPlugins() { 111 return Collections.unmodifiableCollection(plugins.values()); 112 } 113 114 122 public Plugin getPlugin(String name) { 123 return plugins.get(name); 124 } 125 126 132 public File getPluginDirectory(Plugin plugin) { 133 return pluginDirs.get(plugin); 134 } 135 136 149 private void loadPlugin(File pluginDir) { 150 if (setupMode && !(pluginDir.getName().equals("admin"))) { 152 return; 153 } 154 Log.debug("Loading plugin " + pluginDir.getName()); 155 Plugin plugin = null; 156 try { 157 File pluginConfig = new File (pluginDir, "plugin.xml"); 158 if (pluginConfig.exists()) { 159 SAXReader saxReader = new SAXReader(); 160 Document pluginXML = saxReader.read(pluginConfig); 161 162 Element minServerVersion = (Element)pluginXML.selectSingleNode("/plugin/minServerVersion"); 165 if (minServerVersion != null) { 166 String requiredVersion = minServerVersion.getTextTrim(); 167 Version version = XMPPServer.getInstance().getServerInfo().getVersion(); 168 String hasVersion = version.getMajor() + "." + version.getMinor() + "." + 169 version.getMicro(); 170 if (hasVersion.compareTo(requiredVersion) < 0) { 171 String msg = "Ignoring plugin " + pluginDir.getName() + ": requires " + 172 "server version " + requiredVersion; 173 Log.warn(msg); 174 System.out.println(msg); 175 return; 176 } 177 } 178 179 PluginClassLoader pluginLoader; 180 181 Element parentPluginNode = (Element)pluginXML.selectSingleNode("/plugin/parentPlugin"); 184 if (parentPluginNode != null) { 185 String parentPlugin = parentPluginNode.getTextTrim(); 186 if (plugins.containsKey(parentPlugin)) { 188 pluginLoader = classloaders.get(getPlugin(parentPlugin)); 189 pluginLoader.addDirectory(pluginDir); 190 } 191 else { 192 if (pluginDir.getName().compareTo(parentPlugin) < 0) { 196 File file = new File (pluginDir.getParentFile(), parentPlugin + ".jar"); 198 if (file.exists()) { 199 return; 202 } 203 else { 204 file = new File (pluginDir.getParentFile(), parentPlugin + ".war"); 205 if (file.exists()) { 206 return; 209 } 210 else { 211 String msg = "Ignoring plugin " + pluginDir.getName() + ": parent plugin " + 212 parentPlugin + " not present."; 213 Log.warn(msg); 214 System.out.println(msg); 215 return; 216 } 217 } 218 } 219 else { 220 String msg = "Ignoring plugin " + pluginDir.getName() + ": parent plugin " + 221 parentPlugin + " not present."; 222 Log.warn(msg); 223 System.out.println(msg); 224 return; 225 } 226 } 227 } 228 else { 230 pluginLoader = new PluginClassLoader(pluginDir); 231 } 232 233 Element developmentNode = (Element)pluginXML.selectSingleNode("/plugin/development"); 236 PluginDevEnvironment dev = null; 237 if (developmentNode != null) { 238 Element webRoot = (Element)developmentNode.selectSingleNode( 239 "/plugin/development/webRoot"); 240 Element classesDir = (Element)developmentNode.selectSingleNode( 241 "/plugin/development/classesDir"); 242 243 dev = new PluginDevEnvironment(); 244 245 String wrd = webRoot.getTextTrim(); 246 File webRootDir = new File (wrd); 247 if (!webRootDir.exists()) { 248 webRootDir = new File (pluginDir, wrd); 250 } 251 252 if (webRootDir.exists()) { 253 dev.setWebRoot(webRootDir); 254 } 255 256 String cd = classesDir.getTextTrim(); 257 File classes = new File (cd); 258 if (!classes.exists()) { 259 classes = new File (pluginDir, cd); 261 } 262 263 if (classes.exists()) { 264 dev.setClassesDir(classes); 265 pluginLoader.addURL(classes.getAbsoluteFile().toURL()); 266 } 267 } 268 269 pluginLoader.initialize(); 270 271 String className = pluginXML.selectSingleNode("/plugin/class").getText(); 272 plugin = (Plugin)pluginLoader.loadClass(className).newInstance(); 273 plugin.initializePlugin(this, pluginDir); 274 plugins.put(pluginDir.getName(), plugin); 275 pluginDirs.put(plugin, pluginDir); 276 277 if (parentPluginNode != null) { 279 String parentPlugin = parentPluginNode.getTextTrim(); 280 List<String > childrenPlugins = parentPluginMap.get(plugins.get(parentPlugin)); 281 if (childrenPlugins == null) { 282 childrenPlugins = new ArrayList<String >(); 283 parentPluginMap.put(plugins.get(parentPlugin), childrenPlugins); 284 } 285 childrenPlugins.add(pluginDir.getName()); 286 childPluginMap.put(plugin, parentPlugin); 288 } 289 else { 290 classloaders.put(plugin, pluginLoader); 293 } 294 295 File webXML = new File (pluginDir, "web" + File.separator + "WEB-INF" + 297 File.separator + "web.xml"); 298 if (webXML.exists()) { 299 PluginServlet.registerServlets(this, plugin, webXML); 300 } 301 File customWebXML = new File (pluginDir, "web" + File.separator + "WEB-INF" + 303 File.separator + "web-custom.xml"); 304 if (customWebXML.exists()) { 305 PluginServlet.registerServlets(this, plugin, customWebXML); 306 } 307 308 if (dev != null) { 309 pluginDevelopment.put(plugin, dev); 310 } 311 312 Element adminElement = (Element)pluginXML.selectSingleNode("/plugin/adminconsole"); 314 if (adminElement != null) { 315 Element imageEl = (Element)adminElement.selectSingleNode( 317 "/plugin/adminconsole/global/logo-image"); 318 if (imageEl != null) { 319 imageEl.setText("plugins/" + pluginDir.getName() + "/" + imageEl.getText()); 320 } 321 imageEl = (Element)adminElement.selectSingleNode( 322 "/plugin/adminconsole/global/login-image"); 323 if (imageEl != null) { 324 imageEl.setText("plugins/" + pluginDir.getName() + "/" + imageEl.getText()); 325 } 326 List urls = adminElement.selectNodes("//@url"); 329 for (int i = 0; i < urls.size(); i++) { 330 Attribute attr = (Attribute)urls.get(i); 331 attr.setValue("plugins/" + pluginDir.getName() + "/" + attr.getValue()); 332 } 333 AdminConsole.addModel(pluginDir.getName(), adminElement); 334 } 335 } 336 else { 337 Log.warn("Plugin " + pluginDir + " could not be loaded: no plugin.xml file found"); 338 } 339 } 340 catch (Exception e) { 341 Log.error("Error loading plugin", e); 342 } 343 } 344 345 357 public void unloadPlugin(String pluginName) { 358 Log.debug("Unloading plugin " + pluginName); 359 360 Plugin plugin = plugins.get(pluginName); 361 if (plugin == null) { 362 return; 363 } 364 365 if (parentPluginMap.containsKey(plugin)) { 367 for (String childPlugin : parentPluginMap.get(plugin)) { 368 Log.debug("Unloading child plugin: " + childPlugin); 369 unloadPlugin(childPlugin); 370 } 371 parentPluginMap.remove(plugin); 372 } 373 374 File webXML = new File (pluginDirectory, pluginName + File.separator + "web" + File.separator + "WEB-INF" + 375 File.separator + "web.xml"); 376 if (webXML.exists()) { 377 AdminConsole.removeModel(pluginName); 378 PluginServlet.unregisterServlets(webXML); 379 } 380 File customWebXML = new File (pluginDirectory, pluginName + File.separator + "web" + File.separator + "WEB-INF" + 381 File.separator + "web-custom.xml"); 382 if (customWebXML.exists()) { 383 PluginServlet.unregisterServlets(customWebXML); 384 } 385 386 plugin.destroyPlugin(); 387 PluginClassLoader classLoader = classloaders.get(plugin); 388 if (classLoader != null) { 390 classLoader.destroy(); 391 } 392 plugins.remove(pluginName); 393 pluginDirs.remove(plugin); 394 classloaders.remove(plugin); 395 396 if (childPluginMap.containsKey(plugin)) { 399 unloadPlugin(childPluginMap.get(plugin)); 400 } 401 childPluginMap.remove(plugin); 402 } 403 404 414 public Class loadClass(Plugin plugin, String className) throws ClassNotFoundException , 415 IllegalAccessException , InstantiationException 416 { 417 PluginClassLoader loader = classloaders.get(plugin); 418 return loader.loadClass(className); 419 } 420 421 429 public PluginDevEnvironment getDevEnvironment(Plugin plugin) { 430 return pluginDevelopment.get(plugin); 431 } 432 433 441 public String getName(Plugin plugin) { 442 String name = getElementValue(plugin, "/plugin/name"); 443 if (name != null) { 444 return name; 445 } 446 else { 447 return pluginDirs.get(plugin).getName(); 448 } 449 } 450 451 458 public String getDescription(Plugin plugin) { 459 return getElementValue(plugin, "/plugin/description"); 460 } 461 462 469 public String getAuthor(Plugin plugin) { 470 return getElementValue(plugin, "/plugin/author"); 471 } 472 473 480 public String getVersion(Plugin plugin) { 481 return getElementValue(plugin, "/plugin/version"); 482 } 483 484 492 private String getElementValue(Plugin plugin, String xpath) { 493 File pluginDir = pluginDirs.get(plugin); 494 if (pluginDir == null) { 495 return null; 496 } 497 try { 498 File pluginConfig = new File (pluginDir, "plugin.xml"); 499 if (pluginConfig.exists()) { 500 SAXReader saxReader = new SAXReader(); 501 Document pluginXML = saxReader.read(pluginConfig); 502 Element element = (Element)pluginXML.selectSingleNode(xpath); 503 if (element != null) { 504 return element.getTextTrim(); 505 } 506 } 507 } 508 catch (Exception e) { 509 Log.error(e); 510 } 511 return null; 512 } 513 514 519 private class PluginMonitor implements Runnable { 520 521 public void run() { 522 try { 523 String pluginDirs = System.getProperty("pluginDirs"); 525 if (pluginDirs != null) { 526 StringTokenizer st = new StringTokenizer(pluginDirs, ", "); 527 while (st.hasMoreTokens()) { 528 String dir = st.nextToken(); 529 if (!devPlugins.contains(dir)) { 530 loadPlugin(new File (dir)); 531 devPlugins.add(dir); 532 } 533 } 534 } 535 536 File [] jars = pluginDirectory.listFiles(new FileFilter () { 537 public boolean accept(File pathname) { 538 String fileName = pathname.getName().toLowerCase(); 539 return (fileName.endsWith(".jar") || fileName.endsWith(".war")); 540 } 541 }); 542 543 for (int i = 0; i < jars.length; i++) { 544 File jarFile = jars[i]; 545 String pluginName = jarFile.getName().substring(0, 546 jarFile.getName().length() - 4).toLowerCase(); 547 File dir = new File (pluginDirectory, pluginName); 549 if (!dir.exists()) { 551 unzipPlugin(pluginName, jarFile, dir); 552 } 553 else if (jarFile.lastModified() > dir.lastModified()) { 556 unloadPlugin(pluginName); 557 System.gc(); 559 int count = 0; 560 while (!deleteDir(dir) && count < 5) { 561 Log.error("Error unloading plugin " + pluginName + ". " + 562 "Will attempt again momentarily."); 563 Thread.sleep(5000); 564 count++; 565 } 566 unzipPlugin(pluginName, jarFile, dir); 568 } 569 } 570 571 File [] dirs = pluginDirectory.listFiles(new FileFilter () { 572 public boolean accept(File pathname) { 573 return pathname.isDirectory(); 574 } 575 }); 576 577 Arrays.sort(dirs, new Comparator<File >() { 580 public int compare(File file1, File file2) { 581 if (file1.getName().equals("admin")) { 582 return -1; 583 } 584 else if (file2.getName().equals("admin")) { 585 return 1; 586 } 587 else 588 return file1.compareTo(file2); 589 } 590 }); 591 592 List<String > toDelete = new ArrayList<String >(); 597 for (File pluginDir : dirs) { 598 String pluginName = pluginDir.getName(); 599 if (pluginName.equals("admin")) { 600 continue; 601 } 602 File file = new File (pluginDirectory, pluginName + ".jar"); 603 if (!file.exists()) { 604 file = new File (pluginDirectory, pluginName + ".war"); 605 if (!file.exists()) { 606 toDelete.add(pluginName); 607 } 608 } 609 } 610 for (String pluginName : toDelete) { 611 unloadPlugin(pluginName); 612 System.gc(); 613 int count = 0; 614 File dir = new File (pluginDirectory, pluginName); 615 while (!deleteDir(dir) && count < 5) { 616 Log.error("Error unloading plugin " + pluginName + ". " + 617 "Will attempt again momentarily."); 618 Thread.sleep(5000); 619 count++; 620 } 621 } 622 623 for (int i = 0; i < dirs.length; i++) { 625 File dirFile = dirs[i]; 626 if (dirFile.exists() && !plugins.containsKey(dirFile.getName())) { 628 loadPlugin(dirFile); 629 } 630 } 631 } 632 catch (Exception e) { 633 Log.error(e); 634 } 635 } 636 637 645 private void unzipPlugin(String pluginName, File file, File dir) { 646 try { 647 ZipFile zipFile = new JarFile (file); 648 if (zipFile.getEntry("plugin.xml") == null) { 650 return; 651 } 652 dir.mkdir(); 653 Log.debug("Extracting plugin: " + pluginName); 654 for (Enumeration e = zipFile.entries(); e.hasMoreElements();) { 655 JarEntry entry = (JarEntry )e.nextElement(); 656 File entryFile = new File (dir, entry.getName()); 657 if (entry.getName().toLowerCase().endsWith("manifest.mf")) { 659 continue; 660 } 661 if (!entry.isDirectory()) { 662 entryFile.getParentFile().mkdirs(); 663 FileOutputStream out = new FileOutputStream (entryFile); 664 InputStream zin = zipFile.getInputStream(entry); 665 byte[] b = new byte[512]; 666 int len = 0; 667 while ((len = zin.read(b)) != -1) { 668 out.write(b, 0, len); 669 } 670 out.flush(); 671 out.close(); 672 zin.close(); 673 } 674 } 675 zipFile.close(); 676 zipFile = null; 677 } 678 catch (Exception e) { 679 Log.error(e); 680 } 681 } 682 683 686 public boolean deleteDir(File dir) { 687 if (dir.isDirectory()) { 688 String [] children = dir.list(); 689 for (int i = 0; i < children.length; i++) { 690 boolean success = deleteDir(new File (dir, children[i])); 691 if (!success) { 692 return false; 693 } 694 } 695 } 696 return dir.delete(); 697 } 698 } 699 } | Popular Tags |