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    }