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 }