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