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 scratch absolute path to the scratch dir 098@param log logging destination. 099*/ 100public PageClassLoader(Log log) 101 { 102 this.log = log; 103 } 104 105/** 106Calls {@link PageClassLoader} with the logger set to: 107<tt>fc.web.page.PageClassLoader</tt> 108*/ 109public PageClassLoader() 110 { 111 this.log = Log.getDefault(); 112 } 113 114Map class_file_paths = new HashMap(); 115 116/** 117Loads a class corresponding to a page (i.e., a name that starts 118with {@link Page.PackageName} from. Delegates the loading of all 119other classes to the parent classloader (typically the system 120classloader). 121<p> 122If 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*/ 130public 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 195Class 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 235public 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}