Java tutorial
/* * OpenNetbank a client to Free your bank information. * Copyright (C) 2010 Jerome Lacoste <jerome@coffeebreaks.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package no.eris.applet; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import util.ClasspathUtils; import util.ThreadUtils; import javax.swing.*; import java.applet.Applet; import java.applet.AppletContext; import java.applet.AppletStub; import java.applet.AudioClip; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookieStore; import java.net.CookiePolicy; import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; /** * A Single AppletViewer should be created per applet we are about to run. * * @author jerome@coffeebreaks.org * @since Sep 23, 2010 7:14:16 PM */ public class AppletViewer { private static Logger LOGGER = LoggerFactory.getLogger(AppletViewer.class); /** * Called back when the applet triggers it */ public interface ShowDocumentListener { void showDocument(URL url, String frame); } final CookieManager manager = new CookieManager(); // simple cookie handled used to diagnose cookie issues private static class LoggingCookieHandler extends CookieHandler { private CookieManager manager; public CookieManager getManager() { return this.manager; } public LoggingCookieHandler(CookieManager manager) { this.manager = manager; } @Override public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) throws IOException { Map<String, List<String>> result = manager.get(uri, requestHeaders); LOGGER.debug("GET cookie from {} headers= {}", new Object[] { uri, result, requestHeaders }); for (String key : result.keySet()) { LOGGER.debug(key + "=" + result.get(key)); } return result; } @Override public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException { LOGGER.debug("PUT cookie from {} headers= {}", uri, responseHeaders); for (String key : responseHeaders.keySet()) { if (key != null && key.toLowerCase().equals("set-cookie")) LOGGER.debug(key + "=" + responseHeaders.get(key)); } manager.put(uri, responseHeaders); } } private JFrame frame; private AppletAdapter appletAdapter; private Map<String, String> attributes; private Map<String, String> params; private Class appletClass = null; /** The Applet instance we are running, or null. Can not be a JApplet * until all the entire world is converted to JApplet. */ Applet applet = null; private ThreadGroup threadGroup = null; /* By default we load the applet remotely */ private boolean loadFromLocalClasspath = false; private List<UriAndCookie> cookies; private ShowDocumentListener showDocumentListener; public static void main(String[] av) { if (av.length < 2) { throw new IllegalArgumentException("USAGE: AppletViewer configFileAttributes configFileParams"); } Thread t = AppletViewer.build(av[0], av[1]).run(true); if (t != null) { while (t.isAlive()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(System.err); } } } } public AppletViewer(Map<String, String> attributes, Map<String, String> params) { this.attributes = attributes; this.params = params; } public void setLoadFromLocalClasspath(boolean loadFromLocalClasspath) { this.loadFromLocalClasspath = loadFromLocalClasspath; } public void setShowDocumentListener(ShowDocumentListener showDocumentListener) { this.showDocumentListener = showDocumentListener; } public void showDocument(URL url, String frame) { if (showDocumentListener != null) { showDocumentListener.showDocument(url, frame); } } /** * Constructs the GUI for an Applet Viewer * @param configPropFileAttributes the property file containing the attributes * @param configPropFileParams the property file containing the parameters * @return the AppletViewer */ static AppletViewer build(String configPropFileAttributes, String configPropFileParams) { Map<String, String> attributes = readConfig(configPropFileAttributes, false); Map<String, String> params = readConfig(configPropFileParams, true); return new AppletViewer(attributes, params); } private static Map<String, String> readConfig(String configPropFile, boolean convertKeyToLowerCase) { Properties p = new Properties(); try { p.load(AppletViewer.class.getResourceAsStream(configPropFile)); Map<String, String> m = new HashMap<String, String>(); for (String key : p.stringPropertyNames()) { if (convertKeyToLowerCase) m.put(key.toLowerCase(), p.getProperty(key)); else m.put(key, p.getProperty(key)); } return m; } catch (IOException e) { throw new IllegalStateException("Couldn't load properties from " + configPropFile, e); } } /** * Runs the applet. Creates a Frame and adds it to it. * @param async whether to start a separate thread running the viewer. * @return the started thread or <code>null</code> when async is false */ public Thread run(final boolean async) { overrideCookieHandler(manager); frame = new JFrame("AppletViewer"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { LOGGER.debug("windowClosing"); disposeResources(); } }); Container cp = frame.getContentPane(); cp.setLayout(new BorderLayout()); // Instantiate the AppletAdapter which gives us // AppletStub and AppletContext. if (appletAdapter == null) appletAdapter = new AppletAdapter(this, attributes, params); // The AppletAdapter also gives us showStatus. // Therefore, must add() it very early on, since the Applet's // Constructor or its init() may use showStatus() cp.add(BorderLayout.SOUTH, appletAdapter); showStatus("Loading Applet "); if (loadFromLocalClasspath) { loadAppletLocaly(); } else { loadAppletRemotely(); } setAppletSize(); if (applet == null) { LOGGER.debug("applet null"); return null; } // Now right away, tell the Applet how to find showStatus et al. applet.setStub(appletAdapter); // Connect the Applet to the Frame. cp.add(BorderLayout.CENTER, applet); threadGroup = new ThreadGroup("AppletViewer-" + applet.getParameter("name") + "-FIXME_ID"); threadGroup.setDaemon(true); // Here we pretend to be a browser! final Runnable task = new Runnable() { public void run() { applet.init(); final Dimension d = applet.getSize(); d.height += appletAdapter.getSize().height; frame.setSize(d); frame.setVisible(true); // make the Frame and all in it appear applet.start(); showStatus("Applet " + applet.getParameter("name") + " loaded"); if (async) { waitForAppletToClose(); } } }; if (async) { Thread t = new Thread(threadGroup, task); final ClassLoader loader = applet.getClass().getClassLoader(); t.setContextClassLoader(loader); t.start(); return t; } else { task.run(); return null; } } private void setAppletSize() { final int height = Integer.parseInt(appletAdapter.getParameter("height")); final int width = Integer.parseInt(appletAdapter.getParameter("width")); applet.setSize(width, height); } public void waitForAppletToClose() { while (applet != null) { try { Thread.sleep(100); // Utils.printObjectGraph(applet); } catch (InterruptedException e) { LOGGER.error("waiting for applet to close", e); } } } public Applet getRunningApplet() { return applet; } public void disposeResources() { LOGGER.debug("Disposing resources..."); if (frame != null) { frame.setVisible(false); frame.dispose(); frame = null; } if (applet != null) { applet.stop(); applet.destroy(); applet = null; } if (threadGroup != null && !threadGroup.isDestroyed()) { int maxWait = 2000; LOGGER.debug("Threads not dead - waiting a bit..."); long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < maxWait) { try { Thread.sleep(100); } catch (InterruptedException e) { LOGGER.error("InterruptedException while waiting for threads to die", e); } } } destroyThreadGroup(); } private void destroyThreadGroup() { if (threadGroup != null && !threadGroup.isDestroyed()) { LOGGER.debug("Threads not dead - killing them"); Thread[] aliveThreads = null; boolean enumeratedAll = false; while (!enumeratedAll) { int countThreads = threadGroup.activeCount(); aliveThreads = new Thread[countThreads + 2]; // take some margin int enumerated = threadGroup.enumerate(aliveThreads); enumeratedAll = enumerated < aliveThreads.length; if (!enumeratedAll) LOGGER.debug("Couldn't enumerate all threads"); } for (Thread aliveThread : aliveThreads) { if (aliveThread != null && aliveThread.isAlive()) { ThreadUtils.forceInterrupt(aliveThread); } } if (threadGroup.activeCount() == 0) { threadGroup.destroy(); } else { LOGGER.debug("Couldn't destroy thread group: not empty !"); ThreadUtils.printAliveThreadStack(); } threadGroup = null; } } public static class UriAndCookie { private URI uri; private HttpCookie cookie; public UriAndCookie(URI uri, HttpCookie cookie) { this.uri = uri; this.cookie = cookie; } public URI getUri() { return uri; } public HttpCookie getCookie() { return cookie; } } public void setCookiesToAddToStore(List<UriAndCookie> cookies) { this.cookies = cookies; } public CookieStore getCookieStore() { return manager.getCookieStore(); } private void overrideCookieHandler(CookieManager manager) { manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); final CookieHandler handler = CookieHandler.getDefault(); LOGGER.debug("CookieStore: size {}", manager.getCookieStore().getCookies().size()); if (cookies != null) { for (UriAndCookie uriAndCookie : cookies) { URI uri = uriAndCookie.getUri(); HttpCookie cookie = uriAndCookie.getCookie(); LOGGER.debug("Adding cookies: <{}> value={} secure={}", new Object[] { uri, cookie, cookie.getSecure() }); manager.getCookieStore().add(uri, cookie); } } LOGGER.debug("CookieStore: size {}", manager.getCookieStore().getCookies().size()); LOGGER.debug("Overriding cookie handler: {}", (handler == null ? null : handler.getClass().getName())); // FIXME because we depend on the system-wide cookie manager, we probably cannot run multiple applets at the time // we also maybe have some security issues lurking here... // I could maybe partition the callers based on the ThreadGroup ?? // FIXME theres also some cleanup to do somewhere CookieHandler.setDefault(new LoggingCookieHandler(manager)); } void loadAppletRemotely() { String archive = appletAdapter.getParameter("archive"); String codebase = appletAdapter.getParameter("codebase"); final String appletName = appletAdapter.getParameter("code"); String className = appletName; if (className.endsWith(".class")) { className = className.substring(0, className.length() - ".class".length()); } final String baseUrl = codebase + (codebase.endsWith("/") ? "" : "/"); final String url = baseUrl + (archive == null ? "" : archive); try { LOGGER.debug("Loading Applet from URL: {}", url); ClassLoader cl = new AppletClassLoader(new URL(url), archive, baseUrl, appletName); appletClass = Class.forName(className, true, cl); // Construct an instance (as if using no-argument constructor) applet = (Applet) appletClass.newInstance(); } catch (Throwable t) { ClasspathUtils.displayClasspath(applet); final String message = "Applet " + appletName + " couldn't load from " + url; showStatus(message, new Exception(message, t)); } } /* * Load the Applet into memory. Should do caching. */ void loadAppletLocaly() { String appletName = appletAdapter.getParameter("code"); if (appletName.endsWith(".class")) { appletName = appletName.substring(0, appletName.length() - ".class".length()); } try { ClasspathUtils.displayClasspath(this); // get a Class object for the Applet subclass appletClass = Class.forName(appletName); // Construct an instance (as if using no-argument constructor) applet = (Applet) appletClass.newInstance(); } catch (ClassNotFoundException e) { ClasspathUtils.displayClasspath(this); showStatus("Applet subclass " + appletName + " did not load", e); } catch (Exception e) { ClasspathUtils.displayClasspath(this); showStatus("Applet " + appletName + " did not instantiate", e); } catch (Throwable t) { ClasspathUtils.displayClasspath(this); showStatus("Applet " + appletName + " did not instantiate", new Exception("...", t)); } } public void showStatus(String s) { appletAdapter.getAppletContext().showStatus(s); } public void showStatus(String s, Exception e) { LOGGER.error("showStatus: {}: {}", s, e.getMessage()); e.printStackTrace(); appletAdapter.getAppletContext().showStatus(s); } private static class AppletClassLoader extends java.net.URLClassLoader { private final String archive; private final String baseUrl; private final String appletName; public AppletClassLoader(URL jarUrl, String archive, String baseUrl, String appletName) { super(new URL[] { jarUrl }, null); this.archive = archive; this.baseUrl = baseUrl; this.appletName = appletName; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { return super.findClass(name); } catch (Exception ignored) { } if (archive == null) { String fileUrl = baseUrl + appletName.replace('.', '/'); if (!fileUrl.endsWith(".class")) { fileUrl += ".class"; } try { LOGGER.debug("Reading Applet class from URL: {}", fileUrl); byte[] bytes = readBytes(new URL(fileUrl)); return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { throw new ClassNotFoundException("Couldn't read from " + fileUrl, e); } } throw new ClassNotFoundException("We don't know where to search for class file..."); } private byte[] readBytes(URL url) throws IOException { URLConnection s = url.openConnection(); if (s instanceof HttpURLConnection) { HttpURLConnection httpUrlConnection = (HttpURLConnection) s; int responseCode = httpUrlConnection.getResponseCode(); if (responseCode >= 400) { throw new IOException("Couldn't open " + url + " response: " + responseCode); } } ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(s.getInputStream(), baos); return baos.toByteArray(); } } } class AppletAdapter extends Panel implements AppletStub, AppletContext { private static Logger LOGGER = LoggerFactory.getLogger(AppletAdapter.class); /** The status window at the bottom */ private Label status = null; private AppletViewer appletViewer; private Map<String, String> attributes; private Map<String, String> params; /** Construct the GUI for an Applet Status window */ AppletAdapter(AppletViewer appletViewer, Map<String, String> attributes, Map<String, String> params) { this.appletViewer = appletViewer; this.attributes = attributes; this.params = params; // Must do this very early on, since the Applet's // Constructor or its init() may use showStatus() add(status = new Label()); // Give "status" the full width status.setSize(getSize().width, status.getSize().height); showStatus("AppletAdapter constructed"); // now it can be said } /****************** AppletStub ***********************/ /** Called when the applet wants to be resized. */ public void appletResize(int w, int h) { LOGGER.debug("appletResize {}, {}", w, h); // applet.setSize(w, h); } /** Gets a reference to the applet's context. */ public AppletContext getAppletContext() { // debug("getAppletContext()"); return this; } /** Gets the base URL. */ public URL getCodeBase() { LOGGER.debug("getCodeBase()"); return getClass().getResource("."); } /** Gets the document URL. */ public URL getDocumentBase() { LOGGER.debug("getDocumentBase()"); return getClass().getResource("."); } /** Returns the value of the named parameter in the HTML tag. * Cases are not sensitive */ public String getParameter(String name) { LOGGER.debug("getParameter({})", name); String value = params.get(name); // try lower case if (value == null) { value = params.get(name.toLowerCase()); } // search attributes if (value == null) { value = attributes.get(name); } // search lower case attributes if (value == null) { value = attributes.get(name.toLowerCase()); } if (value == null) { LOGGER.error("AppletViewer Param '{}' not passed neither as parameter nor as attribute!", name); } return value; } /** Determines if the applet is active. */ public boolean isActive() { LOGGER.debug("isActive()"); return true; } /************************ AppletContext ************************/ /** Finds and returns the applet with the given name. */ public Applet getApplet(String an) { LOGGER.debug("getApplet({})", an); return appletViewer.applet; } /** Finds all the applets in the document */ public Enumeration<Applet> getApplets() { // LOGGER.debug("getApplets()"); List<Applet> applets = new java.util.ArrayList<Applet>(); applets.add(appletViewer.applet); return java.util.Collections.enumeration(applets); } /** Create an audio clip for the given URL of a .au file */ public AudioClip getAudioClip(URL url) { LOGGER.debug("getAudioClip({})", url); return null; } /** Look up and create an Image object that can be paint()ed */ public Image getImage(URL url) { LOGGER.debug("getImage({})", url); return null; } /** Request to overlay the current page with a new one - passed to the listener */ public void showDocument(URL url) { LOGGER.debug("showDocument({})", url); appletViewer.showDocument(url, null); } /** as above but with a Frame target */ public void showDocument(URL url, String frame) { LOGGER.debug("showDocument({},{})", url, frame); appletViewer.showDocument(url, frame); } /** Called by the Applet to display a message in the bottom line */ public void showStatus(String msg) { LOGGER.debug("showStatus({})", msg); if (msg == null) msg = ""; status.setText(msg); } /* StreamKey stuff - new in JDK1.4 */ private HashMap<String, InputStream> streamMap = new HashMap<String, InputStream>(); /** Associate the stream with the key. */ public void setStream(String key, InputStream stream) throws IOException { LOGGER.debug("setStream({}, {})", key, stream); streamMap.put(key, stream); } public InputStream getStream(String key) { LOGGER.debug("getStream({})", key); return streamMap.get(key); } public Iterator<String> getStreamKeys() { LOGGER.debug("getStreamKeys()"); return streamMap.keySet().iterator(); } }