/** * Copyright (c) 2014, the Railo Company Ltd. * Copyright (c) 2015, Lucee Assosication Switzerland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ package lucee.loader.engine; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.UnknownHostException; import java.security.CodeSource; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.jar.Attributes; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import lucee.VersionInfo; import lucee.loader.TP; import lucee.loader.osgi.BundleCollection; import lucee.loader.osgi.BundleLoader; import lucee.loader.osgi.BundleUtil; import lucee.loader.osgi.LoggerImpl; import lucee.loader.util.ExtensionFilter; import lucee.loader.util.Util; import lucee.loader.util.ZipUtil; import lucee.runtime.config.Identification; import lucee.runtime.config.Password; import lucee.runtime.util.Pack200Util; import org.apache.felix.framework.Felix; import org.apache.felix.framework.Logger; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.Version; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.intergral.fusiondebug.server.FDControllerFactory; /** * Factory to load CFML Engine */ public class CFMLEngineFactory extends CFMLEngineFactorySupport { // set to false to disable patch loading, for example in major alpha releases private static final boolean PATCH_ENABLED = true; public static final Version VERSION_ZERO = new Version(0, 0, 0, "0"); private static final String UPDATE_LOCATION = "http://release.lucee.org"; // MUST from server.xml private static final long GB1 = 1024*1024*1024; private static final long MB100 = 1024*1024*100; private static CFMLEngineFactory factory; //private static CFMLEngineWrapper engineListener; private static CFMLEngineWrapper singelton; private static File luceeServerRoot; private Felix felix; private BundleCollection bundleCollection; //private CFMLEngineWrapper engine; private final ClassLoader mainClassLoader = new TP().getClass() .getClassLoader(); private Version version; private final List<EngineChangeListener> listeners = new ArrayList<EngineChangeListener>(); private File resourceRoot; //private PrintWriter out; private final LoggerImpl logger; protected ServletConfig config; /** * Constructor of the class */ protected CFMLEngineFactory(final ServletConfig config) { // PATCH // System.setProperty("org.apache.xerces.xni.parser.XMLParserConfiguration", "org.apache.xerces.parsers.XIncludeAwareParserConfiguration"); File logFile = null; this.config = config; try { logFile = new File(getResourceRoot(), "context/logs/felix.log"); if(logFile.isFile()) { // more than a GB (from the time we did not control it) if(logFile.length()>GB1) { logFile.delete(); // we simply delete it } else if(logFile.length()>MB100) { File bak = new File(logFile.getParentFile(),"felix.1.log"); if(bak.isFile()) bak.delete(); logFile.renameTo(bak); } } } catch (final IOException e) { e.printStackTrace(); } logFile.getParentFile().mkdirs(); logger = new LoggerImpl(logFile); } /** * 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(final ServletConfig config) throws ServletException { if (singelton != null) { if (factory == null) factory = singelton.getCFMLEngineFactory(); // not sure if this ever is done, but it does not hurt return singelton; } if (factory == null) factory = new CFMLEngineFactory(config); // read init param from config factory.readInitParam(config); factory.initEngineIfNecessary(); singelton.addServletConfig(config); // add listener for update //factory.addListener(singelton); return singelton; } /** * 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 (singelton != null) return singelton; throw new RuntimeException( "engine is not initalized, you must first call getInstance(ServletConfig)"); } public static void registerInstance(final CFMLEngine engine) { if (engine instanceof CFMLEngineWrapper) throw new RuntimeException("that should not happen!"); setEngine(engine); } /** * 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(final ServletConfig config, final EngineChangeListener listener) throws ServletException { getInstance(config); // add listener for update factory.addListener(listener); // read init param from config factory.readInitParam(config); factory.initEngineIfNecessary(); singelton.addServletConfig(config); // make the FDController visible for the FDClient FDControllerFactory.makeVisible(); return singelton; } void readInitParam(final ServletConfig config) { if (luceeServerRoot != null) return; String initParam = config.getInitParameter("lucee-server-directory"); if (Util.isEmpty(initParam)) initParam = config.getInitParameter("lucee-server-root"); if (Util.isEmpty(initParam)) initParam = config.getInitParameter("lucee-server-dir"); if (Util.isEmpty(initParam)) initParam = config.getInitParameter("lucee-server"); if (Util.isEmpty(initParam)) initParam = System.getProperty("lucee.server.dir"); initParam = parsePlaceHolder(removeQuotes(initParam, true)); try { if (!Util.isEmpty(initParam)) { final File root = new File(initParam); if (!root.exists()) { if (root.mkdirs()) { luceeServerRoot = root.getCanonicalFile(); return; } } else if (root.canWrite()) { luceeServerRoot = root.getCanonicalFile(); return; } } } catch (final IOException ioe) { ioe.printStackTrace(); } } /** * adds a listener to the factory that will be informed when a new engine * will be loaded. * * @param listener */ private void addListener(final EngineChangeListener listener) { if (!listeners.contains(listener)) listeners.add(listener); } /** * @throws ServletException */ private void initEngineIfNecessary() throws ServletException { if (singelton == null) initEngine(); } private void initEngine() throws ServletException { final Version coreVersion = VersionInfo.getIntVersion(); final long coreCreated = VersionInfo.getCreateTime(); // get newest lucee version as file File patcheDir = null; try { patcheDir = getPatchDirectory(); log(Logger.LOG_DEBUG, "lucee-server-root:" + patcheDir.getParent()); } catch (final IOException e) { throw new ServletException(e); } final File[] patches = PATCH_ENABLED ? patcheDir .listFiles(new ExtensionFilter(new String[] { ".lco" })) : null; File lucee = null; if (patches != null) for (final File patche : patches) if (patche.getName().startsWith("tmp.lco")) patche.delete(); else if (patche.lastModified() < coreCreated) patche.delete(); else if (lucee == null || Util.isNewerThan( toVersion(patche.getName(), VERSION_ZERO), toVersion(lucee.getName(), VERSION_ZERO))) lucee = patche; if (lucee != null && Util.isNewerThan(coreVersion, toVersion(lucee.getName(), VERSION_ZERO))) lucee = null; // Load Lucee //URL url=null; try { // Load core version when no patch available if (lucee == null) { log(Logger.LOG_DEBUG, "Load Build in Core"); // final String coreExt = "lco"; // copy core final File rc = new File(getTempDirectory(), "tmp_"+ System.currentTimeMillis() + "."+coreExt); InputStream is=null; OutputStream os=null; try { is = new TP().getClass().getResourceAsStream("/core/core." + coreExt); os = new BufferedOutputStream(new FileOutputStream(rc)); copy(is, os); } finally { closeEL(is); closeEL(os); } lucee = new File(patcheDir, getVersion(rc) + "." + coreExt); try { is = new FileInputStream(rc); os = new BufferedOutputStream(new FileOutputStream(lucee)); copy(is, os); } finally { closeEL(is); closeEL(os); rc.delete(); } setEngine(_getCore(lucee)); /*if (PATCH_ENABLED) { final InputStream bis = new TP().getClass() .getResourceAsStream("/core/core." + coreExt); final OutputStream bos = new BufferedOutputStream( new FileOutputStream(lucee)); copy(bis, bos); closeEL(bis); closeEL(bos); }*/ } else { bundleCollection = BundleLoader.loadBundles(this, getFelixCacheDirectory(), getBundleDirectory(), lucee, bundleCollection); //bundle=loadBundle(lucee); log(Logger.LOG_DEBUG, "loaded bundle:" + bundleCollection.core.getSymbolicName()); setEngine(getEngine(bundleCollection)); log(Logger.LOG_DEBUG, "loaded engine:" + singelton); } version = singelton.getInfo().getVersion(); log(Logger.LOG_DEBUG, "Loaded Lucee Version " + singelton.getInfo().getVersion()); } catch (final InvocationTargetException e) { e.getTargetException().printStackTrace(); throw new ServletException(e.getTargetException()); } catch (final Exception e) { e.printStackTrace(); throw new ServletException(e); } //check updates String updateType = singelton.getUpdateType(); if (updateType == null || updateType.length() == 0) updateType = "manuell"; if (updateType.equalsIgnoreCase("auto")) new UpdateChecker(this, null).start(); } private static String getVersion(File file) throws IOException, BundleException { JarFile jar = new JarFile(file); try { Manifest manifest = jar.getManifest(); Attributes attrs = manifest.getMainAttributes(); return attrs.getValue("Bundle-Version"); } finally { jar.close(); } } private static CFMLEngineWrapper setEngine(final CFMLEngine engine) { //new RuntimeException("setEngine").printStackTrace(); if (singelton == null) singelton = new CFMLEngineWrapper(engine); else if (!singelton.isIdentical(engine)) { singelton.setEngine(engine); // reset of the old is made before } else { //new RuntimeException("useless call").printStackTrace(); } return singelton; } public Felix getFelix(final File cacheRootDir, Map<String, Object> config) throws BundleException { if (config == null) config = new HashMap<String, Object>(); // Log Level int logLevel = 1; // 1 = error, 2 = warning, 3 = information, and 4 = debug String strLogLevel = getSystemPropOrEnvVar("felix.log.level", null); if(Util.isEmpty(strLogLevel))strLogLevel = (String) config.get("felix.log.level"); if (!Util.isEmpty(strLogLevel)) { if ("0".equalsIgnoreCase(strLogLevel)) logLevel = 0; else if ("error".equalsIgnoreCase(strLogLevel) || "1".equalsIgnoreCase(strLogLevel)) logLevel = 1; else if ("warning".equalsIgnoreCase(strLogLevel) || "2".equalsIgnoreCase(strLogLevel)) logLevel = 2; else if ("info".equalsIgnoreCase(strLogLevel) || "information".equalsIgnoreCase(strLogLevel) || "3".equalsIgnoreCase(strLogLevel)) logLevel = 3; else if ("debug".equalsIgnoreCase(strLogLevel) || "4".equalsIgnoreCase(strLogLevel)) logLevel = 4; } config.put("felix.log.level", "" + logLevel); // storage clean final String storageClean = (String) config .get(Constants.FRAMEWORK_STORAGE_CLEAN); if (Util.isEmpty(storageClean)) config.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); // parent classLoader final String parentClassLoader = (String) config .get(Constants.FRAMEWORK_BUNDLE_PARENT); if (Util.isEmpty(parentClassLoader)) config.put(Constants.FRAMEWORK_BUNDLE_PARENT, Constants.FRAMEWORK_BUNDLE_PARENT_FRAMEWORK); else config.put(Constants.FRAMEWORK_BUNDLE_PARENT, BundleUtil.toFrameworkBundleParent(parentClassLoader)); // felix.cache.rootdir boolean isNew=false; if (!cacheRootDir.exists()) { cacheRootDir.mkdirs(); isNew=true; } if (cacheRootDir.isDirectory()) config.put("felix.cache.rootdir", cacheRootDir.getAbsolutePath()); if (logger != null) config.put("felix.log.logger", logger); // TODO felix.log.logger // remove any empty record, this can produce trouble { final Iterator<Entry<String, Object>> it = config.entrySet() .iterator(); Entry<String, Object> e; Object v; while (it.hasNext()) { e = it.next(); v = e.getValue(); if (v == null || v.toString().isEmpty()) it.remove(); } } final StringBuilder sb = new StringBuilder("loading felix with config:"); final Iterator<Entry<String, Object>> it = config.entrySet().iterator(); Entry<String, Object> e; while (it.hasNext()) { e = it.next(); sb.append("\n- ").append(e.getKey()).append(':') .append(e.getValue()); } log(Logger.LOG_INFO, sb.toString()); felix = new Felix(config); try { felix.start(); } catch(BundleException be) { // this could be cause by an invalid felix cache, so we simply delete it and try again if(!isNew && "Error creating bundle cache.".equals(be.getMessage())) { Util.deleteContent(cacheRootDir,null); } } return felix; } protected static String getSystemPropOrEnvVar(String name, String defaultValue) { // env String value=System.getenv(name); if(!Util.isEmpty(value)) return value; // prop value=System.getProperty(name); if(!Util.isEmpty(value)) return value; // env 2 name=name.replace('.', '_').toUpperCase(); value=System.getenv(name); if(!Util.isEmpty(value)) return value; return defaultValue; } public void log(final Throwable t) { if (logger != null) logger.log(Logger.LOG_ERROR, "", t); } public void log(final int level, final String msg) { if (logger != null) logger.log(level, msg); } private CFMLEngine _getCore(File rc) throws IOException, BundleException, ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { bundleCollection = BundleLoader.loadBundles(this, getFelixCacheDirectory(), getBundleDirectory(), rc, bundleCollection); return getEngine(bundleCollection); } /** * 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(final Password password, final Identification id) throws IOException, ServletException { if (!singelton.can(CFMLEngine.CAN_UPDATE, password)) throw new IOException("access denied to update CFMLEngine"); //new RunUpdate(this).start(); return _update(id); } /** * restart the cfml engine * * @param password * @return has updated * @throws IOException * @throws ServletException */ public boolean restart(final Password password) throws IOException, ServletException { if (!singelton.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(final String configId, final Password password) throws IOException, ServletException { if (!singelton.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 { if(singelton!=null) singelton.reset(); initEngine(); System.gc(); return true; } /** * updates the engine when a update is available * * @return has updated * @throws IOException * @throws ServletException */ private boolean _update(final Identification id) throws IOException, ServletException { if(singelton!=null) singelton.reset(); final File newLucee = downloadCore(id); if (newLucee == null) return false; final Version v = null; try { bundleCollection = BundleLoader.loadBundles(this, getFelixCacheDirectory(), getBundleDirectory(), newLucee, bundleCollection); final CFMLEngine e = getEngine(bundleCollection); if (e == null) throw new IOException("can't load engine"); version = e.getInfo().getVersion(); //engine = e; setEngine(e); //e.reset(); callListeners(e); } catch (final Exception e) { System.gc(); try { newLucee.delete(); } catch (final Exception ee) { } log(e); e.printStackTrace(); return false; } log(Logger.LOG_DEBUG, "Version (" + v + ")installed"); return true; } public File downloadBundle(final String symbolicName, final String symbolicVersion, Identification id) throws IOException { final File jarDir = getBundleDirectory(); // before we download we check if we have it bundled File jar = deployBundledBundle(jarDir,symbolicName,symbolicVersion); if(jar!=null && jar.isFile()) return jar; jar = new File(jarDir, symbolicName.replace('.', '-') + "-" + symbolicVersion.replace('.', '-') + (".jar")); final URL updateProvider = getUpdateLocation(); if (id == null && singelton != null) id = singelton.getIdentification(); final URL updateUrl = new URL(updateProvider, "/rest/update/provider/download/" + symbolicName + "/" + symbolicVersion + "/" + (id != null ? id.toQueryString() : "") + (id == null ? "?" : "&")+"allowRedirect=true" ); System.out.println("download " + symbolicName + ":" + symbolicVersion +" from "+updateUrl+" and copy to "+jar); // MUST remove log(Logger.LOG_DEBUG, "download bundle [" + symbolicName + ":"+ symbolicVersion + "] from " + updateUrl+" and copy to "+jar); int code; HttpURLConnection conn; try { conn = (HttpURLConnection) updateUrl.openConnection(); conn.setRequestMethod("GET"); conn.connect(); code = conn.getResponseCode(); } catch (UnknownHostException e) { throw new IOException("could not download the bundle [" + symbolicName + ":"+ symbolicVersion + "] from " + updateUrl+" and copy to "+jar, e); } //System.out.println("SC:" + code+"->"+conn.getFollowRedirects()); // the update provider is not providing a download for this if (code != 200) { // the update provider can also provide a different (final) location for this if(code==302) { String location = conn.getHeaderField("Location"); // just in case we check invalid names if(location==null)location = conn.getHeaderField("location"); if(location==null)location = conn.getHeaderField("LOCATION"); System.out.println("download redirected:" + location); // MUST remove conn.disconnect(); URL url = new URL(location); try { conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.connect(); code = conn.getResponseCode(); } catch (final UnknownHostException e) { log(e); throw new IOException("could not download the bundle [" + symbolicName + ":"+ symbolicVersion + "] from " + location+" and copy to "+jar, e); } } // no download available! if(code != 200){ final String msg = "Lucee is not able do download the bundle for [" + symbolicName + "] in version [" + symbolicVersion + "] from " + updateUrl + ", please donwload manually and copy to [" + jarDir + "]"; log(Logger.LOG_ERROR, msg); conn.disconnect(); throw new IOException(msg); } } //if(jar.createNewFile()) { copy((InputStream) conn.getContent(), new FileOutputStream(jar)); conn.disconnect(); return jar; /*} else { throw new IOException("File ["+jar.getName()+"] already exists, won't copy new one"); }*/ } private File deployBundledBundle(File bundleDirectory, String symbolicName, String symbolicVersion) { String sub="bundles/"; String nameAndVersion=symbolicName+"|"+symbolicVersion; String osgiFileName=symbolicName+"-"+symbolicVersion+".jar"; String pack20Ext=".pack.gz"; boolean isPack200=false; // first we look for a exact match InputStream is = getClass().getResourceAsStream("bundles/"+osgiFileName); if(is==null) is = getClass().getResourceAsStream("/bundles/"+osgiFileName); System.out.println("found "+osgiFileName+":"+(is!=null)); if(is==null) { is = getClass().getResourceAsStream("bundles/"+osgiFileName+pack20Ext); if(is==null)is = getClass().getResourceAsStream("/bundles/"+osgiFileName+pack20Ext); isPack200=true; } if(is!=null) { File temp=null; try { // copy to temp file temp=File.createTempFile("bundle", ".tmp"); Util.copy(new BufferedInputStream(is), new FileOutputStream(temp),true,true); if(isPack200) { File temp2 = File.createTempFile("bundle", ".tmp2"); Pack200Util.pack2Jar(temp, temp2); temp.delete(); temp=temp2; } // adding bundle File trg=new File(bundleDirectory,osgiFileName); temp.renameTo(trg); printDate("adding bundle ["+symbolicName+"] in version ["+symbolicVersion+"] to ["+trg+"]"); log(Logger.LOG_DEBUG, "adding bundle ["+symbolicName+"] in version ["+symbolicVersion+"] to ["+trg+"]"); return trg; } catch(IOException ioe){} finally { if(temp!=null && temp.exists())temp.delete(); } } // now we search the current jar as a external zip what is slow (we do not support pack200 in this case) // this also not works with windows if(isWindows()) return null; ZipEntry entry; File temp; ZipInputStream zis = null; try { CodeSource src = CFMLEngineFactory.class.getProtectionDomain().getCodeSource(); if (src == null) return null; URL loc = src.getLocation(); zis=new ZipInputStream(loc.openStream()); String path,name,bundleInfo; int index; while ((entry = zis.getNextEntry())!= null) { temp=null; path = entry.getName().replace('\\', '/'); if(path.startsWith("/")) path=path.substring(1); // some zip path start with "/" some not isPack200=false; if(path.startsWith(sub) && (path.endsWith(".jar") /*|| (isPack200=path.endsWith(".jar.pack.gz"))*/)) { // ignore non jar files or file from elsewhere index=path.lastIndexOf('/')+1; if(index==sub.length()) { // ignore sub directories name=path.substring(index); temp=null; try { temp=File.createTempFile("bundle", ".tmp"); Util.copy(zis, new FileOutputStream(temp),false,true); /*if(isPack200) { File temp2 = File.createTempFile("bundle", ".tmp2"); Pack200Util.pack2Jar(temp, temp2); temp.delete(); temp=temp2; name=name.substring(0,name.length()-".pack.gz".length()); }*/ bundleInfo=BundleLoader.loadBundleInfo(temp); if(bundleInfo!=null && nameAndVersion.equals(bundleInfo)) { File trg=new File(bundleDirectory,name); temp.renameTo(trg); printDate("adding bundle [ "+symbolicName+" ] in version [ "+symbolicVersion+" ] to [ "+trg+" ]"); log(Logger.LOG_DEBUG, "adding bundle ["+symbolicName+"] in version ["+symbolicVersion+"] to ["+trg+"]"); return trg; } } finally { if(temp!=null && temp.exists())temp.delete(); } } } zis.closeEntry(); } } catch(Throwable t){ if(t instanceof ThreadDeath) throw (ThreadDeath)t; } finally { Util.closeEL(zis); } return null; } private boolean isWindows() { String os = System.getProperty("os.name").toLowerCase(); return os.startsWith("windows"); } private void printDate(String value) { long millis=System.currentTimeMillis(); System.out.println( new Date(millis) +"-" +(millis-(millis/1000*1000)) +" "+value); } private File downloadCore(Identification id) throws IOException { final URL updateProvider = getUpdateLocation(); if (id == null && singelton != null) id = singelton.getIdentification(); // only happens when the code runs from the debug project if(version==null) version=getInstance().getInfo().getVersion(); final URL infoUrl = new URL(updateProvider, "/rest/update/provider/update-for/" + version.toString() + (id != null ? id.toQueryString() : "")); log(Logger.LOG_DEBUG, "Check for update at " + updateProvider); String strAvailableVersion = toString( (InputStream) infoUrl.getContent()).trim(); log(Logger.LOG_DEBUG, "receive available update version from update provider (" + strAvailableVersion + ") "); strAvailableVersion = CFMLEngineFactorySupport.removeQuotes(strAvailableVersion, true); if (strAvailableVersion.length() == 0 || !Util.isNewerThan(toVersion(strAvailableVersion, VERSION_ZERO), version)) { log(Logger.LOG_DEBUG, "There is no newer Version available"); return null; } log(Logger.LOG_DEBUG, "Found a newer Version \n - current Version " + version.toString() + "\n - available Version " + strAvailableVersion); final URL updateUrl = new URL(updateProvider, "/rest/update/provider/download/" + strAvailableVersion + (id != null ? id.toQueryString() : "") + (id == null ? "?" : "&")+"allowRedirect=true" ); log(Logger.LOG_DEBUG, "download update from " + updateUrl); System.out.println(updateUrl); // local resource final File patchDir = getPatchDirectory(); final File newLucee = new File(patchDir, strAvailableVersion + (".lco")); //// int code; HttpURLConnection conn; try { conn = (HttpURLConnection) updateUrl.openConnection(); conn.setRequestMethod("GET"); conn.connect(); code = conn.getResponseCode(); } catch (final UnknownHostException e) { log(e); throw e; } // the update provider is not providing a download for this if (code != 200) { // the update provider can also provide a different (final) location for this if(code==302) { String location = conn.getHeaderField("Location"); // just in case we check invalid names if(location==null)location = conn.getHeaderField("location"); if(location==null)location = conn.getHeaderField("LOCATION"); System.out.println("download redirected:" + location); // MUST remove log(Logger.LOG_DEBUG, "download redirected to " + updateUrl); conn.disconnect(); URL url = new URL(location); try { conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.connect(); code = conn.getResponseCode(); } catch (final UnknownHostException e) { log(e); throw e; } } // no download available! if(code != 200){ final String msg = "Lucee is not able do download the core for version [" + version.toString() + "] from " + updateUrl + ", please donwload it manually and copy to [" + patchDir + "]"; log(Logger.LOG_ERROR, msg); conn.disconnect(); throw new IOException(msg); } } // copy it to local directory if (newLucee.createNewFile()) { copy((InputStream) conn.getContent(), new FileOutputStream(newLucee)); conn.disconnect(); // when it is a loader extract the core from it File tmp = extractCoreIfLoader(newLucee); if(tmp!=null) { System.out.println("extract core from loader"); // MUST remove log(Logger.LOG_DEBUG, "extract core from loader"); newLucee.delete(); tmp.renameTo(newLucee); tmp.delete(); System.out.println("exist?"+newLucee.exists()); // MUST remove } } else { conn.disconnect(); log(Logger.LOG_DEBUG, "File for new Version already exists, won't copy new one"); return null; } return newLucee; } public static File extractCoreIfLoader(File file) { try { return _extractCoreIfLoader(file); } catch (IOException e) { e.printStackTrace(); return null; } } public static File _extractCoreIfLoader(File file) throws IOException { JarFile jf = new JarFile(file); try{ // is it a lucee loader ? String value = jf.getManifest().getMainAttributes().getValue("Main-Class"); if(Util.isEmpty(value) || !value.equals("lucee.runtime.script.Main")) return null; // get the core file; JarEntry je = jf.getJarEntry("core/core.lco"); if(je==null) return null; InputStream is = jf.getInputStream(je); File trg = File.createTempFile("lucee", ".lco"); OutputStream os=new FileOutputStream(trg); try{ Util.copy(is, os); } finally { Util.closeEL(is); Util.closeEL(os); } return trg; } finally { jf.close(); } } public URL getUpdateLocation() throws MalformedURLException { URL location = singelton == null ? null : singelton.getUpdateLocation(); // read location directly from xml if (location == null) { final InputStream is = null; try { final File xml = new File(getResourceRoot(), "context/lucee-server.xml"); if (xml.exists() || xml.length() > 0) { final DocumentBuilderFactory dbFactory = DocumentBuilderFactory .newInstance(); final DocumentBuilder dBuilder = dbFactory .newDocumentBuilder(); final Document doc = dBuilder.parse(xml); final Element root = doc.getDocumentElement(); final NodeList children = root.getChildNodes(); for (int i = children.getLength() - 1; i >= 0; i--) { final Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("update")) { final String loc = ((Element) node) .getAttribute("location"); if (!Util.isEmpty(loc)) location = new URL(loc); } } } } catch (final Throwable t) { t.printStackTrace(); } finally { CFMLEngineFactorySupport.closeEL(is); } } // if there is no lucee-server.xml if (location == null) location = new URL(UPDATE_LOCATION); return location; } /** * 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(final Password password) throws IOException, ServletException { if (!singelton.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(final Password password) throws IOException, ServletException { if (!singelton.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 { final File patchDir = getPatchDirectory(); final File[] patches = patchDir.listFiles(new ExtensionFilter( new String[] { "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 { final File patchDir = getPatchDirectory(); final File[] patches = patchDir.listFiles(new ExtensionFilter( new String[] { ".lco" })); File patch = null; for (final File patche : patches) if (patch == null || Util.isNewerThan(toVersion(patche.getName(), VERSION_ZERO), toVersion(patch.getName(), VERSION_ZERO))) patch = patche; if (patch != null && !patch.delete()) patch.deleteOnExit(); _restart(); return true; } public String[] getInstalledPatches() throws ServletException, IOException { final File patchDir = getPatchDirectory(); final File[] patches = patchDir.listFiles(new ExtensionFilter( new String[] { ".lco" })); final List<String> list = new ArrayList<String>(); String name; final int extLen = "rc".length() + 1; for (final File patche : patches) { name = patche.getName(); name = name.substring(0, name.length() - extLen); list.add(name); } final 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(final CFMLEngine engine) { final Iterator<EngineChangeListener> it = listeners.iterator(); while (it.hasNext()) it.next().onUpdate(); } public File getPatchDirectory() throws IOException { File pd = getDirectoryByProp("lucee.patches.dir"); if (pd != null) return pd; pd = new File(getResourceRoot(), "patches"); if (!pd.exists()) pd.mkdirs(); return pd; } public File getBundleDirectory() throws IOException { File bd = getDirectoryByProp("lucee.bundles.dir"); if (bd != null) return bd; bd = new File(getResourceRoot(), "bundles"); if (!bd.exists()) bd.mkdirs(); return bd; } public File getFelixCacheDirectory() throws IOException { return getResourceRoot(); //File bd = new File(getResourceRoot(),"felix-cache"); //if(!bd.exists())bd.mkdirs(); //return bd; } /** * return directory to lucee resource root * * @return lucee root directory * @throws IOException */ public File getResourceRoot() throws IOException { if (resourceRoot == null) { resourceRoot = new File(_getResourceRoot(), "lucee-server"); if (!resourceRoot.exists()) resourceRoot.mkdirs(); } return resourceRoot; } /** * @return return running context root * @throws IOException * @throws IOException */ private File _getResourceRoot() throws IOException { // custom configuration if (luceeServerRoot == null) readInitParam(config); if (luceeServerRoot != null) return luceeServerRoot; File lbd = getDirectoryByProp("lucee.base.dir"); // directory defined by the caller if(lbd==null) lbd = getDirectoryByEnv("lucee.base.dir"); // directory defined by the caller File root=lbd; // get the root directory if (root == null) root = getDirectoryByProp("jboss.server.home.dir"); // Jboss/Jetty|Tomcat if (root == null) root = getDirectoryByProp("jonas.base"); // Jonas if (root == null) root = getDirectoryByProp("catalina.base"); // Tomcat if (root == null) root = getDirectoryByProp("jetty.home"); // Jetty if (root == null) root = getDirectoryByProp("org.apache.geronimo.base.dir"); // Geronimo if (root == null) root = getDirectoryByProp("com.sun.aas.instanceRoot"); // Glassfish if (root == null) root = getDirectoryByProp("env.DOMAIN_HOME"); // weblogic if (root == null) root = getClassLoaderRoot(mainClassLoader).getParentFile() .getParentFile(); final File classicRoot = getClassLoaderRoot(mainClassLoader); // in case of a war file the server root need to be with the context if(lbd==null) { File webInf=getWebInfFolder(classicRoot); if(webInf!=null) { root=webInf; if(!root.exists())root.mkdir(); System.out.println("war-root-directory:" + root); } } System.out.println("root-directory:" + root); if (root == null) throw new IOException( "can't locate the root of the servlet container, please define a location (physical path) for the server configuration" +" with help of the servlet init param [lucee-server-directory] in the web.xml where the Lucee Servlet is defined" +" or the system property [lucee.base.dir]."); final File modernDir = new File(root, "lucee-server"); if (true) { // there is a server context in the old lucee location, move that one File classicDir; System.out.println("classic-root-directory:" + classicRoot); boolean had = false; if (classicRoot.isDirectory() && (classicDir = new File(classicRoot, "lucee-server")) .isDirectory()) { System.out.println("had lucee-server classic" + classicDir); moveContent(classicDir, modernDir); had = true; } // there is a railo context if (!had && classicRoot.isDirectory() && (classicDir = new File(classicRoot, "railo-server")) .isDirectory()) { System.out.println("had railo-server classic" + classicDir); // check if there is a Railo context copyRecursiveAndRename(classicDir, modernDir); // zip the railo-server di and delete it (optional) try { ZipUtil.zip(classicDir, new File(root, "railo-server-context-old.zip")); Util.delete(classicDir); } catch (final Throwable t) { t.printStackTrace(); } //moveContent(classicDir,new File(root,"lucee-server")); } } return root; } private static File getWebInfFolder(File file) { File parent; while(file!=null && !file.getName().equals("WEB-INF")) { parent=file.getParentFile(); if(file.equals(parent)) return null; // this should not happen, simply to be sure file=parent; } return file; } private static void copyRecursiveAndRename(final File src, File trg) throws IOException { if (!src.exists()) return; if (src.isDirectory()) { if (!trg.exists()) trg.mkdirs(); final File[] files = src.listFiles(); for (final File file : files) copyRecursiveAndRename(file, new File(trg, file.getName())); } else if (src.isFile()) { if (trg.getName().endsWith(".rc") || trg.getName().startsWith(".")) return; if (trg.getName().equals("railo-server.xml")) { trg = new File(trg.getParentFile(), "lucee-server.xml"); // cfLuceeConfiguration final FileInputStream is = new FileInputStream(src); final FileOutputStream os = new FileOutputStream(trg); try { String str = Util.toString(is); str = str .replace("<cfRailoConfiguration", "<!-- copy from Railo context --><cfLuceeConfiguration"); str = str.replace("</cfRailoConfiguration", "</cfLuceeConfiguration"); str = str .replace("<railo-configuration", "<!-- copy from Railo context --><cfLuceeConfiguration"); str = str.replace("</railo-configuration", "</cfLuceeConfiguration"); str = str.replace("{railo-config}", "{lucee-config}"); str = str.replace("{railo-server}", "{lucee-server}"); str = str.replace("{railo-web}", "{lucee-web}"); str = str.replace("\"railo.commons.", "\"lucee.commons."); str = str.replace("\"railo.runtime.", "\"lucee.runtime."); str = str.replace("\"railo.cfx.", "\"lucee.cfx."); str = str .replace("/railo-context.ra", "/lucee-context.lar"); str = str.replace("/railo-context", "/lucee"); str = str.replace("railo-server-context", "lucee-server"); str = str.replace("http://www.getrailo.org", "http://release.lucee.org"); str = str.replace("http://www.getrailo.com", "http://release.lucee.org"); final ByteArrayInputStream bais = new ByteArrayInputStream( str.getBytes()); try { Util.copy(bais, os); bais.close(); } finally { Util.closeEL(is, os); } } finally { Util.closeEL(is, os); } return; } final FileInputStream is = new FileInputStream(src); final FileOutputStream os = new FileOutputStream(trg); try { Util.copy(is, os); } finally { Util.closeEL(is, os); } } } private void moveContent(final File src, final File trg) throws IOException { if (src.isDirectory()) { final File[] children = src.listFiles(); if (children != null) for (final File element : children) moveContent(element, new File(trg, element.getName())); src.delete(); } else if (src.isFile()) { trg.getParentFile().mkdirs(); src.renameTo(trg); } } private File getDirectoryByProp(final String name) { return _getDirectoryBy(System.getProperty(name)); } private File getDirectoryByEnv(final String name) { return _getDirectoryBy(System.getenv(name)); } private File _getDirectoryBy(final String value) { if (Util.isEmpty(value, true)) return null; final File dir = new File(value); dir.mkdirs(); if (dir.isDirectory()) return dir; return null; } /** * returns the path where the classloader is located * * @param cl ClassLoader * @return file of the classloader root */ public static File getClassLoaderRoot(final ClassLoader cl) { final String path = "lucee/loader/engine/CFMLEngine.class"; final URL res = cl.getResource(path); if (res == null) return null; // get file and remove all after ! String strFile = null; try { strFile = URLDecoder.decode(res.getFile().trim(), "iso-8859-1"); } catch (final 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 lucee.jar at the end if (strFile.startsWith("file:")) strFile = strFile.substring(5); if (strFile.endsWith("lucee.jar")) strFile = strFile.substring(0, strFile.length() - 9); File file = new File(strFile); if (file.isFile()) file = file.getParentFile(); return file; } /** * Load CFMl Engine Implementation (lucee.runtime.engine.CFMLEngineImpl) * from a Classloader * * @param bundle * @return * @throws ClassNotFoundException * @throws SecurityException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws IllegalAccessException * @throws InvocationTargetException */ private CFMLEngine getEngine(final BundleCollection bc) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { log(Logger.LOG_DEBUG, "state:" + BundleUtil.bundleState(bc.core.getState(), "")); //bundle.getBundleContext().getServiceReference(CFMLEngine.class.getName()); log(Logger.LOG_DEBUG, Constants.FRAMEWORK_BOOTDELEGATION + ":" + bc.getBundleContext().getProperty( Constants.FRAMEWORK_BOOTDELEGATION)); log(Logger.LOG_DEBUG, "felix.cache.rootdir:" + bc.getBundleContext().getProperty("felix.cache.rootdir")); //log(Logger.LOG_DEBUG,bc.master.loadClass(TP.class.getName()).getClassLoader().toString()); final Class<?> clazz = bc.core .loadClass("lucee.runtime.engine.CFMLEngineImpl"); log(Logger.LOG_DEBUG, "class:" + clazz.getName()); final Method m = clazz.getMethod("getInstance", new Class[] { CFMLEngineFactory.class, BundleCollection.class }); return (CFMLEngine) m.invoke(null, new Object[] { this, bc }); } private class UpdateChecker extends Thread { private final CFMLEngineFactory factory; private final Identification id; private UpdateChecker(final CFMLEngineFactory factory, final Identification id) { this.factory = factory; this.id = id; } @Override public void run() { long time = 10000; while (true) try { sleep(time); time = 1000 * 60 * 60 * 24; factory._update(id); } catch (final Exception e) { } } } }