001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: TemplateClassLoader.java 3811 2007-06-25 15:06:16Z gbevin $
007: */
008: package com.uwyn.rife.template;
009:
010: import com.tc.object.loaders.BytecodeProvider;
011: import com.tc.object.loaders.NamedClassLoader;
012: import com.uwyn.rife.config.RifeConfig;
013: import com.uwyn.rife.resources.ResourceFinder;
014: import com.uwyn.rife.template.exceptions.TemplateException;
015: import com.uwyn.rife.tools.FileUtils;
016: import com.uwyn.rife.tools.exceptions.FileUtilsErrorException;
017: import java.io.File;
018: import java.lang.reflect.InvocationTargetException;
019: import java.lang.reflect.Method;
020: import java.net.URL;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: class TemplateClassLoader extends ClassLoader implements
025: NamedClassLoader, BytecodeProvider {
026: private TemplateFactory mTemplateFactory = null;
027:
028: // member vars required to support Terracotta
029: private Map<String, byte[]> mBytecodeRepository = null;
030: private String mClassLoaderName = "RIFE:TemplateClassLoader";
031:
032: TemplateClassLoader(TemplateFactory templateFactory,
033: ClassLoader initiating) {
034: super (initiating);
035:
036: assert templateFactory != null;
037: assert initiating != null;
038:
039: // check for the presence of Terracotta by loading its ClassProcessorHelper class
040: try {
041: Class classprocessor_helper_class = Class
042: .forName("com.tc.object.bytecode.hook.impl.ClassProcessorHelper");
043: if (classprocessor_helper_class != null) {
044: mBytecodeRepository = new HashMap<String, byte[]>();
045:
046: if (initiating instanceof NamedClassLoader) {
047: NamedClassLoader namedParent = (NamedClassLoader) initiating;
048: mClassLoaderName = "Rife:Template:"
049: + namedParent.__tc_getClassLoaderName();
050:
051: try {
052: Method method = classprocessor_helper_class
053: .getDeclaredMethod(
054: "registerGlobalLoader",
055: new Class[] { NamedClassLoader.class });
056: method.invoke(null, new Object[] { this });
057: } catch (Exception e) {
058: throw new RuntimeException(
059: "Unable to register the template classloader '"
060: + mClassLoaderName
061: + "' with Terracotta.", e);
062: }
063: }
064: }
065: } catch (ClassNotFoundException e) {
066: // this is OK, Terracotta is simply not present in the classpath
067: }
068:
069: mTemplateFactory = templateFactory;
070: }
071:
072: public byte[] __tc_getBytecodeForClass(final String className) {
073: if (null == mBytecodeRepository) {
074: return null;
075: }
076:
077: synchronized (mBytecodeRepository) {
078: return mBytecodeRepository
079: .get(constructBytecodeRepositoryKey(className));
080: }
081: }
082:
083: private String constructBytecodeRepositoryKey(String className) {
084: return mClassLoaderName + '#' + className;
085: }
086:
087: public void __tc_setClassLoaderName(String name) {
088: throw new UnsupportedOperationException(
089: "class loader name can not be modified for loader with name: "
090: + mClassLoaderName);
091: }
092:
093: public String __tc_getClassLoaderName() {
094: return mClassLoaderName;
095: }
096:
097: protected Class loadClass(String classname, boolean resolve,
098: String encoding, TemplateTransformer transformer)
099: throws ClassNotFoundException {
100: assert classname != null;
101:
102: // see if this classloader has cached the class with the provided name
103: Class c = findLoadedClass(classname);
104:
105: // if an already loaded version was found, check whether it's outdated or not
106: if (c != null) {
107: // if an already loaded version was found, check whether it's outdated or not
108: // this can only be Template classes since those are the only ones that are
109: // handled by this classloader
110: if (RifeConfig.Template.getAutoReload()) {
111: // if the template was modified, don't use the cached class
112: // otherwise, just take the previous template class
113: if (isTemplateModified(c, transformer)) {
114: TemplateClassLoader new_classloader = new TemplateClassLoader(
115: mTemplateFactory, this .getParent());
116: // register the new classloader as the default templatefactory's
117: // classloader
118: mTemplateFactory.setClassLoader(new_classloader);
119: return new_classloader.loadClass(classname,
120: resolve, encoding, transformer);
121: }
122: }
123: }
124: // try to obtain the class in another way
125: else {
126: // try to obtain it from the parent classloader or from the system classloader
127: ClassLoader parent = getParent();
128: if (parent != null) {
129: try {
130: // the parent is never a TemplateClassLoader, it's always the
131: // class that instantiated the initial TemplateClassLoader
132: // thus, the encoding doesn't need to be passed on further
133: c = parent.loadClass(classname);
134:
135: // if templates are reloaded automatically, check if a corresponding
136: // class was found in the parent classloader. If that class came from a jar
137: // file, make sure it's returned immediately and don't try to recompile it
138: if (c != null
139: && RifeConfig.Template.getAutoReload()) {
140: URL resource = parent.getResource(classname
141: .replace('.', '/')
142: + ".class");
143: if (resource != null
144: && resource.getPath().indexOf('!') != -1) {
145: // resolve the class if it's needed
146: if (resolve) {
147: resolveClass(c);
148: }
149:
150: return c;
151: }
152: }
153: } catch (ClassNotFoundException e) {
154: c = null;
155: }
156: }
157:
158: if (null == c) {
159: try {
160: c = findSystemClass(classname);
161: } catch (ClassNotFoundException e) {
162: c = null;
163: }
164: }
165:
166: // load template class files in class path
167: if (c != null && !classname.startsWith("java.")
168: && !classname.startsWith("javax.")
169: && !classname.startsWith("sun.")
170: && Template.class.isAssignableFrom(c)) {
171: // verify if the template in the classpath has been updated
172: if (RifeConfig.Template.getAutoReload()
173: && isTemplateModified(c, transformer)) {
174: c = null;
175: }
176: }
177:
178: if (null == c) {
179: // intern the classname to get a synchronization lock monitor
180: // that is specific for the current class that is loaded and
181: // that will not lock up the classloading of all other classes
182: // by for instance synchronizing on this classloader
183: classname = classname.intern();
184: synchronized (classname) {
185: // make sure that the class has not been defined in the
186: // meantime, otherwise defining it again will trigger an
187: // exception;
188: // reuse the existing class if it has already been defined
189: c = findLoadedClass(classname);
190: if (null == c) {
191: byte[] raw = compileTemplate(classname,
192: encoding, transformer);
193:
194: // only use the bytecode repository when is has been initialized because Terracotta is available
195: if (mBytecodeRepository != null) {
196: synchronized (mBytecodeRepository) {
197: String key = constructBytecodeRepositoryKey(classname);
198: mBytecodeRepository.put(key, raw);
199: }
200: }
201:
202: // define the bytes of the class for this classloader
203: c = defineClass(classname, raw, 0, raw.length);
204: }
205: }
206: }
207: }
208:
209: // resolve the class if it's needed
210: if (resolve) {
211: resolveClass(c);
212: }
213:
214: assert c != null;
215:
216: return c;
217: }
218:
219: private byte[] compileTemplate(String classname, String encoding,
220: TemplateTransformer transformer)
221: throws ClassNotFoundException {
222: assert classname != null;
223:
224: // try to resolve the classname as a template name by resolving the template
225: URL template_url = mTemplateFactory.getParser().resolve(
226: classname);
227: if (null == template_url) {
228: throw new ClassNotFoundException(
229: "Couldn't resolve template: '" + classname + "'.");
230: }
231:
232: // prepare the template with all the information that's needed to be able to identify
233: // this template uniquely
234: Parsed template_parsed = mTemplateFactory.getParser().prepare(
235: classname, template_url);
236:
237: // parse the template
238: try {
239: mTemplateFactory.getParser().parse(template_parsed,
240: encoding, transformer);
241: } catch (TemplateException e) {
242: throw new ClassNotFoundException(
243: "Error while parsing template: '" + classname
244: + "'.", e);
245: }
246:
247: byte[] byte_code = template_parsed.getByteCode();
248: if (RifeConfig.Template.getGenerateClasses()) {
249: // get the package and the short classname of the template
250: String template_package = template_parsed.getPackage();
251: template_package = template_package.replace('.',
252: File.separatorChar);
253: String template_classname = template_parsed.getClassName();
254:
255: // setup everything to perform the conversion of the template to java sources
256: // and to compile it into a java class
257: String generation_path = RifeConfig.Template
258: .getGenerationPath()
259: + File.separatorChar;
260: String packagedir = generation_path + template_package;
261: String filename_class = packagedir + File.separator
262: + template_classname + ".class";
263: File file_packagedir = new File(packagedir);
264: File file_class = new File(filename_class);
265:
266: // prepare the package directory
267: if (!file_packagedir.exists()) {
268: if (!file_packagedir.mkdirs()) {
269: throw new ClassNotFoundException(
270: "Couldn't create the template package directory : '"
271: + packagedir + "'.");
272: }
273: } else if (!file_packagedir.isDirectory()) {
274: throw new ClassNotFoundException(
275: "The template package directory '" + packagedir
276: + "' exists but is not a directory.");
277: } else if (!file_packagedir.canWrite()) {
278: throw new ClassNotFoundException(
279: "The template package directory '" + packagedir
280: + "' is not writable.");
281: }
282:
283: try {
284: FileUtils.writeBytes(byte_code, file_class);
285: } catch (FileUtilsErrorException e) {
286: throw new ClassNotFoundException(
287: "Error while writing the contents of the template class file '"
288: + classname + "'.", e);
289: }
290: }
291: return byte_code;
292: }
293:
294: private boolean isTemplateModified(Class c,
295: TemplateTransformer transformer) {
296: assert c != null;
297:
298: boolean is_modified = true;
299: Method is_modified_method = null;
300: String modification_state = null;
301: if (transformer != null) {
302: modification_state = transformer.getState();
303: }
304: try {
305: is_modified_method = c.getMethod("isModified", new Class[] {
306: ResourceFinder.class, String.class });
307: is_modified = (Boolean) is_modified_method.invoke(null,
308: new Object[] {
309: mTemplateFactory.getResourceFinder(),
310: modification_state });
311: } catch (NoSuchMethodException e) {
312: // do nothing, template will be considered as outdated
313: } catch (SecurityException e) {
314: // do nothing, template will be considered as outdated
315: } catch (IllegalAccessException e) {
316: // do nothing, template will be considered as outdated
317: } catch (IllegalArgumentException e) {
318: // do nothing, template will be considered as outdated
319: } catch (InvocationTargetException e) {
320: // do nothing, template will be considered as outdated
321: }
322:
323: return is_modified;
324: }
325: }
|