/*
* Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com)
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: TemplateClassLoader.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.template;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import com.tc.object.loaders.BytecodeProvider;
import com.uwyn.rife.config.RifeConfig;
import com.uwyn.rife.resources.ResourceFinder;
import com.uwyn.rife.template.exceptions.TemplateException;
import com.uwyn.rife.tools.FileUtils;
import com.uwyn.rife.tools.exceptions.FileUtilsErrorException;
class TemplateClassLoader extends ClassLoader implements BytecodeProvider
{
private TemplateFactory mTemplateFactory = null;
// member vars required to support Terracotta
private Map<String, byte[]> mBytecodeRepository = null;
private String mClassLoaderName = "RIFE:TemplateClassLoader";
TemplateClassLoader(TemplateFactory templateFactory, ClassLoader initiating)
{
super(initiating);
assert templateFactory != null;
assert initiating != null;
// check for the presence of Terracotta by loading its ClassProcessorHelper class
try
{
Class classprocessorhelper_class = Class.forName("com.tc.object.bytecode.hook.impl.ClassProcessorHelper");
Class namedclassloader_class = Class.forName("com.tc.object.loaders.NamedClassLoader");
if (classprocessorhelper_class != null &&
namedclassloader_class != null)
{
mBytecodeRepository = new HashMap<String, byte[]>();
if (namedclassloader_class.isAssignableFrom(initiating.getClass()))
{
try
{
Method getclassloadername_method = namedclassloader_class.getDeclaredMethod("__tc_getClassLoaderName", new Class[0]);
mClassLoaderName = "Rife:Template:" + getclassloadername_method.invoke(initiating, new Object[0]);
Method method = classprocessorhelper_class.getDeclaredMethod("registerGlobalLoader", new Class[] {namedclassloader_class});
method.invoke(null, new Object[] {this});
}
catch (Exception e)
{
throw new RuntimeException("Unable to register the template classloader '"+mClassLoaderName+"' with Terracotta.", e);
}
}
}
}
catch (ClassNotFoundException e)
{
// this is OK, Terracotta is simply not present in the classpath
}
mTemplateFactory = templateFactory;
}
public byte[] __tc_getBytecodeForClass(final String className)
{
if (null == mBytecodeRepository)
{
return null;
}
synchronized (mBytecodeRepository)
{
return mBytecodeRepository.get(constructBytecodeRepositoryKey(className));
}
}
private String constructBytecodeRepositoryKey(String className)
{
return mClassLoaderName + '#' + className;
}
public void __tc_setClassLoaderName(String name)
{
throw new UnsupportedOperationException("class loader name can not be modified for loader with name: " + mClassLoaderName);
}
public String __tc_getClassLoaderName()
{
return mClassLoaderName;
}
protected Class loadClass(String classname, boolean resolve, String encoding, TemplateTransformer transformer)
throws ClassNotFoundException
{
assert classname != null;
// see if this classloader has cached the class with the provided name
Class c = findLoadedClass(classname);
// if an already loaded version was found, check whether it's outdated or not
if (c != null)
{
// if an already loaded version was found, check whether it's outdated or not
// this can only be Template classes since those are the only ones that are
// handled by this classloader
if (RifeConfig.Template.getAutoReload())
{
// if the template was modified, don't use the cached class
// otherwise, just take the previous template class
if (isTemplateModified(c, transformer))
{
TemplateClassLoader new_classloader = new TemplateClassLoader(mTemplateFactory, this.getParent());
// register the new classloader as the default templatefactory's
// classloader
mTemplateFactory.setClassLoader(new_classloader);
return new_classloader.loadClass(classname, resolve, encoding, transformer);
}
}
}
// try to obtain the class in another way
else
{
// try to obtain it from the parent classloader or from the system classloader
ClassLoader parent = getParent();
if (parent != null)
{
try
{
// the parent is never a TemplateClassLoader, it's always the
// class that instantiated the initial TemplateClassLoader
// thus, the encoding doesn't need to be passed on further
c = parent.loadClass(classname);
// if templates are reloaded automatically, check if a corresponding
// class was found in the parent classloader. If that class came from a jar
// file, make sure it's returned immediately and don't try to recompile it
if (c != null &&
RifeConfig.Template.getAutoReload())
{
URL resource = parent.getResource(classname.replace('.', '/')+".class");
if (resource != null &&
resource.getPath().indexOf('!') != -1)
{
// resolve the class if it's needed
if (resolve)
{
resolveClass(c);
}
return c;
}
}
}
catch (ClassNotFoundException e)
{
c = null;
}
}
if (null == c)
{
try
{
c = findSystemClass(classname);
}
catch (ClassNotFoundException e)
{
c = null;
}
}
// load template class files in class path
if (c != null &&
!classname.startsWith("java.") &&
!classname.startsWith("javax.") &&
!classname.startsWith("sun.") &&
Template.class.isAssignableFrom(c))
{
// verify if the template in the classpath has been updated
if (RifeConfig.Template.getAutoReload() &&
isTemplateModified(c, transformer))
{
c = null;
}
}
if (null == c)
{
// intern the classname to get a synchronization lock monitor
// that is specific for the current class that is loaded and
// that will not lock up the classloading of all other classes
// by for instance synchronizing on this classloader
classname = classname.intern();
synchronized (classname)
{
// make sure that the class has not been defined in the
// meantime, otherwise defining it again will trigger an
// exception;
// reuse the existing class if it has already been defined
c = findLoadedClass(classname);
if (null == c)
{
byte[] raw = compileTemplate(classname, encoding, transformer);
// only use the bytecode repository when is has been initialized because Terracotta is available
if (mBytecodeRepository != null)
{
synchronized (mBytecodeRepository)
{
String key = constructBytecodeRepositoryKey(classname);
mBytecodeRepository.put(key, raw);
}
}
// define the bytes of the class for this classloader
c = defineClass(classname, raw, 0, raw.length);
}
}
}
}
// resolve the class if it's needed
if (resolve)
{
resolveClass(c);
}
assert c != null;
return c;
}
private byte[] compileTemplate(String classname, String encoding, TemplateTransformer transformer)
throws ClassNotFoundException
{
assert classname != null;
// try to resolve the classname as a template name by resolving the template
URL template_url = mTemplateFactory.getParser().resolve(classname);
if (null == template_url)
{
throw new ClassNotFoundException("Couldn't resolve template: '"+classname+"'.");
}
// prepare the template with all the information that's needed to be able to identify
// this template uniquely
Parsed template_parsed = mTemplateFactory.getParser().prepare(classname, template_url);
// parse the template
try
{
mTemplateFactory.getParser().parse(template_parsed, encoding, transformer);
}
catch (TemplateException e)
{
throw new ClassNotFoundException("Error while parsing template: '"+classname+"'.", e);
}
byte[] byte_code = template_parsed.getByteCode();
if (RifeConfig.Template.getGenerateClasses())
{
// get the package and the short classname of the template
String template_package = template_parsed.getPackage();
template_package = template_package.replace('.', File.separatorChar);
String template_classname = template_parsed.getClassName();
// setup everything to perform the conversion of the template to java sources
// and to compile it into a java class
String generation_path = RifeConfig.Template.getGenerationPath()+File.separatorChar;
String packagedir = generation_path+template_package;
String filename_class = packagedir+File.separator+template_classname+".class";
File file_packagedir = new File(packagedir);
File file_class = new File(filename_class);
// prepare the package directory
if (!file_packagedir.exists())
{
if (!file_packagedir.mkdirs())
{
throw new ClassNotFoundException("Couldn't create the template package directory : '"+packagedir+"'.");
}
}
else if (!file_packagedir.isDirectory())
{
throw new ClassNotFoundException("The template package directory '"+packagedir+"' exists but is not a directory.");
}
else if (!file_packagedir.canWrite())
{
throw new ClassNotFoundException("The template package directory '"+packagedir+"' is not writable.");
}
try
{
FileUtils.writeBytes(byte_code, file_class);
}
catch (FileUtilsErrorException e)
{
throw new ClassNotFoundException("Error while writing the contents of the template class file '"+classname+"'.", e);
}
}
return byte_code;
}
private boolean isTemplateModified(Class c, TemplateTransformer transformer)
{
assert c != null;
boolean is_modified = true;
Method is_modified_method = null;
String modification_state = null;
if (transformer != null)
{
modification_state = transformer.getState();
}
try
{
is_modified_method = c.getMethod("isModified", new Class[] {ResourceFinder.class, String.class});
is_modified = (Boolean)is_modified_method.invoke(null, new Object[] {mTemplateFactory.getResourceFinder(), modification_state});
}
catch (NoSuchMethodException e)
{
// do nothing, template will be considered as outdated
}
catch (SecurityException e)
{
// do nothing, template will be considered as outdated
}
catch (IllegalAccessException e)
{
// do nothing, template will be considered as outdated
}
catch (IllegalArgumentException e)
{
// do nothing, template will be considered as outdated
}
catch (InvocationTargetException e)
{
// do nothing, template will be considered as outdated
}
return is_modified;
}
}