Revision: 4616 http://sourceforge.net/p/vexi/code/4616 Author: mkpg2 Date: 2013-12-19 18:02:49 +0000 (Thu, 19 Dec 2013) Log Message: ----------- Alternative downloadable launcher. Refactor. Split out common code from the applet.
Modified Paths: -------------- trunk/org.vexi-launcher/meta/module.xml trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Launcher.java trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Splash.java trunk/org.vexi-launcher/src/test/java/org/vexi/launcher/TestLauncher.java Added Paths: ----------- trunk/org.vexi-launcher/Launcher.java trunk/org.vexi-launcher/LauncherApplet.java trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherApplet.java trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherMain.java trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/Problem.java trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/StreamReader.java Added: trunk/org.vexi-launcher/Launcher.java =================================================================== --- trunk/org.vexi-launcher/Launcher.java (rev 0) +++ trunk/org.vexi-launcher/Launcher.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -0,0 +1,463 @@ +package org.vexi.launcher; + +import java.applet.Applet; +import java.awt.Color; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Vector; + +import org.vexi.DotVexi; + +/** + * @author m...@webenableit.co.uk + * @contributor char...@webenableit.co.uk + * + * <p>The launcher is responsible for downloading the Vexi Core + * and the principle application components (.vexi files) + * and launching it as a separate process so that it can + * exist and survive outside of the restrictive environment + * that is imposed upon applets.</p> + * + */ +abstract public class Launcher { + + // REMARK although a signed applet doesn't have any known securities + // we restrict it to running from domains that we control because in + // the event that it is compromised we can update the copy on these + // domains. In this way the fetching of the applet acts as the security + // check. + abstract public Color getTextColor(); + abstract public Color getBorderColor(); + abstract public Color getBarColor(); + abstract public URL getSplashImageResource(); + abstract public String[] getPermittedDomains(); + abstract public Map getCerts(); + abstract public String getVersion(); + + //////////////// + // INIT + final LauncherApplet applet; + DotVexi dotvexi; + String fetchCount = "?"; + int fetchIndex = 1; + public Launcher(LauncherApplet applet) { + this.applet = applet; + } + + static protected void logflush() { + System.err.flush(); + } + + static protected void log(String s) { + System.err.println(s); + } + + + static private String getEnv(String name) { + try { + return System.getenv(name); + } catch(Throwable t) { + // REMARK - they deprecated this in 1.4, only to bring it back in 1.5. + // By deprecated we mean it throws an error. So here we fall back to + // getting it from the commandline. + try { + String os = System.getProperty("os.name").toLowerCase(); + Process p; + String cmd; + if (os.indexOf("windows 9") != -1 || os.indexOf("windows me") != -1) { + cmd = "command.com /c set"; + } else if (os.indexOf("windows") > -1) { + cmd = "cmd.exe /c set"; + } else { + cmd = "env"; + } + p = Runtime.getRuntime().exec(cmd); + BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String s; + while ((s = br.readLine()) != null) { + if (s.startsWith(name + "=")) { + return s.substring(name.length() + 1); + } + } + } catch (Throwable t2) { + log("Encountered an exception during fallback discovery of: '"+name+"'"); + t2.printStackTrace(); + } + return null; + } + } + + /** generates the basic command string to start java */ + static private String findInJavaHome(String os_name, String javaHome) { + if (javaHome != null && !javaHome.equals("")) { + String r = javaHome + File.separatorChar + "bin" + File.separatorChar + "java"; + if (os_name.indexOf("windows") != -1) { + r += ".exe"; + } + if (new File(r).exists()) { + return r; + } + } + return null; + } + + /** searches for the JVM binary in the usual places + * @throws Problem */ + static private String findJvmBinary() throws Exception { + log("-- Locating the Java Binary --"); + String jvmBinary = null; + String os_name = System.getProperty("os.name", "").toLowerCase(); + + log("Trying Environment Variable 'VEXI_JRE'"); + jvmBinary = findInJavaHome(os_name,getEnv("VEXI_JRE")); + if (jvmBinary != null) { + return jvmBinary; + } + + log("Trying Environment Variable 'JAVA_HOME'"); + jvmBinary = findInJavaHome(os_name,getEnv("JAVA_HOME")); + if (jvmBinary != null) { + return jvmBinary; + } + + log("Trying System Property 'java.home'"); + jvmBinary = findInJavaHome(os_name,System.getProperty("java.home")); + if (jvmBinary != null) { + return jvmBinary; + } + + // check PATH + log("Checking to see if java is on the PATH Environment Variable"); + String path = getEnv("PATH"); + if (path == null) { + path = getEnv("Path"); + } + if (path!=null) { + StringTokenizer st = new StringTokenizer(path, File.pathSeparatorChar + ""); + while (st.hasMoreTokens()) { + String s = st.nextToken(); + if (new File(s + File.separatorChar + "java").exists() || new File(s + File.separatorChar + "java.exe").exists() ) { + jvmBinary = s + File.separatorChar + "java"; + if (os_name.indexOf("windows") != -1) { + jvmBinary += ".exe"; + } + return jvmBinary; + } + } + } + + throw new Problem("Couldn't find a suitable JVM binary! See console log for details"); + } + + long lastDate = -1; + protected InputStream getInputStream(String url) throws IOException{ + final URLConnection uc = connect(url); + InputStream is = uc.getInputStream(); + int contentLength = uc.getContentLength(); + lastDate = uc.getLastModified(); + return progressInputStream(is, url, contentLength); + } + + public void initDotVexi() { + dotvexi = new DotVexi(getCerts()) { + + protected void log(String s) { Launcher.log(s); } + + protected InputStream getInputStream(Object fountain, Object principal) throws IOException { + String url = (String)fountain; + return Launcher.this.getInputStream(url); + } + protected long getRemoteDate(Object fountain, Object principal) { + return lastDate; + } + }; + } + + public void initSplash(){ + Splash.init(getVersion(),getSplashImageResource(), getTextColor(), getBorderColor(), getBarColor()); + } + + + private InputStream progressInputStream(InputStream is, final String displayname, final int contentLength ) { + final String left = "Downloading: "+displayname; + final String right = fetchIndex+"/"+fetchCount; + + return new FilterInputStream(new BufferedInputStream(is)) { + int total = 0; + int percent = 0; + private void display() { + double loaded = ((double)total) / ((double)contentLength); + int newpercent = ((int)Math.ceil(loaded * 100)); + if (newpercent!=percent) { + percent = newpercent; + + //log(percent + "%"); + Splash.update(left, right, (double)percent); + } + } + + public int read() throws IOException { + int ret = super.read(); + if (ret != -1) { + total++; + } + display(); + return ret; + } + public int read(byte[] buf, int off, int len) throws IOException { + int ret = super.read(buf, off, len); + if (ret != -1) { + total += ret; + } + display(); + return ret; + } + }; + } + + /** check if a url falls within the permitted domains + * @param url a string of an absolute url, must start with 'http://' or 'file://' + * @return true iff url is permitted */ + protected boolean checkUrl(String url) { + if (url.startsWith("file")) { + return true; + } + if (url.startsWith("http")) { + // remove the protocol + url = url.substring(url.indexOf("//")+2); + while (true) { + String[] permittedDomains = getPermittedDomains(); + for (int i=0; i<permittedDomains.length; i++) { + if (url.startsWith(permittedDomains[i])) { + return true; + } + } + // if we are a subdomain remove leading part and recheck + if (url.indexOf('.')==-1) { + break; + } + url = url.substring(url.indexOf('.')+1); + } + } + return false; + } + + static private String join(String[] ss) { + String r = ""; + for (int i=0; i<ss.length; i++) { + if (i>0) { + r += ","; + } + r += ss[i]; + } + return r; + } + + /** fetches a file from the distribution site, writing it to the appropriate place */ + public File fetch(String url) throws IOException { + File localfile = dotvexi.getLocalFile(url); + dotvexi.fetch(url, null, url, localfile); + fetchIndex++; + return localfile; + } + + /** attempts to create a local file for logging vexi output to */ + protected File createLog(String logfile) throws IOException { + File dir = dotvexi.findSubDir("logs"); + dir.mkdirs(); + File f=null; + String fname = logfile; + for (int i=0; i<100; i++) { + f = new File(dir,fname); + if ((f.exists() && !f.delete())) { + fname = logfile+i; + continue; // file occupied! + } + if (!f.createNewFile()) { + break; + } + return f; + } + throw new RuntimeException("Could not create log file: " + f.getCanonicalPath()); + } + + + + static public class Problem extends Exception { + public Problem(String message) { super(message); } + } + + + + public void go( + String core, + String mem, + List<String> fetchVexis) { + //log("Codebase is "+url); + fetchCount = ""+(fetchVexis.size()+1); + + + String coreurl = core; + if (!coreurl.endsWith(".signed")) { + permitUnsignedCore(coreurl); + } + File corefile = fetch(coreurl); + + List command = new ArrayList(); + command.add(findJvmBinary()); + if (mem!=null) { + command.add("-Xmx" + mem); + } + command.add("-jar"); + command.add(corefile.getPath()); + + String logfile = getParameter("logfile"); + if (logfile!=null) { + createLog(logfile); + command.add("-l"); + command.add("logs/"+logfile); + } + command.add("-biscuitid"); + command.add(DotVexi.urlUniqueName(codebase)); + + + for (int i = 0; i<10000;i++) { + if (getParameter("option" + i) == null) { + break; + } + command.add(getParameter("option" + i)); + } + + + for (int i = 0; i<fetchVexis.size();i++) { + File f = fetch((String)fetchVexis.get(i)); + command.add(f.getPath()); + } + + + // REMARK - discovering os/architecture not relevant until + // native builds are working again + /* + String os_name = System.getProperty("os.name", "").toLowerCase(); + log("os.name == " + os_name); + Vector command = new Vector(); + String arch = null; + if (os_name.indexOf("linux") != -1) { + arch = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec("/bin/uname -m").getInputStream())).readLine(); + log("arch is " + arch); + }*/ + + spawn(command); + } + + /** spawns the Vexi core + * @param command contains the full command - including java */ + private void spawn(List<String> command) throws IOException { + File dir = dotvexi.getBaseDir(); + String[] command_vec = command.toArray(new String[command.size()]); + log("in directory : " + dir.getCanonicalPath()); + log("executing : "); + for (int i=0; i<command_vec.length; i++) { + log(" \"" + command_vec[i] + "\""); + } + + final Process p = Runtime.getRuntime().exec(command_vec,null,dir); + + new Thread(new Runnable() { + public void run() { + // catch output + StreamReader errorstream = new StreamReader(p.getErrorStream(), "ERROR", true); + StreamReader outputstream = new StreamReader(p.getInputStream(), "OUTPUT", false); + // kick off output streams + errorstream.start(); + outputstream.start(); + try { + updateStatus("Vexi loaded"); + int exitValue = p.waitFor(); + if (exitValue != 0) { + updateError("Vexi exited abnormally with error code '"+exitValue+"', see log output"); + } else { + updateStatus("Vexi has finished running"); + } + } catch (Throwable t) { + log("Error exiting... " + p.exitValue()); + t.printStackTrace(); + } + log("Exiting..."); + logflush(); + } + }).start(); + } + + + /** + * @author char...@webenableit.co.uk + * + * A very simple stream reader, copied from: + * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html + */ + static class StreamReader extends Thread { + InputStream is; + String type; + + StreamReader(InputStream is, String type, boolean listenDisplay) { + this.is = is; + this.type = type; + this.listenDisplay = listenDisplay; + } + private boolean listenDisplay; + + public void run() { + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line=null; + while ((line = br.readLine()) != null) { + if (listenDisplay && line.contains("**display launched**")) { + Splash.close(); + } + log(type + ">" + line); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + } + + + //////////////////// + //// Utility + static private URLConnection connect(String url) throws IOException { + try { + URL u = new URL(url); + final URLConnection uc = u.openConnection(); + uc.setUseCaches(false); // don't use the possibly short Java cache + uc.connect(); + return uc; + } catch (NullPointerException npe) { + // WORKAROUND - sun libraries throw a NPE rather than + // a more informative exception. + throw new IOException("Invalid url: "+url); + } + } + + ////////////// + // Permission + void permitAlternativeURL(String url) throws Problem { + throw new Problem("Applet can not be run from unknown domain " + url + "\nPermitted domains: " + join(getPermittedDomains())); + } + void permitUnsignedCore(String file) throws Problem { + throw new Problem("Applet will not run unsigned core file: "+ file); + } +} Property changes on: trunk/org.vexi-launcher/Launcher.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: trunk/org.vexi-launcher/LauncherApplet.java =================================================================== --- trunk/org.vexi-launcher/LauncherApplet.java (rev 0) +++ trunk/org.vexi-launcher/LauncherApplet.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -0,0 +1,153 @@ +package org.vexi.launcher; + +import java.applet.Applet; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Vector; + +import org.vexi.DotVexi; +import org.vexi.launcher.Launcher.Problem; + +/** + * @author m...@webenableit.co.uk + * @contributor char...@webenableit.co.uk + * + * <p>The launcher is responsible for downloading the Vexi Core + * and the principle application components (.vexi files) + * and launching it as a separate process so that it can + * exist and survive outside of the restrictive environment + * that is imposed upon applets.</p> + * + */ +abstract public class LauncherApplet extends Applet { + abstract protected Launcher createLauncher(); + + /////////////////////// + // APPLET + /////////////////////// + + private Launcher launcher; + + /** applet entry point - this is where it all begins */ + public final void init() { + launcher = createLauncher(); + launcher.initSplash(); + launcher.initDotVexi(); + + // the rest is initialized outside of the restrictive applet sandbox + // as we need to download several files and start up a new process + // REMARK: this requires the applet to be signed, an unsigned applet + // will most likely fail very soon after this next thread is started + new Thread() { + public void run() { + java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { + public Object run() { + launcher.go(); + return null; + } + }); + } + }.start(); + + } + + void log(String s){ Launcher.log(s);} + + public void go() { + try{ + log("Launcher Build : "+launcher.getVersion()); + + // Check codebase is permitted + String codebase = getCodeBase()+""; + updateStatus(""+codebase); + if (!checkUrl(codebase)) { + permitAlternativeURL(codebase); + } + + String core = getParameter("core"); + if (core==null) { + throw new Problem("Core property not set"); + } + + List<String> fetchVexis = new ArrayList(10); + for (int i = 0; i<10000;i++) { + String fetch = getParameter("vexi" + i); + if (fetch == null) { + break; + } + fetchVexis.add(fetch); + } + + go(core, fetchVexis); + + } catch (Problem e) { + updateError(e.getMessage()); + log(e.getMessage()); + } catch (Throwable e) { + updateError("Error; please check the Java console"); + e.printStackTrace(); + } finally { + Splash.close(); + } + } + + + + final Font font = new Font("Sans-serif", Font.BOLD, 12); + final private Object statusLock = new Object(); + private String statusText; + private Color statusColor; + private Image backbuffer = null; + + protected void updateStatus(String statusText) { + updateStatus(statusText, Color.black); + } + protected void updateError(String statusText) { + Launcher.log("Error: "+statusText); + updateStatus(statusText, Color.red); + } + + void updateStatus(String statusText, Color statusColor) { + synchronized (statusLock) { + this.statusText = statusText; + this.statusColor = statusColor; + } + repaint(); + } + + + public final void paint(Graphics g) { update(g); } + public final void update(Graphics g2) { + if (backbuffer == null || backbuffer.getWidth(null) != getSize().width || backbuffer.getHeight(null) != getSize().height) { + backbuffer = createImage(getSize().width, getSize().height); + } + if (backbuffer == null) { + return; + } + Graphics2D g = (Graphics2D)backbuffer.getGraphics(); + + Color color; + String text; + synchronized(statusLock) { + text = statusText; + color = statusColor; + } + + g.setColor(Color.white); + g.fillRect(0, 0, getSize().width, getSize().height); + + int centerOffset = (getSize().width-(int)font.getStringBounds(text, g.getFontRenderContext()).getWidth())/2; + g.setColor(color); + g.setFont(font); + g.drawString(text, centerOffset, 15); + + g2.setClip(0, 0, getSize().width, getSize().height); + g2.drawImage(backbuffer, 0, 0, null); + } +} Property changes on: trunk/org.vexi-launcher/LauncherApplet.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Modified: trunk/org.vexi-launcher/meta/module.xml =================================================================== --- trunk/org.vexi-launcher/meta/module.xml 2013-12-19 13:26:44 UTC (rev 4615) +++ trunk/org.vexi-launcher/meta/module.xml 2013-12-19 18:02:49 UTC (rev 4616) @@ -1,7 +1,7 @@ <ebuild-module ebuild-version="0.8"> <artifact name="java_classes.jar" /> <dependencies> - <system name="java.jre" tag="1.4"/> + <system name="java.jre" tag="1.5"/> <dependency source="local" name="library.crypto" /> <!-- Test Dependencies --> <dependency source="local" name="library.testing" scope="test"/> Modified: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Launcher.java =================================================================== --- trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Launcher.java 2013-12-19 13:26:44 UTC (rev 4615) +++ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Launcher.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -1,11 +1,6 @@ package org.vexi.launcher; -import java.applet.Applet; import java.awt.Color; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; @@ -21,6 +16,8 @@ import java.util.Vector; import org.vexi.DotVexi; +import org.vexi.launcher.util.Problem; +import org.vexi.launcher.util.StreamReader; /** * @author m...@webenableit.co.uk @@ -33,7 +30,16 @@ * that is imposed upon applets.</p> * */ -abstract public class Launcher extends Applet { +abstract public class Launcher { + public interface Context { + public void flush(); + public void log(String msg); + public void warn(String msg); + public void error(String msg); + public void updateStatus(String msg); + public String getParameter(String s); + public void check() throws Problem; + } // REMARK although a signed applet doesn't have any known securities // we restrict it to running from domains that we control because in @@ -45,26 +51,35 @@ abstract public Color getBarColor(); abstract public URL getSplashImageResource(); abstract public String[] getPermittedDomains(); - abstract public Map getCerts(); + abstract public Map loadCerts() throws IOException; abstract public String getVersion(); //////////////// // INIT - + final protected Context context; DotVexi dotvexi; String fetchCount = "?"; int fetchIndex = 1; - static protected void logflush() { - System.err.flush(); + + public Launcher(Context feedback) { + this.context = feedback; + } + + protected void log(String s) { + context.log(s); } - - static protected void log(String s) { - System.err.println(s); + + public Map getCerts() { + try { + return loadCerts(); + } catch (Throwable e) { + context.error("Error: applet unable to load root certificates"); + throw new Error(e); + } } - - static private String getEnv(String name) { + private String getEnv(String name) { try { return System.getenv(name); } catch(Throwable t) { @@ -114,7 +129,7 @@ /** searches for the JVM binary in the usual places * @throws Problem */ - static private String findJvmBinary() throws Exception { + private String findJvmBinary() throws Exception { log("-- Locating the Java Binary --"); String jvmBinary = null; String os_name = System.getProperty("os.name", "").toLowerCase(); @@ -161,20 +176,23 @@ } + long lastDate = -1; + public InputStream getInputStream(String url) throws IOException{ + final URLConnection uc = connect(url); + InputStream is = uc.getInputStream(); + int contentLength = uc.getContentLength(); + lastDate = uc.getLastModified(); + return progressInputStream(is, url, contentLength); + } public void initDotVexi() { dotvexi = new DotVexi(getCerts()) { - long lastDate = -1; - protected void log(String s) { Launcher.log(s); } + protected void log(String s) { context.log(s); } protected InputStream getInputStream(Object fountain, Object principal) throws IOException { String url = (String)fountain; - final URLConnection uc = connect(url); - InputStream is = uc.getInputStream(); - int contentLength = uc.getContentLength(); - lastDate = uc.getLastModified(); - return progressInputStream(is, url, contentLength); + return Launcher.this.getInputStream(url); } protected long getRemoteDate(Object fountain, Object principal) { return lastDate; @@ -182,28 +200,12 @@ }; } - /** applet entry point - this is where it all begins */ - public final void init() { - Splash.init(getVersion(),getSplashImageResource(), getTextColor(), getBorderColor(), getBarColor()); - initDotVexi(); - - // the rest is initialized outside of the restrictive applet sandbox - // as we need to download several files and start up a new process - // REMARK: this requires the applet to be signed, an unsigned applet - // will most likely fail very soon after this next thread is started - new Thread() { - public void run() { - java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { - public Object run() { - - go(); - return null; - } - }); - } - }.start(); + + public void initSplash(){ + Splash.init(context, getVersion(),getSplashImageResource(), getTextColor(), getBorderColor(), getBarColor()); + } - } + private InputStream progressInputStream(InputStream is, final String displayname, final int contentLength ) { final String left = "Downloading: "+displayname; @@ -312,23 +314,17 @@ try { log("Launcher Build : "+getVersion()); - // Check codebase is permitted - String codebase = getCodeBase()+""; - updateStatus(""+codebase); - //log("Codebase is "+url); - - if (!checkUrl(codebase)) { - permitAlternativeURL(codebase); - } - - String core = getParameter("core"); + context.check(); + + + String core = context.getParameter("core"); if (core==null) { throw new Problem("Core property not set"); } ArrayList fetchVexis = new ArrayList(10); for (int i = 0; i<10000;i++) { - String fetch = getParameter("vexi" + i); + String fetch = context.getParameter("vexi" + i); if (fetch == null) { break; } @@ -345,27 +341,30 @@ Vector command = new Vector(); command.add(findJvmBinary()); - if (getParameter("mem") != null) { - command.add("-Xmx" + getParameter("mem")); + if (context.getParameter("mem") != null) { + command.add("-Xmx" + context.getParameter("mem")); } command.add("-jar"); command.add(corefile.getPath()); - String logfile = getParameter("logfile"); + String logfile = context.getParameter("logfile"); if (logfile!=null) { createLog(logfile); command.add("-l"); command.add("logs/"+logfile); } + + + command.add("-biscuitid"); - command.add(DotVexi.urlUniqueName(codebase)); + command.add(DotVexi.urlUniqueName(context.getParameter("origin"))); for (int i = 0; i<10000;i++) { - if (getParameter("option" + i) == null) { + if (context.getParameter("option" + i) == null) { break; } - command.add(getParameter("option" + i)); + command.add(context.getParameter("option" + i)); } @@ -389,20 +388,17 @@ spawn(command); } catch (Problem e) { - updateError(e.getMessage()); + context.error(e.getMessage()); log(e.getMessage()); } catch (Throwable e) { - updateError("Error; please check the Java console"); + context.error("Error; please check the Java console"); e.printStackTrace(); } finally { Splash.close(); } } + - static public class Problem extends Exception { - public Problem(String message) { super(message); } - } - /** spawns the Vexi core * @param command contains the full command - including java */ private void spawn(Vector command) throws IOException { @@ -420,127 +416,32 @@ new Thread(new Runnable() { public void run() { // catch output - StreamReader errorstream = new StreamReader(p.getErrorStream(), "ERROR", true); - StreamReader outputstream = new StreamReader(p.getInputStream(), "OUTPUT", false); + StreamReader errorstream = new StreamReader(context, p.getErrorStream(), "ERROR", true); + StreamReader outputstream = new StreamReader(context, p.getInputStream(), "OUTPUT", false); // kick off output streams errorstream.start(); outputstream.start(); try { - updateStatus("Vexi loaded"); + context.updateStatus("Vexi loaded"); int exitValue = p.waitFor(); if (exitValue != 0) { - updateError("Vexi exited abnormally with error code '"+exitValue+"', see log output"); + context.error("Vexi exited abnormally with error code '"+exitValue+"', see log output"); } else { - updateStatus("Vexi has finished running"); + context.updateStatus("Vexi has finished running"); } } catch (Throwable t) { - log("Error exiting... " + p.exitValue()); + context.error("Error exiting... " + p.exitValue()); t.printStackTrace(); } - log("Exiting..."); - logflush(); + context.log("Exiting..."); + context.flush(); } }).start(); } - /** - * @author char...@webenableit.co.uk - * - * A very simple stream reader, copied from: - * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html - */ - static class StreamReader extends Thread { - InputStream is; - String type; - - StreamReader(InputStream is, String type, boolean listenDisplay) { - this.is = is; - this.type = type; - this.listenDisplay = listenDisplay; - } - private boolean listenDisplay; - - public void run() { - try { - InputStreamReader isr = new InputStreamReader(is); - BufferedReader br = new BufferedReader(isr); - String line=null; - while ((line = br.readLine()) != null) { - if (listenDisplay && line.contains("**display launched**")) { - Splash.close(); - } - log(type + ">" + line); - } - } catch (IOException ioe) { - ioe.printStackTrace(); - } - } - } - /////////////////////// - // Display/Status - /////////////////////// - - final Font font = new Font("Sans-serif", Font.BOLD, 12); - final private Object statusLock = new Object(); - private String statusText; - private Color statusColor; - private Image backbuffer = null; - - /** controls the visual display of the applet - * - * @param percent - * -1 causes the applet to be displayed in red - * 0-100 causes the applet to be a blue progress bar - * @param text - * message or error to display in the applet - */ - protected void updateStatus(String statusText) { updateStatus(statusText, Color.black); } - protected void updateError(String statusText) { - System.err.println("Error: "+statusText); - updateStatus(statusText, Color.red); - } - private void updateStatus(String statusText, Color statusColor) { - synchronized (statusLock) { - this.statusText = statusText; - this.statusColor = statusColor; - } - repaint(); - } - - - public final void paint(Graphics g) { update(g); } - public final void update(Graphics g2) { - if (backbuffer == null || backbuffer.getWidth(null) != getSize().width || backbuffer.getHeight(null) != getSize().height) { - backbuffer = createImage(getSize().width, getSize().height); - } - if (backbuffer == null) { - return; - } - Graphics2D g = (Graphics2D)backbuffer.getGraphics(); - - Color color; - String text; - synchronized(statusLock) { - text = statusText; - color = statusColor; - } - - g.setColor(Color.white); - g.fillRect(0, 0, getSize().width, getSize().height); - - int centerOffset = (getSize().width-(int)font.getStringBounds(text, g.getFontRenderContext()).getWidth())/2; - g.setColor(color); - g.setFont(font); - g.drawString(text, centerOffset, 15); - - g2.setClip(0, 0, getSize().width, getSize().height); - g2.drawImage(backbuffer, 0, 0, null); - } - - //////////////////// //// Utility static private URLConnection connect(String url) throws IOException { Added: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherApplet.java =================================================================== --- trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherApplet.java (rev 0) +++ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherApplet.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -0,0 +1,130 @@ +package org.vexi.launcher; + +import java.applet.Applet; +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; + +import org.vexi.launcher.Launcher.Context; +import org.vexi.launcher.util.Problem; + +abstract public class LauncherApplet extends Applet implements Context{ + + Launcher launcher; + + abstract protected Launcher createLauncher(); + + + /** applet entry point - this is where it all begins */ + public final void init() { + launcher = createLauncher(); + launcher.initSplash(); + launcher.initDotVexi(); + + // the rest is initialized outside of the restrictive applet sandbox + // as we need to download several files and start up a new process + // REMARK: this requires the applet to be signed, an unsigned applet + // will most likely fail very soon after this next thread is started + new Thread() { + public void run() { + java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { + public Object run() { + launcher.go(); + return null; + } + }); + } + }.start(); + + } + public void check() throws Problem { + + String codebase = getCodeBase()+""; + updateStatus(""+codebase); + //log("Codebase is "+url); + + if (!launcher.checkUrl(codebase)) { + launcher.permitAlternativeURL(codebase); + } + } + + /////////////////////// + // Display/Status + /////////////////////// + + final Font font = new Font("Sans-serif", Font.BOLD, 12); + final private Object statusLock = new Object(); + private String statusText; + private Color statusColor; + private Image backbuffer = null; + + public String getOrigin() { + return ""+getCodeBase(); + } + + /** controls the visual display of the applet + * + * @param percent + * -1 causes the applet to be displayed in red + * 0-100 causes the applet to be a blue progress bar + * @param text + * message or error to display in the applet + */ + + public void log(String msg) { + System.err.println(msg); + } + public void flush() { + System.err.flush(); + } + + public void warn(String msg) { log("[WARNING]: "+msg); } + + public void error(String statusText) { + log("[ERROR]: "+statusText); + updateStatus(statusText, Color.red); + } + public void updateStatus(String statusText) { updateStatus(statusText, Color.black); } + private void updateStatus(String statusText, Color statusColor) { + synchronized (statusLock) { + this.statusText = statusText; + this.statusColor = statusColor; + } + repaint(); + } + + + public final void paint(Graphics g) { update(g); } + public final void update(Graphics g2) { + if (backbuffer == null || backbuffer.getWidth(null) != getSize().width || backbuffer.getHeight(null) != getSize().height) { + backbuffer = createImage(getSize().width, getSize().height); + } + if (backbuffer == null) { + return; + } + Graphics2D g = (Graphics2D)backbuffer.getGraphics(); + + Color color; + String text; + synchronized(statusLock) { + text = statusText; + color = statusColor; + } + + g.setColor(Color.white); + g.fillRect(0, 0, getSize().width, getSize().height); + + int centerOffset = (getSize().width-(int)font.getStringBounds(text, g.getFontRenderContext()).getWidth())/2; + g.setColor(color); + g.setFont(font); + g.drawString(text, centerOffset, 15); + + g2.setClip(0, 0, getSize().width, getSize().height); + g2.drawImage(backbuffer, 0, 0, null); + } + + + +} Property changes on: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherApplet.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherMain.java =================================================================== --- trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherMain.java (rev 0) +++ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherMain.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -0,0 +1,113 @@ +package org.vexi.launcher; + +import java.io.File; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.vexi.launcher.Launcher.Context; +import org.vexi.launcher.util.Problem; +import org.vexi.util.IOUtil; + +/** + * + * @author Mike Goodwin + * + */ +abstract public class LauncherMain implements Context{ + + private Map<String,String> params; + private String application; + private String hash; + + abstract public Launcher getLauncher(); + + + void init(String[] args){ + for(String s: args){ + String[] k_v = s.split("="); + String k = k_v[0]; + String v = k_v[1]; + if("application".equals(k)){ + this.application = v; + }else if("hash".equals(k)){ + this.hash = v; + } + } + } + + public void run(String[] args) throws Exception{ + getLauncher().initSplash(); + + File confFile = new File("conf"); + if(confFile.isFile()){ + String[] conf = IOUtil.fileToString(confFile).split("/n"); + init(conf); + }else{ + warn("conf file does not exist"); + } + + init(args); + if(application==null){ + error("'application' not specified (in conf or args)"); + System.exit(1); + } + + checkVersion(); + fetchArgs(); + getLauncher().initDotVexi(); + getLauncher().go(); + + } + + + String readString(String path) throws Exception{ + return IOUtil.readerToString(new InputStreamReader(getLauncher().getInputStream(path), "UTF8")); + } + + void checkVersion() throws Exception{ + String hashes = readString(application+"/launcherversions"); + if(hash==null){ + warn("no version check"); + }else{ + for(String hash: hashes.split("\n")){ + if(this.hash.equals(hash)){ + return; + } + } + log("** Launcher incompatible, please download new version **"); + log("our hash: "+hash); + log("allowed: "); + for(String hash: hashes.split("\n")){ + log(" "+hash); + } + System.exit(0); + } + } + + void fetchArgs() throws Exception{ + params = new LinkedHashMap(); + String args = readString(application+"/launcherargs"); + String[] argArr = args.split("\n"); + for(String s: argArr){ + if("".equals(s.trim())) continue; + + String[] k_v = s.split("=", 2); + if(k_v.length==2){ + params.put(k_v[0], k_v[1]); + }else{ + warn("invalid arg: "+s); + } + } + } + + public void flush() { System.err.flush(); } + public void log(String msg) { System.err.println(msg); } + public void warn(String msg) { log("[WARNING]: "+msg); } + public void error(String msg) { log("[ERROR]: "+msg); } + public void updateStatus(String msg) { log(msg); } + + public String getParameter(String key) { return params.get(key); } + + public void check() throws Problem {} +} Property changes on: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/LauncherMain.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Modified: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Splash.java =================================================================== --- trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Splash.java 2013-12-19 13:26:44 UTC (rev 4615) +++ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/Splash.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -15,6 +15,8 @@ import java.io.IOException; import java.net.URL; +import org.vexi.launcher.Launcher.Context; + /** * * @author Mike Goodwin @@ -29,24 +31,24 @@ static private SplashWindow initFallback() { return new Fallback(); } - static private SplashWindow initFast() { + static private SplashWindow initFast(Context context) { SplashScreen splash = SplashScreen.getSplashScreen(); if (splash==null) { - System.err.println("[warning] could not find fast splash screen, using old style"); + context.warn("could not find fast splash screen, using old style"); return initFallback(); } else { return new Fast(splash); } } - static public SplashWindow init(String version, URL splashImage, Color textcolor, Color bordercolor, Color barcolor) { + static public SplashWindow init(Context context, String version, URL splashImage, Color textcolor, Color bordercolor, Color barcolor) { Splash.version = version; Splash.textcolor = textcolor; Splash.bordercolor = bordercolor; Splash.barcolor = barcolor; try { Splash.class.getClassLoader().loadClass("java.awt.SplashScreen"); - instance = initFast(); + instance = initFast(context); } catch (ClassNotFoundException e) { instance = initFallback(); } Added: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/Problem.java =================================================================== --- trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/Problem.java (rev 0) +++ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/Problem.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -0,0 +1,5 @@ +package org.vexi.launcher.util; + +public class Problem extends Exception { + public Problem(String message) { super(message); } +} \ No newline at end of file Property changes on: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/Problem.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Added: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/StreamReader.java =================================================================== --- trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/StreamReader.java (rev 0) +++ trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/StreamReader.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -0,0 +1,45 @@ +package org.vexi.launcher.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.vexi.launcher.Launcher.Context; +import org.vexi.launcher.Splash; + +/** + * @author char...@webenableit.co.uk + * + * A very simple stream reader, copied from: + * http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html + */ +public class StreamReader extends Thread { + final Context feedback; + final InputStream is; + final String type; + + public StreamReader(Context feedback, InputStream is, String type, boolean listenDisplay) { + this.feedback = feedback; + this.is = is; + this.type = type; + this.listenDisplay = listenDisplay; + } + private boolean listenDisplay; + + public void run() { + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line=null; + while ((line = br.readLine()) != null) { + if (listenDisplay && line.contains("**display launched**")) { + Splash.close(); + } + feedback.log(type + ">" + line); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } +} \ No newline at end of file Property changes on: trunk/org.vexi-launcher/src/main/java/org/vexi/launcher/util/StreamReader.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Modified: trunk/org.vexi-launcher/src/test/java/org/vexi/launcher/TestLauncher.java =================================================================== --- trunk/org.vexi-launcher/src/test/java/org/vexi/launcher/TestLauncher.java 2013-12-19 13:26:44 UTC (rev 4615) +++ trunk/org.vexi-launcher/src/test/java/org/vexi/launcher/TestLauncher.java 2013-12-19 18:02:49 UTC (rev 4616) @@ -10,13 +10,13 @@ public void testCheckUrls(){ - Launcher l = new Launcher(){ + Launcher l = new Launcher(null){ public Color getTextColor() { return null; } public Color getBorderColor() { return null; } public Color getBarColor() { return null; } public URL getSplashImageResource() { return null; } public String[] getPermittedDomains() { return new String[]{"localhost"}; } - public Map getCerts() { return null; } + public Map loadCerts() { return null; } public String getVersion() { return null; } }; boolean ok = l.checkUrl("http://localhost:7070/"); @@ -24,13 +24,13 @@ } public void testCheckUrlsSubdomain(){ - Launcher l = new Launcher(){ + Launcher l = new Launcher(null){ public Color getTextColor() { return null; } public Color getBorderColor() { return null; } public Color getBarColor() { return null; } public URL getSplashImageResource() { return null; } public String[] getPermittedDomains() { return new String[]{"emanate5.com"}; } - public Map getCerts() { return null; } + public Map loadCerts() { return null; } public String getVersion() { return null; } }; boolean ok = l.checkUrl("http://blah.emanate5.com/foo/x"); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Rapidly troubleshoot problems before they affect your business. Most IT organizations don't have a clear picture of how application performance affects their revenue. With AppDynamics, you get 100% visibility into your Java,.NET, & PHP application. Start your 15-day FREE TRIAL of AppDynamics Pro! http://pubads.g.doubleclick.net/gampad/clk?id=84349831&iu=/4140/ostg.clktrk _______________________________________________ Vexi-svn mailing list Vexi-svn@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/vexi-svn