summaryrefslogtreecommitdiffstats
path: root/src/classes/com/sun
diff options
context:
space:
mode:
Diffstat (limited to 'src/classes/com/sun')
-rwxr-xr-xsrc/classes/com/sun/opengl/utils/JOGLAppletLauncher.java702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/classes/com/sun/opengl/utils/JOGLAppletLauncher.java b/src/classes/com/sun/opengl/utils/JOGLAppletLauncher.java
new file mode 100755
index 000000000..c3899e36c
--- /dev/null
+++ b/src/classes/com/sun/opengl/utils/JOGLAppletLauncher.java
@@ -0,0 +1,702 @@
+/* This java class is distributed under the BSD license.
+ *
+ * Copyright 2005 Lilian Chamontin.
+ * contact lilian.chamontin at f r e e . f r
+ */
+
+/*
+ * Portions Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistribution of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistribution in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+ * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+ * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+ * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+ * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+ * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed or intended for use
+ * in the design, construction, operation or maintenance of any nuclear
+ * facility.
+ */
+
+package com.sun.opengl.utils;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Label;
+import java.awt.Panel;
+import java.applet.Applet;
+import java.applet.AppletStub;
+import java.applet.AppletContext;
+import java.io.*;
+import java.net.*;
+import java.security.cert.*;
+import java.util.*;
+import java.util.jar.*;
+import javax.swing.*;
+
+import javax.media.opengl.*;
+
+
+/** Basic JOGL installer for Applets. The key functionality this class
+ * supplies is the ability to deploy unsigned applets which use JOGL.
+ * It may also be used to deploy signed applets in which case
+ * multiple security dialogs will be displayed. <p>
+ *
+ * On the server side the codebase must contain jogl.jar and all of
+ * the jogl-natives-*.jar files from the standard JOGL distribution.
+ * This is the location from which the JOGL library used by the
+ * applet is downloaded. The codebase additionally contains the jar
+ * file of the user's potentially untrusted applet. The jogl.jar and
+ * all jogl-natives jars must be signed by the same entity, which is
+ * typically Sun Microsystems, Inc.
+ *
+ * Sample applet code:
+ * <pre>
+ * <applet code="com.sun.opengl.utils.JOGLAppletInstaller"
+ * width=600
+ * height=400
+ * codebase="/lib"
+ * archive="jogl.jar,your_applet.jar">
+ * <param name="subapplet.classname" VALUE="untrusted.JOGLApplet">
+ * <param name="subapplet.displayname" VALUE="My JOGL Applet">
+ * <param name="progressbar" value="true">
+ * <param name="cache_archive" VALUE="jogl.jar,your_applet.jar">
+ * <param name="cache_archive_ex" VALUE="jogl.jar;preload,your_applet.jar;preload">
+ * </applet>
+ * </pre>
+ * <p>
+ *
+ * There are some limitations with this approach. It is not possible
+ * to specify e.g. -Dsun.java2d.noddraw=true or
+ * -Dsun.java2d.opengl=true for better control over the Java2D
+ * pipeline as it is with Java Web Start. There appear to be issues
+ * with multiple JOGL-based applets on the same web page, though
+ * multiple instances of the same applet appear to work. The latter
+ * may simply be a bug which needs to be fixed. <p>
+ *
+ * The JOGL natives are cached in the user's home directory (the value
+ * of the "user.home" system property in Java) under the directory
+ * .jogl_ext. The Java Plug-In is responsible for performing all other
+ * jar caching. If the JOGL installation is updated on the server, the
+ * .jogl_ext cache will automatically be updated. <p>
+ *
+ * This technique requires that JOGL has not been installed in to the
+ * JRE under e.g. jre/lib/ext. If problems are seen when deploying
+ * this applet launcher, the first question to ask the end user is
+ * whether jogl.jar and any associated DLLs, .so's, etc. are installed
+ * directly in to the JRE. The applet launcher has been tested
+ * primarily under Mozilla and Firefox; there may be problems when
+ * running under, for example, Opera. <p>
+ *
+ * @author Lilian Chamontin
+ * @author Kenneth Russell
+ */
+public class JOGLAppletLauncher extends Applet {
+ static {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception ignore) {
+ }
+ }
+
+ // metadata for native libraries
+ private static class NativeLibInfo {
+ private String osName;
+ private String osArch;
+ private String nativeJar;
+ private String nativePrefix;
+ private String nativeSuffix;
+
+ public NativeLibInfo(String osName, String osArch, String nativeJar, String nativePrefix, String nativeSuffix) {
+ this.osName = osName;
+ this.osArch = osArch;
+ this.nativeJar = nativeJar;
+ this.nativePrefix = nativePrefix;
+ this.nativeSuffix = nativeSuffix;
+ }
+
+ public boolean matchesOSAndArch(String osName, String osArch) {
+ if (osName.startsWith(this.osName)) {
+ if ((this.osArch == null) ||
+ (osArch.startsWith(this.osArch))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean matchesNativeLib(String nativeLibraryName) {
+ if (nativeLibraryName.toLowerCase().endsWith(nativeSuffix)) {
+ return true;
+ }
+ return false;
+ }
+
+ public String getNativeJarName() {
+ return nativeJar;
+ }
+
+ public String getNativeLibName(String baseName) {
+ return nativePrefix + baseName + nativeSuffix;
+ }
+
+ public boolean isMacOS() {
+ return (osName.equals("mac"));
+ }
+ }
+
+ private static final NativeLibInfo[] allNativeLibInfo = {
+ new NativeLibInfo("win", null, "jogl-natives-win32.jar", "", ".dll"),
+ new NativeLibInfo("mac", null, "jogl-natives-macosx.jar", "lib", ".jnilib"),
+ new NativeLibInfo("linux", null, "jogl-natives-linux.jar", "lib", ".so"),
+ new NativeLibInfo("sunos", "sparc", "jogl-natives-solsparc.jar", "lib", ".so"),
+ new NativeLibInfo("sunos", "x86", "jogl-natives-solsparc.jar", "lib", ".so")
+ };
+
+ private NativeLibInfo nativeLibInfo;
+ // Library names computed once the jar comes down.
+ // The signatures of these native libraries are checked before
+ // installing them.
+ private String[] nativeLibNames;
+
+ /** The applet we have to start */
+ private Applet subApplet;
+
+ private String subAppletClassName; // from applet PARAM
+ private String subAppletDisplayName; // from applet PARAM
+ /** URL string to an image used while installing */
+ private String subAppletImageName; // from applet PARAM
+
+ private String installDirectory; // (defines a private directory for native libs)
+
+ private JPanel loaderPanel = new JPanel(new BorderLayout());
+
+ private JProgressBar progressBar = new JProgressBar(0,100);
+
+ private boolean isInitOk = false;
+
+ /** false once start() has been invoked */
+ private boolean firstStart = true;
+
+ /** true if start() has passed successfully */
+ private boolean joglStarted = false;
+
+ public JOGLAppletLauncher() {
+ }
+
+ /** Applet initialization */
+ public void init() {
+
+ this.subAppletClassName = getParameter("subapplet.classname");
+ if (subAppletClassName == null){
+ displayError("Init failed : Missing subapplet.classname argument");
+ return;
+ }
+ this.subAppletDisplayName = getParameter("subapplet.displayname");
+ if (subAppletDisplayName == null){
+ subAppletDisplayName = "Applet";
+ }
+
+ this.subAppletImageName = getParameter("subapplet.image");
+
+ initLoaderLayout();
+ validate();
+
+ String codeBase = getCodeBase().toExternalForm().substring(7); // minus http://
+
+ this.installDirectory = codeBase.replace(':', '_')
+ .replace('.', '_').replace('/', '_').replace('~','_'); // clean up the name
+
+ String osName = System.getProperty("os.name").toLowerCase();
+ String osArch = System.getProperty("os.arch").toLowerCase();
+ if (checkOSAndArch(osName, osArch)) {
+ this.isInitOk = true;
+ } else {
+ displayError("Init failed : Unsupported os / arch ( " + osName + " / " + osArch + " )");
+ }
+ }
+
+ private void displayMessage(String message){
+ progressBar.setString(message);
+ }
+
+ private void displayError(String errorMessage){
+ progressBar.setString("Error : " + errorMessage);
+ }
+
+ private void initLoaderLayout(){
+ setLayout(new BorderLayout());
+ progressBar.setBorderPainted(true);
+ progressBar.setStringPainted(true);
+ progressBar.setString("Loading...");
+ boolean includeImage = false;
+ ImageIcon image = null;
+ if (subAppletImageName != null){
+ try {
+ image = new ImageIcon(new URL(subAppletImageName));
+ includeImage = true;
+ } catch (MalformedURLException ex) {
+ ex.printStackTrace();
+ // not blocking
+ }
+ }
+ if (includeImage){
+ add(loaderPanel, BorderLayout.SOUTH);
+ loaderPanel.add(new JLabel(image), BorderLayout.CENTER);
+ loaderPanel.add(progressBar, BorderLayout.SOUTH);
+ } else {
+ add(loaderPanel, BorderLayout.SOUTH);
+ loaderPanel.add(progressBar, BorderLayout.CENTER);
+ }
+ }
+
+
+ /** start asynchroneous loading of libraries if needed */
+ public void start(){
+ if (isInitOk){
+ if (firstStart) {
+ firstStart = false;
+ String userHome = System.getProperty("user.home");
+ String installDirName = userHome + File.separator + ".jogl_ext"
+ + File.separator + installDirectory + File.separator + Version.getVersion();
+
+ final File installDir = new File(installDirName);
+
+ Thread refresher = new Thread() {
+ public void run() {
+ refreshJOGL(installDir);
+ }
+ };
+ refresher.setPriority(Thread.NORM_PRIORITY - 1);
+ refresher.start();
+ } else if (joglStarted) {
+ // we have to start again the applet (start can be called multiple times,
+ // e.g once per tabbed browsing
+ subApplet.start();
+ }
+ }
+ }
+
+ public void stop(){
+ if (subApplet != null){
+ subApplet.stop();
+ }
+ }
+
+ public void destroy(){
+ if (subApplet != null){
+ subApplet.destroy();
+ }
+ }
+
+
+ private boolean checkOSAndArch(String osName, String osArch) {
+ for (int i = 0; i < allNativeLibInfo.length; i++) {
+ NativeLibInfo info = allNativeLibInfo[i];
+ if (info.matchesOSAndArch(osName, osArch)) {
+ nativeLibInfo = info;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** This method is executed from outside the Event Dispatch Thread, and installs
+ * the required native libraries in the local folder.
+ */
+ private void refreshJOGL(final File installDir) {
+ try {
+ Class subAppletClass = Class.forName(subAppletClassName);
+ // this will block until the applet jar is downloaded
+ } catch (ClassNotFoundException cnfe){
+ displayError("Start failed : class not found : " + subAppletClassName);
+ return;
+ }
+
+ if (!installDir.exists()){
+ if (!installDir.mkdirs()) {
+ displayError("Unable to create directories for target: " + installDir);
+ return;
+ }
+ }
+
+ String nativeJarName = nativeLibInfo.getNativeJarName();
+
+ URL nativeLibURL;
+ URLConnection urlConnection;
+ String path = getCodeBase().toExternalForm() + nativeJarName;
+ try {
+ nativeLibURL = new URL(path);
+ urlConnection = nativeLibURL.openConnection();
+ } catch (Exception e){
+ e.printStackTrace();
+ displayError("Couldn't access the native lib URL : " + path);
+ return;
+ }
+
+ // the timestamp used to determine if we have to download the native jar again
+ // don't rely on the OS's timestamp to cache this
+ long lastModified = getTimestamp(installDir, urlConnection.getLastModified());
+ if (lastModified != urlConnection.getLastModified()) {
+ displayMessage("Updating local version of the native libraries");
+ // first download the full jar locally
+ File localJarFile = new File(installDir, nativeJarName);
+ try {
+ saveNativesJarLocally(localJarFile, urlConnection);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ displayError("Unable to install the native file locally");
+ return;
+ }
+
+ try {
+ JarFile jf = new JarFile(localJarFile);
+
+ // Iterate the entries finding all candidate libraries that need
+ // to have their signatures verified
+ if (!findNativeEntries(jf)) {
+ displayError("native libraries not found in jar file");
+ return;
+ }
+
+ byte[] buf = new byte[8192];
+
+ // Go back and verify the signatures
+ for (int i = 0; i < nativeLibNames.length; i++) {
+ JarEntry entry = jf.getJarEntry(nativeLibNames[i]);
+ if (entry == null) {
+ displayError("error looking up jar entry " + nativeLibNames[i]);
+ return;
+ }
+ if (!checkNativeCertificates(jf, entry, buf)) {
+ displayError("Native library " + nativeLibNames[i] + " isn't properly signed or has other errors");
+ return;
+ }
+ }
+
+ // Now install the native library files
+ progressBar.setValue(0);
+ for (int i = 0; i < nativeLibNames.length; i++) {
+ displayMessage("Installing native files");
+ if (!installFile(installDir, jf, nativeLibNames[i], buf)) {
+ return;
+ }
+ int percent = (100 * (i + 1) / nativeLibNames.length);
+ progressBar.setValue(percent);
+ }
+
+ // At this point we can delete the jar file we just downloaded
+ jf.close();
+ localJarFile.delete();
+
+ // If installation succeeded, write a timestamp for all of the
+ // files to be checked next time
+ try {
+ File timestampFile = new File(installDir, "timestamp");
+ timestampFile.delete();
+ BufferedWriter writer = new BufferedWriter(new FileWriter(timestampFile));
+ writer.write("" + urlConnection.getLastModified());
+ writer.flush();
+ writer.close();
+ } catch (Exception e) {
+ displayError("Error writing time stamp for native libraries");
+ return;
+ }
+
+ } catch (Exception e) {
+ displayError("Error opening jar file " + localJarFile.getName() + " for reading");
+ return;
+ }
+ }
+
+ loadNativesAndStart(installDir);
+ }
+
+ public long getTimestamp(File installDir, long timestamp) {
+ // Avoid returning valid value if timestamp file doesn't exist
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(new File(installDir, "timestamp")));
+ try {
+ StreamTokenizer tokenizer = new StreamTokenizer(reader);
+ // Avoid screwing up by not being able to read full longs
+ tokenizer.resetSyntax();
+ tokenizer.wordChars('0', '9');
+ tokenizer.wordChars('-', '-');
+ tokenizer.nextToken();
+ String tok = tokenizer.sval;
+ if (tok != null) {
+ return Long.parseLong(tok);
+ }
+ } catch (Exception e) {
+ } finally {
+ reader.close();
+ }
+ } catch (Exception e) {
+ }
+ return ((timestamp == 0) ? 1 : 0);
+ }
+
+ private void saveNativesJarLocally(File localJarFile,
+ URLConnection urlConnection) throws IOException {
+ BufferedOutputStream out = null;;
+ InputStream in = null;
+ displayMessage("Downloading native library");
+ progressBar.setValue(0);
+ try {
+ out = new BufferedOutputStream(new
+ FileOutputStream(localJarFile));
+ int totalLength = urlConnection.getContentLength();
+ in = urlConnection.getInputStream();
+ byte[] buffer = new byte[1024];
+ int len;
+ int sum = 0;
+ while ( (len = in.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
+ sum += len;
+ int percent = (100 * sum / totalLength);
+ progressBar.setValue(percent);
+ }
+ out.close();
+ in.close();
+ } finally {
+ // close the files
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException ignore) {
+ }
+ }
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ }
+
+ private boolean findNativeEntries(JarFile jf) {
+ List list = new ArrayList();
+ Enumeration e = jf.entries();
+ while (e.hasMoreElements()) {
+ JarEntry entry = (JarEntry) e.nextElement();
+ if (nativeLibInfo.matchesNativeLib(entry.getName())) {
+ list.add(entry.getName());
+ }
+ }
+ if (list.isEmpty()) {
+ return false;
+ }
+ nativeLibNames = (String[]) list.toArray(new String[0]);
+ return true;
+ }
+
+ /** checking the native certificates with the jogl ones (all must match)*/
+ private boolean checkNativeCertificates(JarFile jar, JarEntry entry, byte[] buf){
+ // API states that we must read all of the data from the entry's
+ // InputStream in order to be able to get its certificates
+ try {
+ InputStream is = jar.getInputStream(entry);
+ int totalLength = (int) entry.getSize();
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ }
+ is.close();
+ Certificate[] nativeCerts = entry.getCertificates();
+ // locate the JOGL certificates
+ Certificate[] joglCerts = GLDrawableFactory.class.getProtectionDomain().
+ getCodeSource().getCertificates();
+
+ if (nativeCerts == null || nativeCerts.length == 0) {
+ return false;
+ }
+ int checked = 0;
+ for (int i = 0; i < joglCerts.length; i++) {
+ for (int j = 0; j < nativeCerts.length; j++) {
+ if (nativeCerts[j].equals(joglCerts[i])){
+ checked++;
+ break;
+ }
+ }
+ }
+ return (checked == joglCerts.length);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean installFile(File installDir,
+ JarFile jar,
+ String fileName,
+ byte[] buf) {
+ try {
+ JarEntry entry = jar.getJarEntry(fileName);
+ if (entry == null) {
+ displayError("Error finding native library " + fileName);
+ return false;
+ }
+ InputStream is = jar.getInputStream(entry);
+ int totalLength = (int) entry.getSize();
+ BufferedOutputStream out = null;
+ File outputFile = new File(installDir, fileName);
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(outputFile));
+ } catch (Exception e) {
+ displayError("Error opening file " + fileName + " for writing");
+ return false;
+ }
+ int len;
+ try {
+ while ( (len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ } catch (IOException ioe) {
+ displayError("Error writing file " + fileName + " to disk");
+ ioe.printStackTrace();
+ outputFile.delete();
+ return false;
+ }
+ out.flush();
+ out.close();
+ return true;
+ } catch (Exception e2) {
+ e2.printStackTrace();
+ displayError("Error writing file " + fileName + " to disk");
+ return false;
+ }
+ }
+
+ /** last step before launch : System.load() the natives and init()/start() the child applet */
+ private void loadNativesAndStart(final File nativeLibDir) {
+ // back to the EDT
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ displayMessage("Loading native libraries");
+
+ // Load core JOGL native library
+ loadLibrary(nativeLibDir, "jogl");
+
+ if (!nativeLibInfo.isMacOS()) { // borrowed from NativeLibLoader
+ // Must pre-load JAWT on all non-Mac platforms to
+ // ensure references from jogl_awt shared object
+ // will succeed since JAWT shared object isn't in
+ // default library path
+ try {
+ System.loadLibrary("jawt");
+ } catch (UnsatisfiedLinkError ex) {
+ // Accessibility technologies load JAWT themselves; safe to continue
+ // as long as JAWT is loaded by any loader
+ if (ex.getMessage().indexOf("already loaded") == -1) {
+ displayError("Unable to load JAWT");
+ throw ex;
+ }
+ }
+ }
+
+ // Load AWT-specific native code
+ loadLibrary(nativeLibDir, "jogl_awt");
+
+ // disable JOGL loading from elsewhere
+ com.sun.opengl.impl.NativeLibLoader.disableLoading();
+
+ displayMessage("Starting applet " + subAppletDisplayName);
+
+ // start the subapplet
+ startSubApplet();
+ }
+ });
+ }
+
+ private void loadLibrary(File installDir, String libName) {
+ String nativeLibName = nativeLibInfo.getNativeLibName(libName);
+ try {
+ System.load(new File(installDir, nativeLibName).getPath());
+ } catch (UnsatisfiedLinkError ex) {
+ // should be safe to continue as long as the native is loaded by any loader
+ if (ex.getMessage().indexOf("already loaded") == -1) {
+ displayError("Unable to load " + nativeLibName);
+ throw ex;
+ }
+ }
+ }
+
+ /** The true start of the sub applet (invoked in the EDT) */
+ private void startSubApplet(){
+ try {
+ subApplet = (Applet)Class.forName(subAppletClassName).newInstance();
+ subApplet.setStub(new AppletStubProxy());
+ } catch (ClassNotFoundException cnfe) {
+ cnfe.printStackTrace();
+ displayError("Class not found (" + subAppletClassName + ")");
+ return;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ displayError("Unable to start " + subAppletDisplayName);
+ return;
+ }
+
+ add(subApplet, BorderLayout.CENTER);
+
+ try {
+ subApplet.init();
+ remove(loaderPanel);
+ validate();
+ subApplet.start();
+ joglStarted = true;
+ } catch (Exception ex){
+ ex.printStackTrace();
+ }
+
+ }
+
+ /** a proxy to allow the subApplet to work like a real applet */
+ class AppletStubProxy implements AppletStub {
+ public boolean isActive() {
+ return JOGLAppletLauncher.this.isActive();
+ }
+
+ public URL getDocumentBase() {
+ return JOGLAppletLauncher.this.getDocumentBase();
+ }
+
+ public URL getCodeBase() {
+ return JOGLAppletLauncher.this.getCodeBase();
+ }
+
+ public String getParameter(String name) {
+ return JOGLAppletLauncher.this.getParameter(name);
+ }
+
+ public AppletContext getAppletContext() {
+ return JOGLAppletLauncher.this.getAppletContext();
+ }
+
+ public void appletResize(int width, int height) {
+ JOGLAppletLauncher.this.resize(width, height);
+ }
+ }
+}
+