package org.anodyneos.xpImpl.runtime;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.anodyneos.commons.xml.UnifiedResolver;
import org.anodyneos.commons.xml.xsl.TemplatesCache;
import org.anodyneos.commons.xml.xsl.TemplatesCacheImpl;
import org.anodyneos.servlet.xsl.GenericErrorHandler;
import org.anodyneos.xp.XpCompilationException;
import org.anodyneos.xp.XpException;
import org.anodyneos.xp.XpFactory;
import org.anodyneos.xp.XpFileNotFoundException;
import org.anodyneos.xp.XpPage;
import org.anodyneos.xp.XpTranslationException;
import org.anodyneos.xp.http.HttpXpContext;
import org.anodyneos.xp.standalone.StandaloneXpAppContext;
import org.anodyneos.xp.standalone.StandaloneXpAppContextMapAdapter;
import org.anodyneos.xp.standalone.StandaloneXpContext;
import org.anodyneos.xpImpl.http.HttpXpContextImpl;
import org.anodyneos.xpImpl.standalone.StandaloneXpContextImpl;
import org.anodyneos.xpImpl.translater.Translater;
import org.anodyneos.xpImpl.translater.TranslaterResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class XpFactoryImpl extends XpFactory {
private static final Log log = LogFactory.getLog(XpFactoryImpl.class);
public static final long NEVER_LOADED = -1;
private ClassLoader parentLoader;
private String classPath;
private File classRoot;
private File javaRoot;
private URI xpRegistry;
private StandaloneXpAppContext appCtx;
private UnifiedResolver resolver;
private boolean autoLoad = true;
private TemplatesCache templatesCache;
private final Map xpCache =
Collections.synchronizedMap(new HashMap());
public XpFactoryImpl() {
// TODO: lazy load if possible
templatesCache = new TemplatesCacheImpl();
GenericErrorHandler errorHandler = new GenericErrorHandler();
templatesCache.setErrorHandler(errorHandler);
templatesCache.setErrorListener(errorHandler);
templatesCache.setCacheEnabled(true);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = this.getClass().getClassLoader();
}
setParentLoader(loader);
}
public XpPage newXpPage(URI xpURI)
throws XpFileNotFoundException, XpTranslationException, XpCompilationException, XpException {
//XpPage xpPage = (XpPage)xpCache.get(xpURI.toString());
XpPageHolder xpPageHolder = (XpPageHolder) xpCache.get(xpURI.toString());
long loadTime = NEVER_LOADED;
if (xpPageHolder != null ){
loadTime = xpPageHolder.loadTime;
}
// if the page hasn't been loaded before or it's out of date,
// make sure we have exclusive access to the cache
// then check again to be more certain that nobody has reloaded it since we last checked
// TODO: jv note: technically, this may not work, see http://www.javaworld.com/jw-02-2001/jw-0209-double.html
// not sure how much the weakness may just be theoretical, but we may want to take a closer look.
if ((xpPageHolder == null )
|| (xpPageHolder != null
&& xpNeedsReloading(xpURI, loadTime, xpPageHolder.xpPageClass.getClassLoader()))){
synchronized(this){
xpPageHolder = (XpPageHolder)xpCache.get(xpURI.toString());
loadTime = NEVER_LOADED;
if (xpPageHolder != null ){
loadTime = xpPageHolder.loadTime;
}
// is the xp file even there any longer ?
// TODO this should be checked above, not just when reloading is required (also dependents)
if (!Translater.xpExists(xpURI,getResolver())){
// TODO xpCache.remove(xpURI.toString());
throw new XpFileNotFoundException(xpURI.toString());
}
// does it still need reloading (we could have spent a lot of time waiting for the lock) ?
if ((xpPageHolder == null )
|| (xpPageHolder != null
&& xpNeedsReloading(xpURI, loadTime, xpPageHolder.getClass().getClassLoader()))) {
if (log.isInfoEnabled()) {
log.info("reloading: " + xpURI.toString());
}
translateXp(xpURI);
compileXp(xpURI);
xpCache.remove(xpURI.toString());
xpPageHolder = loadPage(xpURI);
xpCache.put(xpURI.toString(),xpPageHolder);
}
}
}
try {
AbstractXpPage xpPage = (AbstractXpPage) xpPageHolder.xpPageClass.newInstance();
xpPage.setTemplatesCache(getTemplatesCache());
xpPage.init();
return xpPage;
} catch (IllegalAccessException e) {
throw new XpCompilationException(e);
} catch (InstantiationException e) {
throw new XpCompilationException(e);
}
}
public HttpXpContext getHttpXpContext(
Servlet servlet, ServletRequest servletRequest, ServletResponse servletResponse) {
HttpXpContextImpl ctx = new HttpXpContextImpl();
ctx.initialize(servlet, servletRequest, servletResponse);
return ctx;
}
public void releaseHttpXpContext(HttpXpContext ctx) {
// noop
}
public StandaloneXpContext getStandaloneXpContext() {
StandaloneXpContext ctx = new StandaloneXpContextImpl();
ctx.initialize(getStandaloneXpAppContext());
return ctx;
}
public void releaseStandaloneXpContext(StandaloneXpContext ctx) {
// noop
}
private XpPageHolder loadPage(URI xpURI) throws XpCompilationException{
try{
XpPageHolder xpPageHolder = new XpPageHolder();
xpPageHolder.loadTime = System.currentTimeMillis();
XpClassLoader loader = new XpClassLoader(parentLoader);
loader.setRoot(getClassGenDirectory().getPath());
xpPageHolder.xpPageClass = loader.loadClass(Translater.getClassName(xpURI));
return xpPageHolder;
}catch(Exception e){
throw new XpCompilationException(e);
}
}
private boolean xpNeedsReloading(URI xpURI, long loadTime, ClassLoader loader) throws XpFileNotFoundException{
if (this.isAutoLoad()){
if (Translater.xpIsOutOfDate(xpURI, getClassGenDirectory().getPath(), getResolver(), loadTime)) {
return true;
} else {
try{
Class xpClass = Class.forName(Translater.getClassName(xpURI),true,loader);
Method getDependents = xpClass.getDeclaredMethod("getDependents",(Class[])null);
List dependents = (List)getDependents.invoke((Object)null,(Object[])null);
for (int i=0; i<dependents.size();i++){
String dependent = (String)dependents.get(i);
URI uriDep = new URI(dependent);
if (xpNeedsReloading(uriDep,loadTime,loader)){
return true;
}
}
}catch (Exception e){
// something happened
if(log.isErrorEnabled()) {
log.error("Unable to inspect children of "
+ xpURI.toString() + " to see if they would cause a reload.", e);
}
return true;
}
}
// neither the file itself nor any dependents are out of date
return false;
}else{
// autoload is not enabled, so never reload what's already there.
return false;
}
}
private void compileXp(URI xpURI) {
if(log.isDebugEnabled()) {
log.debug("compiling: " + xpURI.toString());
}
String[] args = new String[9];
args[0] = "-classpath";
args[1] = getCompileClassPath();
args[2] = "-sourcepath";
args[3] = getJavaGenDirectory().getPath();
args[4] = "-d";
args[5] = getClassGenDirectory().getPath();
args[6] = Translater.getJavaFile(getJavaGenDirectory().getPath(), xpURI);
args[7] = "-noExit";
args[8] = "-nowarn";
org.eclipse.jdt.internal.compiler.batch.Main.main(args);
/*
JavaCompiler compiler = new SunJavaCompiler(getClassPath(), getClassRoot());
compiler.setSourcePath(getJavaRoot());
compiler.compile(Translater.getJavaFile(getJavaRoot(),xpURI),System.err);
*/
}
private void translateXp(URI xpURI) throws IllegalStateException, XpTranslationException,XpFileNotFoundException{
if(log.isDebugEnabled()) {
log.debug("translating: " + xpURI.toString());
}
if (getResolver() == null){
throw new IllegalStateException("XpCachingLoader requires resolver to be set.");
}
// TODO parse the registry separately, check it on each page load.
TranslaterResult result = Translater.translate(
getJavaGenDirectory().getPath(), xpURI, getXpRegistryURI().toString(),resolver);
}
public File getClassGenDirectory() {
return classRoot;
}
public void setClassGenDirectory(File classRoot) {
this.classRoot = classRoot;
refreshClassPath();
}
public ClassLoader getParentLoader() {
return parentLoader;
}
public void setParentLoader(ClassLoader parentLoader) {
this.parentLoader = parentLoader;
refreshClassPath();
}
public File getJavaGenDirectory() { return javaRoot; }
public void setJavaGenDirectory(File javaRoot) { this.javaRoot = javaRoot; }
public URI getXpRegistryURI() { return xpRegistry; }
public void setXpRegistryURI(URI xpRegistry) { this.xpRegistry = xpRegistry; }
public StandaloneXpAppContext getStandaloneXpAppContext() {
if (null == appCtx) {
appCtx = new StandaloneXpAppContextMapAdapter();
}
return appCtx;
}
public void setStandaloneXpAppContext(StandaloneXpAppContext appCtx) { this.appCtx = appCtx; }
public String getCompileClassPath() { return classPath; }
public boolean isAutoLoad() { return autoLoad; }
public void setAutoLoad(boolean autoLoad) { this.autoLoad = autoLoad; }
public TemplatesCache getTemplatesCache() { return templatesCache; }
public void setTemplatesCache(TemplatesCache templatesCache) { this.templatesCache = templatesCache; }
public UnifiedResolver getResolver() {
return resolver;
}
public void setResolver(UnifiedResolver resolver) {
this.resolver = resolver;
templatesCache.setUnifiedResolver(resolver);
}
private void refreshClassPath() {
StringBuffer cpath = new StringBuffer();
StringBuffer rejectpath = new StringBuffer();
for (URLClassLoader loopLoader = (URLClassLoader) this.parentLoader;
loopLoader != null;
loopLoader = (URLClassLoader) loopLoader.getParent()) {
URL[] urls = loopLoader.getURLs();
if (log.isDebugEnabled()) {
log.debug("adding URLClassloader URLs:" + Arrays.asList(urls));
}
for(int i=0; i<urls.length;i++) {
URL url = urls[i];
if( url.getProtocol().equals("file") ) {
String file = url.getFile();
if (file.endsWith(".jnilib") || file.endsWith(".dylib")) {
// skip these; Eclipse JDT 3.3.1 complains about them.
// JDT 3.4.2 doesn't complain, but we are using jasper-jdt
// for Maven dependency convenience, and it is currently 3.3.1.
rejectpath.append(file + File.pathSeparator);
} else {
cpath.append(file + File.pathSeparator);
}
}
}
}
// The following is tomcat specific, produces the same result as climbing the classloader chain on 5.5
/*
String tccp = (String) getServletContext().getAttribute("org.apache.catalina.jsp_classpath");
logger.debug("Tomcat servlet classpath: " + tccp);
if (null != tccp) {
cpath.append(File.pathSeparator + tccp);
}
*/
if (null != classRoot) {
cpath.append(classRoot);
}
if (rejectpath.length() > 0) {
log.info("skipping classpath elements to avoid future warnings from JDT: '" + rejectpath.toString() + "'");
}
if (log.isDebugEnabled()) {
log.debug("now using classpath: " + cpath.toString());
}
this.classPath = cpath.toString();
}
private class XpPageHolder {
private Class xpPageClass;
private long loadTime;
}
}