changeset 280:77640d74d21c

Fix appcontext related plugin bugs.
author Denis Lila <dlila@redhat.com>
date Fri, 29 Apr 2011 16:58:05 -0400
parents f0647c938535
children 360bd0a75304
files ChangeLog netx/net/sourceforge/jnlp/Launcher.java netx/net/sourceforge/jnlp/NetxPanel.java netx/net/sourceforge/jnlp/PluginBridge.java netx/net/sourceforge/jnlp/runtime/AppThreadGroup.java plugin/icedteanp/java/sun/applet/PluginAppletViewer.java
diffstat 6 files changed, 129 insertions(+), 132 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Jan 07 02:48:05 2012 -0500
+++ b/ChangeLog	Fri Apr 29 16:58:05 2011 -0400
@@ -1,3 +1,27 @@
+2011-04-28  Denis Lila  <dlila@redhat.com>
+
+	* netx/net/sourceforge/jnlp/NetxPanel.java:
+	Remove unused import; add imports.
+	(uKey, uKeyToTG, appContextCreated): New members.
+	(getThreadGroup, createNewAppContext): New methods.
+	(runLoader): Pass uKey to PluginBridge's constructor.
+	(run): Remove. No longer needed.
+	(NetxPanel): Initialize uKey. If it is a new key, make a new thread
+	group for it and save it in the hash map.
+	(createAppletThread): Use getFutureTG instead of creating a thread
+	group on the spot.
+	* plugin/icedteanp/java/sun/applet/PluginAppletViewer.java:
+	(createPanel): Initialize and frame the panel in a separate thread.
+	* netx/net/sourceforge/jnlp/Launcher.java:
+	Remove unused import.
+	(createApplet, createApplication, createThreadGroup): Replace
+	AppThreadGroup with ThreadGroup. Remove all calls to setApplication.
+	* netx/net/sourceforge/jnlp/PluginBridge.java:
+	(PluginBridge): Remove the uniqueKey initialization logic. Set
+	uniqueKey to the uKey parameter.
+	* netx/net/sourceforge/jnlp/runtime/AppThreadGroup.java:
+	Remove file.
+
 2012-01-07  Omair Majid  <omajid@redhat.com>
 
 	* netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java
--- a/netx/net/sourceforge/jnlp/Launcher.java	Sat Jan 07 02:48:05 2012 -0500
+++ b/netx/net/sourceforge/jnlp/Launcher.java	Fri Apr 29 16:58:05 2011 -0400
@@ -33,7 +33,6 @@
 
 import net.sourceforge.jnlp.cache.CacheUtil;
 import net.sourceforge.jnlp.cache.UpdatePolicy;
-import net.sourceforge.jnlp.runtime.AppThreadGroup;
 import net.sourceforge.jnlp.runtime.AppletInstance;
 import net.sourceforge.jnlp.runtime.ApplicationInstance;
 import net.sourceforge.jnlp.runtime.JNLPClassLoader;
@@ -707,7 +706,7 @@
                 throw new ClassNotFoundException("Can't do a codebase look up and there are no jars. Failing sooner rather than later");
             }
 
-            AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup();
+            ThreadGroup group = Thread.currentThread().getThreadGroup();
 
             String appletName = file.getApplet().getMainClass();
 
@@ -723,7 +722,6 @@
             else
                 appletInstance = new AppletInstance(file, group, loader, applet, cont);
 
-            group.setApplication(appletInstance);
             loader.setApplication(appletInstance);
 
             setContextClassLoaderForAllThreads(appletInstance.getThreadGroup(), appletInstance.getClassLoader());
