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 }