001    // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002    // The Molly framework is freely distributable under the terms of an
003    // MIT-style license. For details, see the molly pages web site at:
004    // http://www.mollypages.org/. Use, modify, have fun !
005    
006    package fc.web.page;
007    
008    import java.util.*;
009    import java.io.*;
010    
011    import fc.io.*;
012    import fc.util.*;
013    
014    /**
015    A classloader that loads molly server pages. Each page is loaded by it's own 
016    separate ClassLoader.
017    <p>
018    If a page changes on disk (this is tracked by {@link PageMgr}), then the
019    new page is loaded by a new instance of this class. Loaded pages are
020    casted to the non-reloadable {@link Page} interface. This Page interface 
021    itself is loaded by a parent (typically JVM/system) classloader.
022    
023    @author hursh jain
024    */
025    public class PageClassLoader extends ClassLoader 
026    {
027    /*
028    Implementation note:
029    1) 
030    We can use the decorator pattern (as in java.io.*), define a base
031    classloader interface and then various implentations such as
032    reloadingclassloader, statistics_gathering_classloader etc, and various
033    implementations can then be chained:
034    
035    classloader foo =  new reloadingclassloader( 
036              new statclassloader (
037                new ...)));
038    
039    Alternately, I can simply fold all that functionality in this one
040    classloader. Since we know upfront what we need, I think this latter
041    approach will provide less clutter and is hence taken.
042    
043    2)
044    We don't need to use definePackage for our classloaders,
045    there are no manifest based versions associated with pages.
046    
047    3)
048    Say a java file in package foo.bar exists in: 
049      /mydir/foo/bar/baz.java
050    
051    Then running "javac -classpath /mydir foo.bar.baz.java" will 
052    result in: 
053      /mydir/foo/bar/baz.class
054    
055    Now say: 
056      cd /mydir/foo/bar/
057      java -classpath "." foo.bar.baz
058      
059    This will cause an error since "java" actually checks to
060    see that the classpath it _used_ is the same as the classpath
061    in the file it found. (in this example, java used "." but found
062    foo.bar.baz.class).
063    
064    However, we can read a byte[] corresponding to a .class file
065    from anywhere on the disk, network, database and this has 
066    (typically for custom loaders) nothing to do with the classpath.
067    
068    Therefore, when implenting a custom classloader, we can
069    maintain a directory structure for generated class files OR
070    put all generated class files in one directory, as say
071    /mydir/foo_bar_baz.class etc OR do both.
072    
073    GENERATED MOLLY CLASSES ARE ALL IN THE SAME PACKAGE (molly.pages) AND
074    ARE ORGANIZED IN A DIRECTORY STRUCTURE THAT MIRRORS THE SOURCE
075    HEIRARCHY. THIS MAKES GENERATED .java PAGES (CORRESPONDING TO THE SOURCE
076    .mp) EASIER TO FIND FOR HUMANS.
077    
078    4. Instead of simply implementing findClass, we _could_ also 
079    override loadClass(). This allows us to warn/error out if
080    the parent classloader was able to load the page's class
081    file (which should never happen since the classpath containing
082    the page's .class files should NOT be in the system classpath).
083    If it IS in the system classpath, page re-loading will not
084    work, which is an error for our purposes. 
085    
086    We still need to delegate to a parent classloader for non-page
087    classes referenced from a page. To simplify things, I've used
088    the delegation model here and will give the parent classloader
089    first crack at loading everything....this is the way it should
090    always be anyway.
091    */
092    
093    private static final boolean dbg = false;
094    Log   log;
095    
096    /**
097    @param  scratch     absolute path to the scratch dir
098    @param  log       logging destination.
099    */
100    public PageClassLoader(Log log) 
101      {
102      this.log = log;
103      }
104    
105    /**
106    Calls {@link PageClassLoader} with the logger set to:
107    <tt>fc.web.page.PageClassLoader</tt>
108    */
109    public PageClassLoader() 
110      {
111      this.log = Log.getDefault();
112      }
113    
114    Map class_file_paths = new HashMap();
115    
116    /**
117    Loads a class corresponding to a page (i.e., a name that starts
118    with {@link Page.PackageName} from. Delegates the loading of all 
119    other classes to the parent classloader (typically the system
120    classloader).
121    <p>
122    If the page suffix is <tt>.mp</tt>, then a name such as
123    <tt>foo/bar/baz.page</tt> is loaded from the scratch dir (from
124    <tt><scratchdir>/foo/bar/baz.class</tt>).
125    
126    @param  name  a relative page path-name (relative to the page 
127            root directory), for example <tt>foo/bar/my.page</tt>
128            or <tt>./my.page</tt>
129    */
130    public Class loadClass(String name, boolean resolve) throws ClassNotFoundException
131      {
132      if (dbg) log.bug("enter loadClass(): name=", name, " resolve=", resolve);
133      
134      //we could implement findClass() instead of loadClass
135      if (name.startsWith(Page.PACKAGE_NAME))
136        {
137        Class c = findLoadedClass(name);
138        if (c != null)
139          return c ;
140          
141        if (dbg) log.bug("Name starts with:[", Page.PACKAGE_NAME, "], loading page: ", name);
142        final int pkgnamelen =  Page.PACKAGE_NAME.length() + 1; //"foo.pkg."
143      
144        name = name.substring(pkgnamelen, name.length());
145      
146        if (dbg) System.out.println("[A] name=" + name);
147        //for inner, anonymous classes
148        //1) pagemgr first says: load  "molly.page.foo.class"  
149        //2) foo.mp contains a anonymous inner class foo.mp$1
150        //3) this method is called again with "molly.page.foo.mp$1" 
151        //   as the argument
152        //4) we need to load "foo.mp$1.class"
153      
154        if (! name.endsWith(".class")) 
155          {
156          if (dbg) System.out.println("[B]");
157          String outer_class_name = name.substring(0, name.indexOf("$"));
158          if (dbg) System.out.println("[C] outer=" + outer_class_name);
159          String path = (String) class_file_paths.get(outer_class_name);
160          if (dbg) System.out.println("[D] path=" + path);
161          if (path == null) {
162            throw new ClassNotFoundException("Internal error: cannot load anonymous/inner classe: " + name);
163            } 
164      
165          name = path + name + ".class";
166          if (dbg) log.warn("anonname=",name);
167          }
168        else{   
169          if (dbg) System.out.println("[E]");
170          String dirname = StringUtil.dirName(name); //has ending '/'
171          String filename = StringUtil.fileName(name);
172          filename = filename.substring(0, filename.indexOf(".class"));
173          class_file_paths.put(filename, dirname);
174          if (dbg) log.bug("name=",name);
175          if (dbg) log.bug("dirname=",dirname);
176          }
177          
178        c  = readClassFromDisk(new File(name));
179        
180        if (resolve) {
181          resolveClass(c);       
182          }
183        return c;
184        }
185        
186      if (dbg) log.bug("Delegating to thread-context classloader to load class: ", name);
187      //we cannot use the parent classloader because it will not have
188      //classes inside of WEB-INF (the parent classloader is a jvm level
189      //classloader). We need to use the servlet containers classloader
190      //instead.
191      //return super.loadClass(name, resolve);
192      return Thread.currentThread().getContextClassLoader().loadClass(name);
193      }
194    
195    Class readClassFromDisk(File classfile) throws ClassNotFoundException
196      {
197      Argcheck.istrue(classfile.exists(), "The specified file does not exist: " + classfile.getAbsolutePath());
198      
199      byte[] buf = null;
200      
201      try {   
202        if (dbg) log.bug("Using filename:", classfile);
203        
204        buf = IOUtil.fileToByteArray(classfile);
205        
206        if (buf == null) {
207          throw new ClassNotFoundException(classfile.getPath());
208          }
209          
210        } 
211      catch (Exception e) {
212        throw new ClassNotFoundException(classfile.getPath());
213        }
214      
215      /* 
216      definePackage(Page.PACKAGE_NAME, null, null, null, null, null, null, null);
217    
218      this works ONCE per instance of the classloader -- will crap out
219      if we use the same instance to define more than 1 class. defining
220      a package is not needed at all, but may be nice to have for logging
221      purposes/setting logging levels. If we have an inner class, this
222      method will be invoked more than once (one per inner class which
223      uses this same classloader). 
224      
225      All of this is very obscure and if this causes problems, remove.
226      */
227      if (getPackage(Page.PACKAGE_NAME) == null)
228        definePackage(Page.PACKAGE_NAME, null, null, null, null, null, null, null);
229    
230      Class c = defineClass(null, buf, 0, buf.length);
231      
232      return c;
233      }
234    
235    public static void main (String args[])  throws Exception
236      {
237      Args myargs = new Args(args);
238      myargs.setUsage("java " + ClassUtil.getClassName() + 
239        " -file full-path-to-class-file");
240      String page = Page.PACKAGE_NAME + "." + myargs.getRequired("file");
241      
242      PageClassLoader cl  = new PageClassLoader();
243      cl.log.setLevel("debug");
244      
245      System.out.println("Loading java.lang.object: ");
246      Class c = cl.loadClass("java.lang.Object");
247      System.out.println(ClassUtil.getClassLoaderInfo(c));
248    
249      System.out.println("Loading " + page);  
250      c = cl.loadClass(page);
251      System.out.println(ClassUtil.getClassLoaderInfo(c));
252      System.out.println("Package for this class: " + c.getPackage());
253      }
254    
255    }