@@ -770,10 +768,9 @@
     protected ApplicationInstance createApplication(JNLPFile file) throws LaunchException {
         try {
             JNLPClassLoader loader = JNLPClassLoader.getInstance(file, updatePolicy);
-            AppThreadGroup group = (AppThreadGroup) Thread.currentThread().getThreadGroup();
+            ThreadGroup group = Thread.currentThread().getThreadGroup();
 
             ApplicationInstance app = new ApplicationInstance(file, group, loader);
-            group.setApplication(app);
             loader.setApplication(app);
 
             return app;
@@ -789,16 +786,16 @@
      * then this method simply returns the existing ThreadGroup. The applet
      * ThreadGroup has to be created at an earlier point in the applet code.
      */
-    protected AppThreadGroup createThreadGroup(JNLPFile file) {
-        AppThreadGroup appThreadGroup = null;
+    protected ThreadGroup createThreadGroup(JNLPFile file) {
+        ThreadGroup tg = null;
 
         if (file instanceof PluginBridge) {
-            appThreadGroup = (AppThreadGroup) Thread.currentThread().getThreadGroup();
+            tg = Thread.currentThread().getThreadGroup();
         } else {
-            appThreadGroup = new AppThreadGroup(mainGroup, file.getTitle());
+            tg = new ThreadGroup(mainGroup, file.getTitle());
         }
 
-        return appThreadGroup;
+        return tg;
     }
 
     /**
--- a/netx/net/sourceforge/jnlp/NetxPanel.java	Sat Jan 07 02:48:05 2012 -0500
+++ b/netx/net/sourceforge/jnlp/NetxPanel.java	Fri Apr 29 16:58:05 2011 -0400
@@ -23,12 +23,13 @@
 package net.sourceforge.jnlp;
 
 import net.sourceforge.jnlp.AppletLog;
-import net.sourceforge.jnlp.runtime.AppThreadGroup;
 import net.sourceforge.jnlp.runtime.AppletInstance;
 import net.sourceforge.jnlp.runtime.JNLPRuntime;
 
 import java.net.URL;
 import java.util.Hashtable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import sun.applet.AppletViewerPanel;
 import sun.awt.SunToolkit;
@@ -44,9 +45,58 @@
     private boolean exitOnFailure = true;
     private AppletInstance appInst = null;
     private boolean appletAlive;
+    private final String uKey;
+
+    // We use this so that we can create exactly one thread group
+    // for all panels with the same uKey.
+    private static final ConcurrentMap<String, ThreadGroup> uKeyToTG =
+        new ConcurrentHashMap<String, ThreadGroup>();
+
+    // This map is actually a set (unfortunately there is no ConcurrentSet
+    // in java.util.concurrent). If KEY is in this map, then we know that
+    // an app context has been created for the panel that has uKey.equals(KEY),
+    // so we avoid creating it a second time for panels with the same uKey.
+    // Because it's a set, only the keys matter. However, we can't insert
+    // null values in because if we did, we couldn't use null checks to see
+    // if a key was absent before a putIfAbsent. 
+    private static final ConcurrentMap<String, Boolean> appContextCreated =
+        new ConcurrentHashMap<String, Boolean>();
 
     public NetxPanel(URL documentURL, Hashtable<String, String> atts) {
         super(documentURL, atts);
+
+        /* According to http://download.oracle.com/javase/6/docs/technotes/guides/deployment/deployment-guide/applet-compatibility.html, 
+         * classloaders are shared iff these properties match:
+         * codebase, cache_archive, java_archive, archive
+         * 
+         * To achieve this, we create the uniquekey based on those 4 values,
+         * always in the same order. The initial "<NAME>=" parts ensure a 
+         * bad tag cannot trick the loader into getting shared with another.
+         */
+
+        // Firefox sometimes skips the codebase if it is default  -- ".", 
+        // so set it that way if absent
+        String codebaseAttr =      atts.get("codebase") != null ?
+                                   atts.get("codebase") : ".";
+
+        String cache_archiveAttr = atts.get("cache_archive") != null ? 
+                                   atts.get("cache_archive") : "";
+
+        String java_archiveAttr =  atts.get("java_archive") != null ? 
+                                   atts.get("java_archive") : "";
+
+        String archiveAttr =       atts.get("archive") != null ? 
+                                   atts.get("archive") : "";
+
+        this.uKey = "codebase=" + codebaseAttr +
+                    "cache_archive=" + cache_archiveAttr + 
+                    "java_archive=" + java_archiveAttr + 
+                    "archive=" +  archiveAttr;
+
+        // when this was being done (incorrectly) in Launcher, the call was
+        // new AppThreadGroup(mainGroup, file.getTitle());
+        ThreadGroup tg = new ThreadGroup(Launcher.mainGroup, this.documentURL.toString());
+        uKeyToTG.putIfAbsent(this.uKey, tg);
     }
 
     // overloaded constructor, called when initialized via plugin
@@ -58,18 +108,6 @@
     }
 
     @Override
-    public void run() {
-        /*
-         * create an AppContext for this thread associated with this particular
-         * plugin instance (which runs in a different thread group from the rest
-         * of the plugin).
-         */
-        SunToolkit.createNewAppContext();
-
-        super.run();
-    }
-
-    @Override
     protected void showAppletException(Throwable t) {
         /*
          * Log any exceptions thrown while loading, initializing, starting,
@@ -78,7 +116,7 @@
         AppletLog.log(t);
         super.showAppletException(t);
     }
-    
+
     //Overriding to use Netx classloader. You might need to relax visibility
     //in sun.applet.AppletPanel for runLoader().
     protected void runLoader() {
@@ -90,7 +128,7 @@
                                 getCode(),
                                 getWidth(),
                                 getHeight(),
-                                atts);
+                                atts, uKey);
 
             doInit = true;
             dispatchAppletEvent(APPLET_LOADING, null);
@@ -154,11 +192,7 @@
             }
         }
 
-        // when this was being done (incorrectly) in Launcher, the call was
-        // new AppThreadGroup(mainGroup, file.getTitle());
-        ThreadGroup tg = new AppThreadGroup(Launcher.mainGroup,
-                this.documentURL.toString());
-        handler = new Thread(tg, this);
+        handler = new Thread(getThreadGroup(), this);
         handler.start();
     }
 
@@ -174,4 +208,19 @@
     public boolean isAlive() {
         return handler != null && handler.isAlive() && this.appletAlive;
     }
+
+    public ThreadGroup getThreadGroup() {
+        return uKeyToTG.get(uKey);
+    }
+
+    public void createNewAppContext() {
+        if (Thread.currentThread().getThreadGroup() != getThreadGroup()) {
+            throw new RuntimeException("createNewAppContext called from the wrong thread.");
+        }
+        // only create a new context if one hasn't already been created for the
+        // applets with this unique key.
+        if (null == appContextCreated.putIfAbsent(uKey, Boolean.TRUE)) {
+            SunToolkit.createNewAppContext();
+        }
+    }
 }
--- a/netx/net/sourceforge/jnlp/PluginBridge.java	Sat Jan 07 02:48:05 2012 -0500
+++ b/netx/net/sourceforge/jnlp/PluginBridge.java	Fri Apr 29 16:58:05 2011 -0400
@@ -44,7 +44,8 @@
     private boolean codeBaseLookup;
 
     public PluginBridge(URL codebase, URL documentBase, String jar, String main,
-                        int width, int height, Hashtable<String, String> atts)
+                        int width, int height, Hashtable<String, String> atts,
+                        String uKey)
             throws Exception {
         specVersion = new Version("1.0");
         fileVersion = new Version("1.1");
@@ -132,34 +133,7 @@
         else
             security = null;
 
-        /* According to http://download.oracle.com/javase/6/docs/technotes/guides/deployment/deployment-guide/applet-compatibility.html, 
-         * classloaders are shared iff these properties match:
-         * codebase, cache_archive, java_archive, archive
-         * 
-         * To achieve this, we create the uniquekey based on those 4 values,
-         * always in the same order. The initial "<NAME>=" parts ensure a 
-         * bad tag cannot trick the loader into getting shared with another.
-         */
-
-        // Firefox sometimes skips the codebase if it is default  -- ".", 
-        // so set it that way if absent
-        String codebaseAttr =      atts.get("codebase") != null ?
-                                   atts.get("codebase") : ".";
-
-        String cache_archiveAttr = atts.get("cache_archive") != null ? 
-                                   atts.get("cache_archive") : "";
-
-        String java_archiveAttr =  atts.get("java_archive") != null ? 
-                                   atts.get("java_archive") : "";
-
-        String archiveAttr =       atts.get("archive") != null ? 
-                                   atts.get("archive") : "";
-
-        this.uniqueKey = "codebase=" + codebaseAttr +
-                         "cache_archive=" + cache_archiveAttr + 
-                         "java_archive=" + java_archiveAttr + 
-                         "archive=" +  archiveAttr;
-
+        this.uniqueKey = uKey;
         usePack = false;
         useVersion = false;
         String jargs = atts.get("java_arguments");
--- a/netx/net/sourceforge/jnlp/runtime/AppThreadGroup.java	Sat Jan 07 02:48:05 2012 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-// Copyright (C) 2001-2003 Jon A. Maxwell (JAM)
-//
-// 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, write to the Free Software
-// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
-package net.sourceforge.jnlp.runtime;
-
-/**
- * Thread group for a JNLP application.
- *
- * @author <a href="mailto:jmaxwell@users.sourceforge.net">Jon A. Maxwell (JAM)</a> - initial author
- * @version $Revision: 1.5 $
- */
-public class AppThreadGroup extends ThreadGroup {
-
-    /** the app */
-    private ApplicationInstance app = null;
-
-    /**
-     * Creates new JavaAppThreadGroup
-     *
-     * @param name of the App
-     */
-    public AppThreadGroup(ThreadGroup parent, String name) {
-        super(parent, name);
-    }
-
-    /**
-     * Sets the JNLP app this group is for; can only be called once.
-     */
-    public void setApplication(ApplicationInstance app) {
-        if (this.app != null)
-            throw new IllegalStateException("Application can only be set once");
-
-        this.app = app;
-    }
-
-    /**
-     * Returns the JNLP app for this thread group.
-     */
-    public ApplicationInstance getApplication() {
-        return app;
-    }
-
-    /**
-     * Handle uncaught exceptions for the app.
-     */
-    public void uncaughtException(Thread t, Throwable e) {
-        super.uncaughtException(t, e);
-    }
-
-}
--- a/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java	Sat Jan 07 02:48:05 2012 -0500
+++ b/plugin/icedteanp/java/sun/applet/PluginAppletViewer.java	Fri Apr 29 16:58:05 2011 -0400
@@ -123,10 +123,10 @@
 class PluginAppletPanelFactory {
 
     public AppletPanel createPanel(PluginStreamHandler streamhandler,
-                                    int identifier,
-                                    long handle, int x, int y,
-                                    final URL doc,
-                                    final Hashtable<String, String> atts) {
+                                   final int identifier,
+                                   final long handle, int x, int y,
+                                   final URL doc,
+                                   final Hashtable<String, String> atts) {
         final NetxPanel panel = AccessController.doPrivileged(new PrivilegedAction<NetxPanel>() {
             public NetxPanel run() {
                 NetxPanel panel = new NetxPanel(doc, atts, false);
@@ -136,13 +136,29 @@
             }
         });
 
-        // create the frame.
-        PluginAppletViewer.framePanel(identifier, handle, panel);
+        // Framing the panel needs to happen in a thread whose thread group
+        // is the same as the threadgroup of the applet thread. If this
+        // isn't the case, the awt eventqueue thread's context classloader
+        // won't be set to a JNLPClassLoader, and when an applet class needs
+        // to be loaded from the awt eventqueue, it won't be found.
+        Thread panelInit = new Thread(panel.getThreadGroup(), new Runnable() {
+            @Override public void run() {
+                panel.createNewAppContext();
+                // create the frame.
+                PluginAppletViewer.framePanel(identifier, handle, panel);
+                panel.init();
+                // Start the applet
+                initEventQueue(panel);
+            }
+        }, "NetXPanel initializer");
 
-        panel.init();
-
-        // Start the applet
-        initEventQueue(panel);
+        panelInit.start();
+        while(panelInit.isAlive()) {
+            try {
+                panelInit.join();
+            } catch (InterruptedException e) {
+            }
+        }
 
         // Wait for the panel to initialize
         PluginAppletViewer.waitForAppletInit(panel);