package railo.loader.engine; import java.io.BufferedOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import railo.Version; import railo.loader.TP; import railo.loader.classloader.RailoClassLoader; import railo.loader.util.ExtensionFilter; import railo.loader.util.Util; import com.intergral.fusiondebug.server.FDControllerFactory; /** * Factory to load CFML Engine */ public class CFMLEngineFactory { // set to false to disable patch loading, for example in major alpha releases private static final boolean PATCH_ENABLED = true; private static CFMLEngineFactory factory; private static File railoServerRoot; private static CFMLEngineWrapper engineListener; private CFMLEngine engine; private ClassLoader mainClassLoader=new TP().getClass().getClassLoader(); private int version; private List<EngineChangeListener> listeners=new ArrayList<EngineChangeListener>(); private File resourceRoot; private PrintWriter out; /** * Constructor of the class */ protected CFMLEngineFactory(){ } /** * returns instance of this factory (singelton-> always the same instance) * do auto update when changes occur * @param config * @return Singelton Instance of the Factory * @throws ServletException */ public static CFMLEngine getInstance(ServletConfig config) throws ServletException { if(engineListener!=null) { if(factory==null) factory=engineListener.getCFMLEngineFactory(); return engineListener; } if(factory==null) factory=new CFMLEngineFactory(); // read init param from config factory.setInitParam(config); CFMLEngine engine = factory.getEngine(); engine.addServletConfig(config); engineListener = new CFMLEngineWrapper(engine); // add listener for update factory.addListener(engineListener); return engineListener; } /** * returns instance of this factory (singelton-> always the same instance) * do auto update when changes occur * @return Singelton Instance of the Factory * @throws RuntimeException */ public static CFMLEngine getInstance() throws RuntimeException { if(engineListener!=null) return engineListener; throw new RuntimeException("engine is not initalized, you must first call getInstance(ServletConfig)"); } /** * used only for internal usage * @param engine * @throws RuntimeException */ public static void registerInstance(CFMLEngine engine) throws RuntimeException { if(factory==null) factory=engine.getCFMLEngineFactory(); // first update existing listener if(engineListener!=null) { if(engineListener.equalTo(engine, true)) return; engineListener.onUpdate(engine);// perhaps this is still refrenced in the code, because of that we update it factory.removeListener(engineListener); } // now register this if(engine instanceof CFMLEngineWrapper) engineListener=(CFMLEngineWrapper) engine; else engineListener = new CFMLEngineWrapper(engine); factory.addListener(engineListener); } /** * returns instance of this factory (singelton-> always the same instance) * @param config * @param listener * @return Singelton Instance of the Factory * @throws ServletException */ public static CFMLEngine getInstance(ServletConfig config, EngineChangeListener listener) throws ServletException { getInstance(config); // add listener for update factory.addListener(listener); // read init param from config factory.setInitParam(config); CFMLEngine e = factory.getEngine(); e.addServletConfig(config); // make the FDController visible for the FDClient FDControllerFactory.makeVisible(); return e; } void setInitParam(ServletConfig config) { if(railoServerRoot!=null) return; String initParam=config.getInitParameter("railo-server-directory"); if(Util.isEmpty(initParam))initParam=config.getInitParameter("railo-server-root"); if(Util.isEmpty(initParam))initParam=config.getInitParameter("railo-server-dir"); if(Util.isEmpty(initParam))initParam=config.getInitParameter("railo-server"); initParam=Util.parsePlaceHolder(Util.removeQuotes(initParam,true)); try { if(!Util.isEmpty(initParam)) { File root=new File(initParam); if(!root.exists()) { if(root.mkdirs()) { railoServerRoot=root.getCanonicalFile(); return; } } else if(root.canWrite()) { railoServerRoot=root.getCanonicalFile(); return; } } } catch(IOException ioe){} } /** * adds a listener to the factory that will be informed when a new engine will be loaded. * @param listener */ private void addListener(EngineChangeListener listener) { if(!listeners.contains(listener)) { listeners.add(listener); } } private void removeListener(EngineChangeListener listener) { listeners.remove(listener); } /** * @return CFML Engine * @throws ServletException */ private CFMLEngine getEngine() throws ServletException { if(engine==null)initEngine(); return engine; } private void initEngine() throws ServletException { int coreVersion=Version.getIntVersion(); long coreCreated=Version.getCreateTime(); // get newest railo version as file File patcheDir=null; try { patcheDir = getPatchDirectory(); log("railo-server-root:"+patcheDir.getParent()); } catch (IOException e) { throw new ServletException(e); } File[] patches=PATCH_ENABLED?patcheDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})):null; File railo=null; if(patches!=null) { for(int i=0;i<patches.length;i++) { if(patches[i].getName().startsWith("tmp.rc")) { patches[i].delete(); } else if(patches[i].lastModified()<coreCreated) { patches[i].delete(); } else if(railo==null || isNewerThan(Util.toInVersion(patches[i].getName()),Util.toInVersion(railo.getName()))) { railo=patches[i]; } } } if(railo!=null && isNewerThan(coreVersion,Util.toInVersion(railo.getName())))railo=null; // Load Railo //URL url=null; try { // Load core version when no patch available if(railo==null) { tlog("Load Build in Core"); // String coreExt=getCoreExtension(); engine=getCore(coreExt); railo=new File(patcheDir,engine.getVersion()+"."+coreExt); if(PATCH_ENABLED) { InputStream bis = new TP().getClass().getResourceAsStream("/core/core."+coreExt); OutputStream bos=new BufferedOutputStream(new FileOutputStream(railo)); Util.copy(bis,bos); Util.closeEL(bis,bos); } } else { try { engine=getEngine(new RailoClassLoader(railo,mainClassLoader)); } catch(EOFException e) { System.err.println("Railo patch file "+railo+" is invalid, please delete it"); engine=getCore(getCoreExtension()); } } version=Util.toInVersion(engine.getVersion()); tlog("Loaded Railo Version "+engine.getVersion()); } catch(InvocationTargetException e) { e.getTargetException().printStackTrace(); throw new ServletException(e.getTargetException()); } catch(Exception e) { e.printStackTrace(); throw new ServletException(e); } //check updates String updateType=engine.getUpdateType(); if(updateType==null || updateType.length()==0)updateType="manuell"; if(updateType.equalsIgnoreCase("auto")) { new UpdateChecker(this).start(); } } private String getCoreExtension() throws ServletException { URL res = new TP().getClass().getResource("/core/core.rcs"); if(res!=null) return "rcs"; res = new TP().getClass().getResource("/core/core.rc"); if(res!=null) return "rc"; throw new ServletException("missing core file"); } private CFMLEngine getCore(String ext) throws SecurityException, IllegalArgumentException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException { InputStream is = null; try { is = new TP().getClass().getResourceAsStream("/core/core."+ext); RailoClassLoader classLoader=new RailoClassLoader(is,mainClassLoader,ext.equalsIgnoreCase("rcs")); return getEngine(classLoader); } finally { Util.closeEL(is); } } /** * method to initalize a update of the CFML Engine. * checks if there is a new Version and update it whwn a new version is available * @param password * @return has updated * @throws IOException * @throws ServletException */ public boolean update(String password) throws IOException, ServletException { if(!engine.can(CFMLEngine.CAN_UPDATE,password)) throw new IOException("access denied to update CFMLEngine"); //new RunUpdate(this).start(); return update(); } /** * restart the cfml engine * @param password * @return has updated * @throws IOException * @throws ServletException */ public boolean restart(String password) throws IOException, ServletException { if(!engine.can(CFMLEngine.CAN_RESTART_ALL,password)) throw new IOException("access denied to restart CFMLEngine"); return _restart(); } /** * restart the cfml engine * @param password * @return has updated * @throws IOException * @throws ServletException */ public boolean restart(String configId, String password) throws IOException, ServletException { if(!engine.can(CFMLEngine.CAN_RESTART_CONTEXT,password))// TODO restart single context throw new IOException("access denied to restart CFML Context (configId:"+configId+")"); return _restart(); } /** * restart the cfml engine * @param password * @return has updated * @throws IOException * @throws ServletException */ private synchronized boolean _restart() throws ServletException { engine.reset(); initEngine(); registerInstance(engine); callListeners(engine); System.gc(); System.gc(); return true; } /** * updates the engine when a update is available * @return has updated * @throws IOException * @throws ServletException */ private boolean update() throws IOException, ServletException { URL hostUrl=getEngine().getUpdateLocation(); if(hostUrl==null)hostUrl=new URL("http://www.getrailo.org"); URL infoUrl=new URL(hostUrl,"/railo/remote/version/info.cfm?ext="+getCoreExtension()+"&version="+version);// FUTURE replace with Info.cfc or better move the functionality to core if possible. something like engine.getUpdater a class provided by the core and defined (interface) by the loader. tlog("Check for update at "+hostUrl); String availableVersion = Util.toString((InputStream)infoUrl.getContent()).trim(); if(availableVersion.length()!=9) throw new IOException("can't get update info from ["+infoUrl+"]"); if(!isNewerThan(Util.toInVersion(availableVersion),version)) { tlog("There is no newer Version available"); return false; } tlog("Found a newer Version \n - current Version "+Util.toStringVersion(version)+"\n - available Version "+availableVersion); URL updateUrl=new URL(hostUrl,"/railo/remote/version/update.cfm?ext="+getCoreExtension()+"&version="+availableVersion); File patchDir=getPatchDirectory(); File newRailo=new File(patchDir,availableVersion+("."+getCoreExtension()));//isSecure?".rcs":".rc" if(newRailo.createNewFile()) { Util.copy((InputStream)updateUrl.getContent(),new FileOutputStream(newRailo)); } else { tlog("File for new Version already exists, won't copy new one"); return false; } try { engine.reset(); } catch(Throwable t) { t.printStackTrace(); } // Test new railo version valid //FileClassLoader classLoader=new FileClassLoader(newRailo,mainClassLoader); RailoClassLoader classLoader=new RailoClassLoader(newRailo,mainClassLoader); //URLClassLoader classLoader=new URLClassLoader(new URL[]{newRailo.toURL()},mainClassLoader); String v=""; try { CFMLEngine e = getEngine(classLoader); if(e==null)throw new IOException("can't load engine"); v=e.getVersion(); engine=e; version=Util.toInVersion(v); //e.reset(); callListeners(e); } catch (Exception e) { classLoader=null; System.gc(); try { newRailo.delete(); } catch(Exception ee){} tlog("There was a Problem with the new Version, can't install ("+e+":"+e.getMessage()+")"); e.printStackTrace(); return false; } tlog("Version ("+v+")installed"); return true; } /** * method to initalize a update of the CFML Engine. * checks if there is a new Version and update it whwn a new version is available * @param password * @return has updated * @throws IOException * @throws ServletException */ public boolean removeUpdate(String password) throws IOException, ServletException { if(!engine.can(CFMLEngine.CAN_UPDATE,password)) throw new IOException("access denied to update CFMLEngine"); return removeUpdate(); } /** * method to initalize a update of the CFML Engine. * checks if there is a new Version and update it whwn a new version is available * @param password * @return has updated * @throws IOException * @throws ServletException */ public boolean removeLatestUpdate(String password) throws IOException, ServletException { if(!engine.can(CFMLEngine.CAN_UPDATE,password)) throw new IOException("access denied to update CFMLEngine"); return removeLatestUpdate(); } /** * updates the engine when a update is available * @return has updated * @throws IOException * @throws ServletException */ private boolean removeUpdate() throws IOException, ServletException { File patchDir=getPatchDirectory(); File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"railo","rc","rcs"})); for(int i=0;i<patches.length;i++) { if(!patches[i].delete())patches[i].deleteOnExit(); } _restart(); return true; } private boolean removeLatestUpdate() throws IOException, ServletException { File patchDir=getPatchDirectory(); File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})); File patch=null; for(int i=0;i<patches.length;i++) { if(patch==null || isNewerThan(Util.toInVersion(patches[i].getName()),Util.toInVersion(patch.getName()))) { patch=patches[i]; } } if(patch!=null && !patch.delete())patch.deleteOnExit(); _restart(); return true; } public String[] getInstalledPatches() throws ServletException, IOException { File patchDir=getPatchDirectory(); File[] patches=patchDir.listFiles(new ExtensionFilter(new String[]{"."+getCoreExtension()})); List<String> list=new ArrayList<String>(); String name; int extLen=getCoreExtension().length()+1; for(int i=0;i<patches.length;i++) { name=patches[i].getName(); name=name.substring(0, name.length()-extLen); list.add(name); } String[] arr = list.toArray(new String[list.size()]); Arrays.sort(arr); return arr; } /** * call all registred listener for update of the engine * @param engine */ private void callListeners(CFMLEngine engine) { Iterator<EngineChangeListener> it = listeners.iterator(); while(it.hasNext()) { it.next().onUpdate(engine); } } private File getPatchDirectory() throws IOException { File pd = new File(getResourceRoot(),"patches"); if(!pd.exists())pd.mkdirs(); return pd; } /** * return directory to railo resource root * @return railo root directory * @throws IOException */ public File getResourceRoot() throws IOException { if(resourceRoot==null) { resourceRoot=new File(getRuningContextRoot(),"railo-server"); if(!resourceRoot.exists()) resourceRoot.mkdirs(); } return resourceRoot; } /** * @return return running context root * @throws IOException * @throws IOException */ private File getRuningContextRoot() throws IOException { if(railoServerRoot!=null) { return railoServerRoot; } File dir=getClassLoaderRoot(mainClassLoader); dir.mkdirs(); if(dir.exists() && dir.isDirectory()) return dir; throw new IOException("can't create/write to directory ["+dir+"], set \"init-param\" \"railo-server-directory\" with path to writable directory"); } /** * returns the path where the classloader is located * @param cl ClassLoader * @return file of the classloader root */ public static File getClassLoaderRoot(ClassLoader cl) { String path="railo/loader/engine/CFMLEngine.class"; URL res = cl.getResource(path); // get file and remove all after ! String strFile=null; try { strFile = URLDecoder.decode(res.getFile().trim(),"iso-8859-1"); } catch (UnsupportedEncodingException e) { } int index=strFile.indexOf('!'); if(index!=-1)strFile=strFile.substring(0,index); // remove path at the end index=strFile.lastIndexOf(path); if(index!=-1)strFile=strFile.substring(0,index); // remove "file:" at start and railo.jar at the end if(strFile.startsWith("file:"))strFile=strFile.substring(5); if(strFile.endsWith("railo.jar")) strFile=strFile.substring(0,strFile.length()-9); File file=new File(strFile); if(file.isFile())file=file.getParentFile(); return file; } /** * check left value against right value * @param left * @param right * @return returns if right is newer than left */ private boolean isNewerThan(int left, int right) { return left>right; } /** * Load CFMl Engine Implementation (railo.runtime.engine.CFMLEngineImpl) from a Classloader * @param classLoader * @return loaded CFML Engine * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws SecurityException * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ private CFMLEngine getEngine(ClassLoader classLoader) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { Class clazz=classLoader.loadClass("railo.runtime.engine.CFMLEngineImpl"); Method m = clazz.getMethod("getInstance",new Class[]{CFMLEngineFactory.class}); return (CFMLEngine) m.invoke(null,new Object[]{this}); } /** * log info to output * @param obj Object to output */ public void tlog(Object obj) { log(new Date()+ " "+obj); } /** * log info to output * @param obj Object to output */ public void log(Object obj) { if(out==null){ boolean isCLI=false; String str=System.getProperty("railo.cli.call"); if(!Util.isEmpty(str, true)) { str=str.trim(); isCLI="true".equalsIgnoreCase(str) || "yes".equalsIgnoreCase(str); } if(isCLI) { try{ File dir = new File(getResourceRoot(),"logs"); dir.mkdirs(); File file = new File(dir,"out"); file.createNewFile(); out=new PrintWriter(file); } catch(Throwable t){t.printStackTrace();} } if(out==null)out=new PrintWriter(System.out); } out.write(""+obj+"\n"); out.flush(); } private class UpdateChecker extends Thread { private CFMLEngineFactory factory; private UpdateChecker(CFMLEngineFactory factory) { this.factory=factory; } public void run() { long time=10000; while(true) { try { sleep(time); time=1000*60*60*24; factory.update(); } catch (Exception e) { } } } } }