11 Replies Latest reply: Feb 14, 2010 4:50 PM by 843798 RSS

    PluginManager implementation

    843798
      Hey guys, I designed a plugin manager I would like you to comment about, it is a bit more of 100 lines but most of it are try and catch blocks...

      Thanks in advance,
      Adam Zehavi.
      package initializer;
      
      import java.io.File;
      import java.io.IOException;
      import java.net.MalformedURLException;
      import java.net.URL;
      import java.net.URLClassLoader;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.jar.JarFile;
      import java.util.jar.Manifest;
      
      import ui.accessories.FileType;
      import ui.accessories.ApplicationFile.ApplicationDirectory;
      
      /**
       * @author TacB0sS - Adam zehavi
       *
       */
      
      public class PluginManager {
           /**
            * @author TacB0sS - Adam Zehavi.<BR>
            * 
            * This object serves as the plug-in manifest. <br>
            * Each Plug-in generates it's own manifest independently. <br> 
            * The only thing you need to do is specify the path where you want to generate the manifest to. <br> // not implemented yet
            */
           public static class PluginManifest {
                private HashMap<String, String> manifest = new HashMap<String, String>();
                public PluginManifest put(String key, String value) {
                     manifest.put(key, value);
                     return this;
                }
                public PluginManifest remove(String key) {
                     manifest.remove(key);
                     return this;
                }
           }
           /**
            * @author TacB0sS - Adam Zehavi.<BR>
            * 
            * This object is responsible for loading and storing the loaded classes,<br>
            * and is able to receive new JarFiles to the build path.
            */
           public static class PluginClassLoader extends URLClassLoader {
                public PluginClassLoader() {
                     super(new URL[]{}, PluginClassLoader.class.getClassLoader());
                }
                
                public synchronized Class<?> loadClass(String className) throws ClassNotFoundException {
                     return findClass(className);
                }
                
                public synchronized void add(JarFile jar) {
                     try {
                          addURL(new URL("jar:file:/" + jar.getName() + "!/"));
                     } catch (MalformedURLException e) {
                          System.out.println("Add here you exception details");
                          e.printStackTrace();
                     }
                }
           }
           /**
            * 
            * @author TacB0sS - Adam Zehavi.
            * @param <Type> - The Plug-in base abstract class.
            * 
            * This object is responsible to load Plug-in file from a directory or a specific Jar file.<br>
            * It holds the PluginClassLoader for this specific Plug-in Type, and stores a single instance of a loaded jar Plug-in file.  
            */
           public static class PluginLoader<Type extends Object> {
                public static final String typeAttributeEntryName = new String("PluginType");
                public static final String mainClassAttributeEntryName = new String("PluginMainClass");
                private FileType fileType;
                private PluginClassLoader loader = new PluginClassLoader();
                private String pluginType;
                private final ArrayList<Type> loadedPlugins = new ArrayList<Type>();
                private final ArrayList<String> loadedPluginsFiles = new ArrayList<String>();
                
                protected PluginLoader(FileType type, String pluginType) {
                     this.fileType = type;
                     this.pluginType = pluginType;
                }
                public final void removeAllPlugins() {
                     loader = new PluginClassLoader();
                     loadedPlugins.clear();
                     loadedPluginsFiles.clear();
                }
                public void load(File file) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
                     wasThisFileLoaded(file);
                     fileType.checkType(file);
                     JarFile jar = new JarFile(file);
                     isOfType(jar);
                     loadedPlugins.add(getPluginMainObject(jar));
                }
                
                private void wasThisFileLoaded(File file) throws IOException {
                     for(String str:loadedPluginsFiles)
                          if(str.equalsIgnoreCase(file.getAbsolutePath()))
                               throw new IOException("Plug-in was loaded already!");
              }
                
                @SuppressWarnings("unchecked")
                public Type getPluginMainObject(JarFile jar) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
                     Manifest m = jar.getManifest();
                     String s = m.getMainAttributes().getValue(mainClassAttributeEntryName);
                     loader.add(jar);
                     return (Type) Class.forName(s).newInstance();
                }
                
                public void isOfType(JarFile jar) throws IOException {
                     Manifest m = jar.getManifest();
                     String s = m.getMainAttributes().getValue(typeAttributeEntryName);
                     if (!pluginType.equalsIgnoreCase(s))
                          throw new IOException("Wrong plugin type");
                }
                
           }
           private ArrayList<PluginLoader<?>> loaders = new ArrayList<PluginLoader<?>>();
           
           public void loadPlugins(ApplicationDirectory dir) {
                File[] files = dir.getListOfFiles();
                for (File file : files)
                     for (PluginLoader<?> loader : loaders)
                          try {
                               loader.load(file);
                               break;
                          } catch (ClassNotFoundException e) {
                               System.out.println(e.getMessage());
                          } catch (IOException e) {
                               System.out.println(e.getMessage());
                          } catch (InstantiationException e) {
                               System.out.println(e.getMessage());
                          } catch (IllegalAccessException e) {
                               System.out.println(e.getMessage());
                          }
           }
           
           public void loadPlugins(ApplicationDirectory dir, PluginLoader<?> loader) {
                File[] files = dir.getListOfFiles();
                for (File file : files)
                     try {
                          loader.load(file);
                          break;
                     } catch (IOException e) {
                          System.out.println(e.getMessage());
                     } catch (ClassNotFoundException e) {
                          System.out.println(e.getMessage());
                     } catch (InstantiationException e) {
                          System.out.println(e.getMessage());
                     } catch (IllegalAccessException e) {
                          System.out.println(e.getMessage());
                     }
           }
           
           public void addPluginLoader(PluginLoader<?> loader) {
                loaders.add(loader);
           }
           public void removePluginLoader(PluginLoader<?> loader) {
                loaders.remove(loader);
           }
      }
        • 1. Re: PluginManager implementation
          DrClap
          Your loadPlugins() methods: their name suggests they might load more than one plugin, but their code only loads the first plugin which can be loaded without throwing an exception and then breaks out of the loop. There's no Javadoc to tell me which is correct.
          • 2. Re: PluginManager implementation
            843798
            Thanks,

            you mean the second method, didn't get to use it...
            just copied it from the first one with over looking this issue...

            you are right... has been fixed...

            Edited by: Adam-Z. on Feb 8, 2010 1:02

            I was also wondering, what do you guys think about the design? is there a better way to design this thing OOD structurally speaking? are there things I didn't take under consideration or that I'm not aware of?

            Edited by: Adam-Z. on Feb 8, 2010 1:05 PM
            • 3. Re: PluginManager implementation
              791266
              Adam-Z. wrote:
              are there things I didn't take under consideration or that I'm not aware of?
              Yes, the fact that you don't need to write the code at all since there are lost of other implementations?
              • 4. Re: PluginManager implementation
                843798
                well yes state the obvious fact, I could have not come up with... really, could it just be I love to implement objects such as this, and this is in fact the first time I let a group of people, that I think highly of, to look in my code and to actually receive a constructive criticism for any future designs I might do!?

                Thanks though, at least your honest.
                • 5. Re: PluginManager implementation
                  791266
                  Adam-Z. wrote:
                  well yes state the obvious fact, I could have not come up with... really, could it just be I love to implement objects such as this, and this is in fact the first time I let a group of people, that I think highly of, to look in my code and to actually receive a constructive criticism for any future designs I might do!?
                  That's good, but you should in that case specify a context. How do you want the plugins to be used? What is a plugin? Why have you made the decisions that you have made.
                  • 6. Re: PluginManager implementation
                    843798
                    Thank you for your reply,
                    let me start by saying, that that was the first time I handled dynamic class loading and ClassLoader object, I learned this on the same day I wrote this object, yesterday, when I got the time to look at the code and actually try to use it, I found that my implementation was actually lame, and I have changed most of it, the concept of loading a class and instantiate an object with a string, is brain storming, that is a whole new approach I had to get my head around, so I have and I implemented the same PluginManager design totally different.

                    Thank you for your help.
                    Will add the comments you suggested.
                    • 7. Re: PluginManager implementation
                      DrClap
                      Just looking at the code from a superficial viewpoint, it looks like competently-written code to me. (Quite often when people have a brilliant idea, you find out their brilliant idea involves holding the shovel by its blade and trying to dig with the handle, and they have a marvellous technique to make this work, although not very well, and they're looking for help with that. Yours doesn't seem to fall into that category.)

                      As for whether it's going to do what it's supposed to do, or whether it's going to screw up by loading classes repeatedly when it should only be loading them once or something like that, I can't tell from a brief inspection. I would say the fact that you haven't had many responses indicates that there isn't much to criticize there.
                      • 8. Re: PluginManager implementation
                        jschellSomeoneStoleMyAlias
                        Add javadocs. Classes and public/protected methods should all have them.
                        return (Type) Class.forName(s).newInstance();
                        You have a very generic plugin system. Being so general this particular creation idiom (no passed data) probably won't work for many cases.

                        Also you can improve this by verifying the class inplements the Type before attempting the create and cast.

                        public void isOfType(JarFile jar) throws IOException
                        Took me a while to figure out what this is.
                        I don't think it is needed. The caller is responsible for providing the jar anyways so there isn't much point is requiring an attribute that isn't part of the code.

                        If you were expecting (your code) to go through a generic directory with a bunch of jars and find only those that were plugins then maybe. But in that case I would just insist on a directory specific for plugins.
                        • 9. Re: PluginManager implementation
                          843798
                          Thank you both so much for your replies.
                          I added Java docs to my new implementation... and I would post it for all to use as soon as I would be 100% sure I'm done with editing it.
                          Thanks:)
                          public void isOfType(JarFile jar) throws IOException
                          Took me a while to figure out what this is.
                          I don't think it is needed. The caller is responsible for providing the jar anyways so there isn't much point is requiring an attribute that isn't part of the code.
                          If you were expecting (your code) to go through a generic directory with a bunch of jars and find only those that were plugins then maybe. But in that case I would just insist on a directory specific for plugins.
                          well yes I have a folder with a bunch of Jars in it, I was not sure about my design implementation, by that I mean how far am I going to take this, so I generalized it to the point that the Jar file manifest must have a PluginType attribute to specify the type of the plugin and before loading it the PluginLoader verifies that the Jar is a plugin of the same type as the loader, it's like a fail safe for any stupid thing I might do.
                          Also you can improve this by verifying the class inplements the Type before attempting the create and cast.
                          I think I got this covered since each plugin that is instantiated, creates a manifest for itself with all the data it needs to load, and saves it in the folder with all the .java files that later I compile into a jar.
                          But in that case I would just insist on a directory specific for plugins.
                          I keep this in mind as I go along, and so far there is only one type of plugin in each directory, I'm just not sure what the future holds for me yet...


                          There is one issue though I was trying to get my head around a the problem is that I cannot load the class by the className via the ClassLoader. it's either the url that I add is not added to the build path, or that the search path for the class ignores the url I've supplied.
                               public static class PluginClassLoader extends URLClassLoader {
                                    public PluginClassLoader() {
                                         super(new URL[]{}, PluginClassLoader.class.getClassLoader());
                                    }
                                    
                                    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException { // cannot find class
                                         return findClass(className);
                                    }
                                    
                                    public synchronized void add(JarFile jar) {
                                         try {
                                              addURL(new URL("jar:file:/" + jar.getName() + "!/"));   // IN THIS LINE
                                         } catch (MalformedURLException e) {
                                              System.out.println("Add here you exception details");
                                              e.printStackTrace();
                                         }
                                    }
                               }
                          So far I was testing my plugins in eclipse, and all of the files where in the same project, so when I was loading a plugin class, it was actually loading it from the application build path, and not from the plugin jar file, yesterday once I moved the loading application to a different project, in order to make it a runnable Jar, I found out that it does not find the classes, I get a ClassNotFoundException, although they are exactly where they suppose to be, I was looking at the code and debugging for few hours, until I realized that the only explanation, is that the PluginClassloader cannot find the given class...
                          whether if it because I don't call the right method to get the class, should I use the ClassLoader loadClass(String className) method or any other one?

                          I'm new to this, I have no clue what is wrong here, and I hope that one of you guys can spot this error in a sec,
                          file: "file:/D:/%Important Documents/WorkSpaces/PacMan/ApplicationManager/Plug-in/DataChunkGenerator.jar!/"
                          protocol: "jar"
                          the path is accurate and the class name is correct and it is in the Jar, and it is public.

                          Thanks in advance,
                          Adam Zehavi.

                          Edited by: Adam-Z. on Feb 14, 2010 2:19 PM
                          • 10. Re: PluginManager implementation
                            EJP
                            addURL(new URL("jar:file:/" + jar.getName() + "!/")); // IN THIS LINE
                            addURL(new URL("file:/" + jar.getName()));
                            • 11. Re: PluginManager implementation
                              843798
                              Thanks my friend, but no, that is not it... I gave it a shot, I thought it might have to do with this:


                              java.net.URLClassLoader

                              This class loader is used to load classes and resources from a search path of URLs referring to both JAR files and directories. Any URL that ends with a '/' is assumed to refer to a directory. Otherwise, the URL is assumed to refer to a JAR file which will be opened as needed.

                              The AccessControlContext of the thread that created the instance of URLClassLoader will be used when subsequently loading classes and resources.

                              The classes that are loaded are by default granted permission only to access the URLs specified when the URLClassLoader was created.



                              Since:
                              1.2

                              --------------------------------------------------------------------------------

                              so I've tried to create new instance of the PluginClassLoader(URLs) and still negative.

                              Thanks though,
                              Adam.