blob: 9781e36d155f3fad2a5571cca76db395ce2f7056 [file] [log] [blame]
// Copyright 2010 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.ioc.internal;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Set;
import javassist.CannotCompileException;
import javassist.ClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.Loader;
import javassist.LoaderClassPath;
import javassist.NotFoundException;
import javassist.Translator;
import javassist.expr.ConstructorCall;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import javassist.expr.MethodCall;
import javassist.expr.NewExpr;
import org.apache.tapestry5.ioc.Invokable;
import org.apache.tapestry5.ioc.ObjectCreator;
import org.apache.tapestry5.ioc.OperationTracker;
import org.apache.tapestry5.ioc.ReloadAware;
import org.apache.tapestry5.ioc.internal.services.ClassFactoryClassPool;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.internal.util.URLChangeTracker;
import org.apache.tapestry5.ioc.services.ClassFabUtils;
import org.apache.tapestry5.services.UpdateListener;
import org.slf4j.Logger;
@SuppressWarnings("all")
public abstract class AbstractReloadableObjectCreator implements ObjectCreator, UpdateListener, Translator
{
private class InternalLoader extends Loader
{
public InternalLoader(ClassLoader parent, ClassPool pool)
{
super(parent, pool);
}
@Override
protected Class findClass(String name) throws ClassNotFoundException
{
if (shouldLoadClassNamed(name))
return super.findClass(name);
return null; // Force delegation to parent class loader
}
}
private final ClassLoader baseClassLoader;
private final String implementationClassName;
private final String classFilePath;
private final Logger logger;
private final OperationTracker tracker;
private final URLChangeTracker changeTracker = new URLChangeTracker();
/**
* The set of class names that should be loaded by the class loader. This is necessary to support
* reloading the class when a base class changes, and to properly support access to protected methods.
*/
private final Set<String> classesToLoad = CollectionFactory.newSet();
private Object instance;
private boolean firstTime = true;
protected AbstractReloadableObjectCreator(ClassLoader baseClassLoader, String implementationClassName,
Logger logger, OperationTracker tracker)
{
this.baseClassLoader = baseClassLoader;
this.implementationClassName = implementationClassName;
this.logger = logger;
this.tracker = tracker;
classFilePath = ClassFabUtils.getPathForClassNamed(implementationClassName);
}
public synchronized void checkForUpdates()
{
if (instance == null)
return;
if (!changeTracker.containsChanges())
return;
if (logger.isDebugEnabled())
logger.debug(String.format("Implementation class %s has changed and will be reloaded on next use.",
implementationClassName));
changeTracker.clear();
boolean reloadNow = informInstanceOfReload();
instance = reloadNow ? createInstance() : null;
}
private boolean informInstanceOfReload()
{
if (instance instanceof ReloadAware)
{
ReloadAware ra = (ReloadAware) instance;
return ra.shutdownImplementationForReload();
}
return false;
}
public synchronized Object createObject()
{
if (instance == null)
instance = createInstance();
return instance;
}
private Object createInstance()
{
return tracker.invoke(String.format("Reloading class %s.", implementationClassName), new Invokable<Object>()
{
public Object invoke()
{
Class reloadedClass = reloadImplementationClass();
return createInstance(reloadedClass);
};
});
}
/**
* Invoked when an instance of the class is needed. It is the responsibility of this method (as implemented in a
* subclass) to instantiate the class and inject dependencies into the class.
*
* @see InternalUtils#findAutobuildConstructor(Class)
*/
abstract protected Object createInstance(Class clazz);
private Class reloadImplementationClass()
{
if (logger.isDebugEnabled())
logger.debug(String.format("%s class %s.", firstTime ? "Loading" : "Reloading", implementationClassName));
ClassFactoryClassPool pool = new ClassFactoryClassPool(baseClassLoader);
ClassLoader threadDeadlockBuffer = new URLClassLoader(new URL[0], baseClassLoader);
Loader loader = new InternalLoader(threadDeadlockBuffer, pool);
ClassPath path = new LoaderClassPath(loader);
pool.appendClassPath(path);
classesToLoad.clear();
add(implementationClassName);
try
{
loader.addTranslator(pool, this);
Class result = loader.loadClass(implementationClassName);
firstTime = false;
return result;
}
catch (Throwable ex)
{
throw new RuntimeException(String.format("Unable to %s class %s: %s", firstTime ? "load" : "reload",
implementationClassName, InternalUtils.toMessage(ex)), ex);
}
}
private boolean shouldLoadClassNamed(String name)
{
return classesToLoad.contains(name);
}
private void add(String className)
{
if (classesToLoad.contains(className))
return;
logger.debug(String.format("Marking class %s to be (re-)loaded", className));
classesToLoad.add(className);
}
public void onLoad(ClassPool pool, String className) throws NotFoundException, CannotCompileException
{
logger.debug(String.format("BEGIN Analyzing %s", className));
analyze(pool, className);
trackClassFileChanges(className);
logger.debug(String.format(" END Analyzing %s", className));
}
private void analyze(ClassPool pool, String className) throws NotFoundException, CannotCompileException
{
CtClass ctClass = pool.get(className);
CtClass[] nestedClasses = ctClass.getNestedClasses();
for (CtClass nc : nestedClasses)
{
add(nc.getName());
}
ctClass.instrument(new ExprEditor()
{
public void edit(ConstructorCall c) throws CannotCompileException
{
if (c.getMethodName().equals("this"))
return;
String cn = c.getClassName();
String classFilePath = ClassFabUtils.getPathForClassNamed(cn);
URL url = baseClassLoader.getResource(classFilePath);
// If the base class is also a file on the file system then mark
// that it should be loaded by the same class loader. This serves two
// purposes: first, if the base class is in the same package then
// protected access will work properly. Secondly, if the base implementation
// changes, the service implementation will be reloaded.
if (url != null && url.getProtocol().equals("file"))
add(cn);
}
});
}
private void trackClassFileChanges(String className)
{
if (isInnerClassName(className))
return;
String path = ClassFabUtils.getPathForClassNamed(className);
URL url = baseClassLoader.getResource(path);
if (url != null && url.getProtocol().equals("file"))
changeTracker.add(url);
}
private boolean isInnerClassName(String className)
{
return className.indexOf('$') >= 0;
}
/** Does nothing. */
public void start(ClassPool pool) throws NotFoundException, CannotCompileException
{
}
}