001 //Copyright (C) 2006 A. Nelson 002 // 003 //This program is free software; you can redistribute it and/or 004 //modify it under the terms of the GNU General Public License 005 //as published by the Free Software Foundation; either version 2 006 //of the License, or (at your option) any later version. 007 // 008 //This program is distributed in the hope that it will be useful, 009 //but WITHOUT ANY WARRANTY; without even the implied warranty of 010 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 011 //GNU General Public License for more details. 012 // 013 //You should have received a copy of the GNU General Public License 014 //along with this program; if not, write to the Free Software 015 //Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 016 017 package pluginCore; 018 019 import java.util.concurrent.locks.ReentrantReadWriteLock; 020 import java.util.Vector; 021 022 /** 023 * The PluginCore is the main object in the plug-in architecture. It handles the 024 * plug-ins, starting, stopping, and message passing among them. To run: 025 * myComputer$ java PluginCore pluginName 026 * 027 */ 028 029 public class PluginCore { 030 031 private static final String pluginPrefix = "ipMedia.Client.plugins."; 032 033 /** 034 * This is the size of the PluginListElement list when the program starts. 035 */ 036 private static final int LIST_STARTING_SIZE = 3000002; 037 038 /** 039 * This is the number of elements that the list grows by each time it runs out of space. 040 */ 041 private static final int LIST_GROWTH_SIZE = 500000; 042 043 /** 044 * Turns on or off debugging output. 045 */ 046 private static final boolean DEBUG = true; 047 048 /** 049 * Turns on or off error output. 050 */ 051 private static final boolean ERROR = true; 052 053 054 /** 055 * This array contains the plug-ins that are currently loaded and running as 056 * their own threads 057 */ 058 private PluginListElement[] pluginList; 059 060 /** 061 * Expand defines the number of slots the pluginList starts with and by how 062 * many it grows when it is full 063 */ 064 private int listSize = LIST_STARTING_SIZE; 065 066 /** 067 * Initializes the PluginCore. 068 */ 069 public PluginCore() { 070 071 pluginList = new PluginListElement[listSize]; 072 073 for (int i = 0; i < listSize; i++) { 074 075 pluginList[i] = null; 076 } 077 } 078 079 /** 080 * Increase the size of pluginList by the ammount LIST_GROWTH_SIZE. 081 */ 082 private void growList() { 083 084 int newListSize; 085 PluginListElement[] newPluginList; 086 087 newListSize = listSize + LIST_GROWTH_SIZE; 088 newPluginList = new PluginListElement[newListSize]; 089 090 for (int i = 0; i < listSize; i++) { 091 092 newPluginList[i] = pluginList[i]; 093 } 094 095 for( int i = listSize; i < newListSize; i++) { 096 097 newPluginList[i] = null; 098 } 099 100 pluginList = newPluginList; 101 listSize = newListSize; 102 } 103 104 /** 105 * Loads a new plug-in into the system and starts its thread running. 106 * 107 * @param pluginName 108 * The full name of the object that you wish to load into the 109 * system. 110 * @return The plugin's Id is returned if successful and -1 is returned on 111 * failure. 112 */ 113 public int loadPlugin(String pluginName) { 114 115 Plugin newPlug; 116 PluginLoader myLoader; 117 118 printDebug("Call to load " + pluginName); 119 120 myLoader = new PluginLoader(); 121 122 try { 123 /** Load the plug-in with the classLoader * */ 124 newPlug = (Plugin) myLoader.loadClass(pluginName).newInstance(); 125 newPlug.setCoreHandeler( new CoreHandler(this, newPlug) ); 126 127 if( insertPlugin( newPlug ) == -1 ) { 128 129 printError("Unable to insert " + pluginName + " into core."); 130 return -1; 131 } 132 133 /** Now Thread it off * */ 134 newPlug.start(); 135 136 return newPlug.getID(); 137 138 } catch (ClassCastException ce) { 139 140 printDebug("Caught exception: " + ce); 141 printError("Module " + pluginName 142 + " Is not of type Plugin. Unable to load module."); 143 return -1; 144 } catch (Exception ie) { 145 146 printDebug("Caught exception: " + ie); 147 printError("Unable to load class " + pluginName); 148 return -1; 149 } 150 } 151 152 /** 153 * Loads a new plug-in into the system and initalizes it with the data 154 * from a previous plugin, that is contained in the stateIn array. 155 * 156 * @param pluginName 157 * The full name of the object that you wish to load into the 158 * system. 159 * @param stateIn The saved object array from the previous plugin. 160 * @return The plugin's Id is returned if successful and -1 is returned on 161 * failure. 162 */ 163 public int loadPlugin(String pluginName, Object[] stateIn) { 164 165 Plugin newPlug; 166 PluginLoader myLoader; 167 int plugID; 168 169 printDebug("Call to loadPlugin(2) " + pluginName); 170 171 if( stateIn == null ) { 172 173 printError("loadPlugin(2) receved null state vector"); 174 return -1; 175 } 176 177 myLoader = new PluginLoader(); 178 179 try { 180 /** Load the plug-in with the classLoader * */ 181 newPlug = (Plugin) myLoader.loadClass(pluginName).newInstance(); 182 183 if( newPlug.loadNewState( stateIn, new CoreHandler(this, newPlug) ) == false ) { 184 185 //ToDo: Should try to clean up the plugin. 186 printError("Plugin loading failed, " + 187 "new Plugin failed to take state."); 188 return -1; 189 } 190 191 /** 192 * Find out where the old plugin was stored; 193 **/ 194 plugID = newPlug.getID(); 195 196 if( this.pluginList[plugID] == null ) { 197 198 printError("Plugin loading failed, " + 199 "Trying to refresh a plugin into a null position"); 200 return -1; 201 } 202 203 if( this.pluginList[plugID].hasUnstableLock() == false ) { 204 205 printError("Plugin loading failed, " + 206 "Trying to refresh a plugin (" + plugID + 207 ") that does not have a unstable lock"); 208 return -1; 209 } 210 211 pluginList[plugID].setThePlugin( newPlug ); 212 pluginList[plugID].getThePlugin().start(); 213 214 return plugID; 215 216 } catch (ClassCastException ce) { 217 218 printDebug("Caught exception: " + ce); 219 printError("Module " + pluginName 220 + " Is not of type Plugin. Unable to load module."); 221 return -1; 222 } catch (Exception ie) { 223 224 printDebug("Caught exception: " + ie); 225 printError("Unable to load class " + pluginName); 226 return -1; 227 } 228 } 229 230 231 232 233 /** 234 * Adds a plug-in into the list of active plug-ins. 235 * 236 * @param newPlug 237 * The plug-in to be added. 238 * @return The id of the plug-in on success or -1 on failure. 239 */ 240 synchronized private int insertPlugin(Plugin newPlug) { 241 242 try { 243 244 for( int i = 0; i < listSize; i++ ) { 245 246 if (pluginList[i] == null) { 247 248 pluginList[i] = new PluginListElement( newPlug ); 249 newPlug.setID(i); 250 return i; 251 } 252 } 253 254 255 /** 256 * There was no room in the array, make a newer, larger, better array to 257 * hold our data. 258 */ 259 260 int oldListSize = listSize; 261 262 growList(); 263 newPlug.setID(oldListSize); 264 pluginList[oldListSize] = new PluginListElement( newPlug ); 265 266 return oldListSize; 267 268 } catch (NullPointerException npe) { 269 270 printError("Caught null pointer exception in insertPlugin: " + npe ); 271 return -1; 272 } catch (Exception e) { 273 274 printError("Caught " + e + " in insertPlugin."); 275 return -1; 276 } 277 } 278 279 /** 280 * Requests that the core send the message 'messIn' to the plug-in with the 281 * same Id as the destID field in the Message object. 282 * 283 * ToDo: This should really return true... 284 * 285 * @param messIn 286 * The message to be sent. 287 * @return Returns 1 on success and -1 on failure. 288 */ 289 public int postMessage(Message messIn) { 290 291 int destPluginID; 292 293 if( messIn == null ) { 294 295 return -1; 296 } 297 298 destPluginID = messIn.destID; 299 300 try { 301 302 if (isValidId(destPluginID) == false) { 303 304 printError("Could not post message, dest. Id is invalid."); 305 return -1; 306 } 307 308 pluginList[destPluginID].aquireReadLock(); 309 310 if( pluginList[destPluginID].getThePlugin() == null ) { 311 312 printError( "The plugin that was being posted to [" + destPluginID + "]was null"); 313 return -1; 314 } 315 316 if ( pluginList[destPluginID].getThePlugin().receive(messIn) == -1) { 317 318 printDebug("Message was not properly receved by plugin \"" 319 + pluginList[destPluginID].getThePlugin().getPluginName() 320 + "\" assocated with id " + destPluginID 321 + " from plugin id " + messIn.sourceID); 322 return -1; 323 } 324 return 1; 325 } catch (Exception e) { 326 327 printDebug("Caught exception " + e + " in postMessage"); 328 e.printStackTrace(); 329 330 pluginList[destPluginID].releaseReadLock(); 331 return -1; 332 } 333 finally { 334 335 pluginList[destPluginID].releaseReadLock(); 336 } 337 } 338 339 /** 340 * Gets the Id of a plug-in, this Id can then be used to send messages to 341 * that plug-in. If the plug-in is not already loaded then the system will 342 * attempt to load it, then return the ID. 343 * 344 * @param plugin 345 * The name of the plugin you are looking for, the PluginCore 346 * checks the field Plugin.pluginName. 347 * @return The Id of the plug-in, or a -1 on failure. 348 */ 349 public int pluginIDRequest(String plugin) { 350 351 String localName; 352 353 if (plugin.startsWith(pluginPrefix) == false) { 354 /** Someone is trying to load something that is not a plugin...* */ 355 356 printError("Request for non plug-in Id: " + plugin); 357 return -1; 358 } 359 360 try { 361 362 /** Ignore the first 24 char's * */ 363 localName = plugin.substring(pluginPrefix.length()); 364 365 for (int i = 0; i < listSize; i++) { 366 367 368 if (pluginList[i] != null) try{ 369 370 pluginList[i].aquireReadLock(); 371 if (localName.equals(pluginList[i].getThePlugin().getPluginName()) == true) { 372 373 return i; 374 } 375 } finally { 376 377 pluginList[i].releaseReadLock(); 378 } 379 } 380 381 printError("Could not find plugin " + plugin + " to load"); 382 383 } catch (Exception e) { 384 385 printError("Caught exception in pluginIDRequest"); 386 } 387 return -1; 388 } 389 390 /** 391 * Removes a plugin from the running PluginCore. 392 * 393 * NOTE: This will not unload the plugin that called this 394 * that is kind of bad, but it will hang... 395 * 396 * @param plugId 397 * The Id of the plugin to remove. 398 * @return True on sucess, else false. 399 */ 400 public boolean unloadPlugin(int plugId) { 401 402 if (isValidId(plugId) == false) { 403 404 printDebug("Unable to unload plugin, bad Id"); 405 return false; 406 } 407 try { 408 409 pluginList[plugId].aquireWriteLock(); 410 411 if( pluginList[plugId].getThePlugin() == null ) { 412 413 printError("trying to unload a serialized plugin!"); 414 return false; 415 } 416 417 shutdownAndJoinPlugin( plugId ); 418 419 pluginList[plugId] = null; 420 printDebug("Plugin id " + plugId + " is now null"); 421 422 return true; 423 } catch (Exception e) { 424 425 printDebug("unloadPlugin() Caught exception " + e 426 + " in postMessage"); 427 e.printStackTrace(); 428 return false; 429 } 430 431 } 432 433 /** 434 * This will shut down a plugin and join the thread. 435 * @param plugId This is the id of the plugin to shutdown. 436 * @return True on sucess, else false. 437 */ 438 private boolean shutdownAndJoinPlugin(int plugId ) { 439 440 441 pluginList[plugId].getThePlugin().shutdown(); 442 if (pluginList[plugId].getThePlugin().safeJoin() == false) { 443 444 printError("Could not join when shutting down plugin " + plugId); 445 return false; 446 } 447 return true; 448 } 449 450 /** 451 * Checks if a plugin has a unstable lock held on it. 452 * @param id The id of the plugin to ckeck. 453 * @return True if the plugin is locked, false if it is unlocked. 454 */ 455 public boolean isLocked( int id ) { 456 //ToDo: This should return a Exception if there is no object 457 //ToDo: Should build a NoSuchPluginException for the plugin core. 458 if( isValidId( id ) == false ) return false; 459 460 try { 461 return this.pluginList[id].hasUnstableLock(); 462 } catch (Exception e){ 463 464 /** 465 * We should really be tossing a exception here, wtf happened? 466 **/ 467 return false; 468 } 469 } 470 471 /** 472 * This will lock a plugin, stop it, reduce the state to a byte aray and return that array. 473 * @param pluginId The plugin to save. 474 * @return The state of the plugin in a byte array. 475 */ 476 public Object[] savePlugin(int pluginId) { 477 478 Object[] tmpArray; 479 480 if (isValidId(pluginId) == false) { 481 482 printError("Id does not exist"); 483 return null; 484 } 485 486 if( pluginList[pluginId] == null ) { 487 488 printError("Could not isolate the plugin"); 489 return null; 490 } 491 pluginList[pluginId].aquireWriteLock(); 492 493 tmpArray = pluginList[pluginId].getThePlugin().savePluginState(); 494 495 if( tmpArray == null ) { 496 497 printError("The plugin " + pluginId + " Did not properly produce a state array."); 498 return null; 499 } 500 501 shutdownAndJoinPlugin( pluginId ); 502 pluginList[pluginId].setThePlugin( null ); 503 504 return tmpArray; 505 } 506 507 /** 508 * Checks to see if the plugin Id given is a valid address in the plugin 509 * list. 510 * 511 * @param plugId 512 * The id to check. 513 * @return True if the id is a valid address, else false. 514 */ 515 private boolean isValidId(int plugId) { 516 517 try { 518 if (( plugId >= listSize ) || ( plugId < 0 )) { 519 520 return false; 521 } 522 523 if (pluginList[plugId] == null) { 524 525 return false; 526 } 527 return true; 528 529 } catch (Exception e) { 530 531 printError("isValidId() Caught exception " + e + " in postMessage"); 532 e.printStackTrace(); 533 return false; 534 } 535 } 536 537 538 539 /** 540 * Stops the system running, first calls the 'shutdown()' call for all 541 * loaded plug-ins then calls a join() on them before finally 'exiting 542 * itself. 543 */ 544 public void shutdown() { 545 546 /** 547 * ToDo: Have it deal with serialized plugins properly. 548 */ 549 int numPlugs; 550 551 printDebug("Shutdown Called"); 552 553 numPlugs = listSize; 554 while (numPlugs > 0) { 555 556 numPlugs--; 557 if (pluginList[numPlugs] != null) 558 unloadPlugin(numPlugs); 559 } 560 System.exit(0); 561 } 562 563 /** 564 * Lists the plug-ins that are currently loaded in the system. 565 * 566 * @return A list of loaded plug-ins with newlines separating them. 567 */ 568 public String[] listLoadedPlugins() { 569 570 int numPlugs; 571 Vector<String> stringList; 572 Object[] tmpList; 573 574 numPlugs = pluginList.length; 575 stringList = new Vector<String>(); 576 577 while (numPlugs > 0) { 578 579 numPlugs--; 580 if (pluginList[numPlugs] != null) { 581 582 try { 583 pluginList[numPlugs].aquireReadLock(); 584 stringList.add( pluginList[numPlugs].getThePlugin().toString()); 585 586 } catch (Exception e) { 587 588 printDebug("Caught exception in listLoadedPlugins"); 589 }finally { 590 591 pluginList[numPlugs].releaseReadLock(); 592 } 593 } 594 } 595 596 //Copy the array of Object to an array of String 597 tmpList = stringList.toArray(); 598 int listSize = tmpList.length; 599 String[] retArray = new String[listSize]; 600 System.arraycopy(tmpList, 0, retArray, 0, listSize); 601 602 return retArray; 603 } 604 605 private void loadAdmin() { 606 607 loadPlugin("pluginCore.AdminPlugin"); 608 } 609 /** 610 * The main function. This is where the magic happens. 611 * 612 * @param args 613 * Handed to the program on load. 614 */ 615 public static void main(String[] args) { 616 617 PluginCore myCore; 618 boolean useAdmin; 619 String argA; 620 621 useAdmin = false; 622 623 if (args.length > 0) { 624 625 argA = args[0]; 626 } else { 627 628 printUseage(); 629 return; 630 } 631 632 if( argA.equals( "-A") == true ){ 633 634 if( args.length > 2 ) { 635 636 printUseage(); 637 return; 638 } 639 640 useAdmin = true; 641 argA = args[1]; 642 } 643 644 if (DEBUG) 645 System.out.println("Loading " + argA); 646 647 myCore = new PluginCore(); 648 if( useAdmin == true ) 649 myCore.loadAdmin(); 650 651 myCore.loadPlugin( argA ); 652 } 653 654 /** 655 * Prints the useage command to the command line when poor input is given. 656 */ 657 private static void printUseage(){ 658 659 System.out.println("Useage: java [-A] PluginCore pluginName"); 660 return; 661 } 662 663 /** 664 * This will print out an debug mesage identifying it as coming from the PluginCore. 665 * @param debugMsg This is the debug message. 666 */ 667 protected void printDebug(String debugMsg) { 668 669 if (DEBUG == true) 670 System.out.println("PluginCore: " + debugMsg); 671 } 672 673 /** 674 * This will print out an error message identifying it as coming from the PluginCore. 675 * @param debugMsg This is the error message. 676 */ 677 protected void printError(String errorMsg) { 678 679 /** 680 * This should have a error on or off. 681 */ 682 if (ERROR == true) 683 System.err.println("Error occoured in the PluginCore: " + errorMsg); 684 685 } 686 687 /** 688 * The implementation of ClassLoader that is used by the PluginCore. 689 */ 690 private class PluginLoader extends ClassLoader { 691 692 /** 693 * Loads a new class from the provided byte array of class data. 694 * @param classDataIn 695 * @return The new Class or null on fail. 696 */ 697 public Class loadFromByteArray( byte[] classDataIn ){ 698 699 try{ 700 701 return defineClass(null, classDataIn, 0, classDataIn.length); 702 }catch( ClassFormatError e ) { 703 704 System.out.println("Class Loader encountered ClassFormatError " + e); 705 return null; 706 } 707 } 708 } 709 710 /** 711 * This is a single element that conains a plugin and a 712 * stable/unstable lock to controll acess to the plugin. 713 * 714 * @author Avro Nelson. 715 */ 716 private class PluginListElement { 717 718 Plugin thePlugin; 719 ReentrantReadWriteLock theLock; 720 721 PluginListElement( ){ 722 723 theLock = new ReentrantReadWriteLock(); 724 thePlugin = null; 725 } 726 727 PluginListElement( Plugin plug ){ 728 729 theLock = new ReentrantReadWriteLock(); 730 thePlugin = plug; 731 } 732 733 public void aquireReadLock() { 734 735 736 theLock.readLock().lock(); 737 } 738 739 public boolean hasUnstableLock(){ 740 741 return theLock.isWriteLocked(); 742 } 743 744 public void releaseReadLock() { 745 746 747 theLock.readLock().unlock(); 748 } 749 750 public void aquireWriteLock() { 751 752 theLock.writeLock().lock(); 753 } 754 755 public void releaseWriteLock() { 756 757 theLock.writeLock().unlock(); 758 } 759 760 public Plugin getThePlugin() { 761 762 return thePlugin; 763 } 764 765 public void setThePlugin(Plugin thePlugin) { 766 767 this.thePlugin = thePlugin; 768 } 769 770 public ReentrantReadWriteLock getTheLock() { 771 772 return theLock; 773 } 774 } 775 }