package com.wilutions.jsfs; import java.util.Arrays; import javax.swing.SwingUtilities; import javax.swing.UIManager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import byps.BAsyncResult; import byps.BLostConnectionHandler; import byps.BMessage; import byps.http.HTransportFactoryClient; import byps.http.HWireClient; /** * Main application class. Connects to the JSFS Dispatcher. Shows an icon in the * system tray. */ public class Main { /** * Re-connect after this pause time if the server is not available. * */ private final static long RECONNECT_AFTER_MILLIS = 30 * 1000; /** * JSFS Dispatcher URL */ private static String jsfsDispatcherUrl = "http://localhost:8080/jsfs-dispatcher/jsfs"; /** * URL to the token service of your web application. */ private static String yourWebappUrl = "http://localhost:8080/yourapp/"; private static String tokenServiceUrl = yourWebappUrl + "auth?jsfstoken=true"; /** * User name to login to the token service. */ private static String userName = "user"; /** * Password for userName. */ private static String userPwd = "pwd"; /** * The system tray icon. */ private static JsfsTrayIcon trayIcon = new JsfsTrayIcon() { public void showError(Throwable ex) { } public void showInfo(String msg) { } public void touch() { } }; /** * Repeatedly check the connection to the JSFS Dispatcher. If the JSFS * Dispatcher is not used for some minutes, it discards the token and the * connection to the browser. A thread calls each * CHECK_CONNECTION_AFTER_MILLIS the keepAlive() function of the JSFS * Dispatcher to signal that the token is still in use. This value must be * shorter than the session lifetime in the JSFS Dispatcher and shorter than * the token lifetime in Your Web Application (see * JsfsToken.TOKEN_LIFETIME_MILLIS). */ private final static long CHECK_CONNECTION_AFTER_MILLIS = (300 * 1000); private final static Log log = LogFactory.getLog(Main.class); public static void main(String[] args) { log.info("main args=" + Arrays.toString(args)); // Get configuration from program arguments if (args.length > 0) tokenServiceUrl = args[0]; if (args.length > 1) jsfsDispatcherUrl = args[1]; if (args.length > 2) userName = args[2]; // Should only be used for testing if (args.length > 3) userPwd = args[3]; // Should only be used for testing // Initialize Swing try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); // UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); } catch (Exception ex) { log.error(ex); } UIManager.put("swing.boldMetal", Boolean.FALSE); // Schedule a job for the event-dispatching thread: // adding TrayIcon and connecting to JSFS Dispatcher SwingUtilities.invokeLater(new Runnable() { public void run() { if (log.isInfoEnabled()) log.info("show tray icon"); trayIcon = JsfsTrayIconImpl.createAndShowGUI(); if (log.isInfoEnabled()) log.info("connect to JSFS Dispatcher"); connectToJsfsDispatcherShowStatus(); } }); } /** * Connect to the JSFS Dispatcher and show status. The tray icon shows an * information or an error depending on a successful connection. */ private static void connectToJsfsDispatcherShowStatus() { if (log.isDebugEnabled()) log.debug("connectToJsfsDispatcherShowStatus("); connectToJSFS(new BAsyncResult<BClient_JSFS>() { public void setAsyncResult(BClient_JSFS client, final Throwable e) { if (log.isDebugEnabled()) log.debug("connectToJSFS returns client=" + client + ", e=" + e); if (e != null) { trayIcon.showError(e); } else { trayIcon.showInfo("Connected to server."); } } }); if (log.isDebugEnabled()) log.debug(")connectToJsfsDispatcherShowStatus"); } /** * Asynchronously connect to the JFSF Dispatcher. * * @param asyncResult * Callback interface that receives the result. */ private static void connectToJSFS(final BAsyncResult<BClient_JSFS> asyncResult) { if (log.isDebugEnabled()) log.debug("connectToJSFS("); // Initialize BYPS: // - provide a BWire object // - provide a BTransportFactory // - create a BClient object // - supply a BAuthentication object // - start the BClient object HWireClient wire = new HWireClient(jsfsDispatcherUrl, 0, 60, null) { @Override public synchronized void send(BMessage msg, BAsyncResult<BMessage> asyncResult) { trayIcon.touch(); super.send(msg, asyncResult); } @Override public void sendR(BMessage msg, final BAsyncResult<BMessage> asyncResult) { BAsyncResult<BMessage> outerResult = new BAsyncResult<BMessage>() { public void setAsyncResult(BMessage result, Throwable ex) { trayIcon.touch(); asyncResult.setAsyncResult(result, ex); } }; super.sendR(msg, outerResult); } }; if (log.isDebugEnabled()) log.debug("wire=" + wire); HTransportFactoryClient transportFactory = new HTransportFactoryClient(BApiDescriptor_JSFS.instance(), wire, 1); if (log.isDebugEnabled()) log.debug("transportFactory=" + transportFactory); final BClient_JSFS bclient = BClient_JSFS.createClient(transportFactory); if (log.isDebugEnabled()) log.debug("client=" + bclient); bclient.setAuthentication(new JsfsAuthentication(yourWebappUrl, tokenServiceUrl, userName, userPwd)); bclient.setLostReverseConnectionHandler(new BLostConnectionHandler() { // This handler is called, if the connection to the server was lost. // It retries to connect after RECONNECT_AFTER_MILLIS. @Override public void onLostConnection(Throwable ex) { if (log.isDebugEnabled()) log.debug("lost connection", ex); reconnectToJSFS(bclient); } }); if (log.isDebugEnabled()) log.debug("bclient.start..."); bclient.start(new BAsyncResult<Boolean>() { public void setAsyncResult(Boolean succ, Throwable ex) { if (log.isDebugEnabled()) log.debug("bclient.start ex=" + ex); if (ex != null) { log.info("JSFS Agent failed to start", ex); reconnectToJSFS(bclient); } else { log.info("JSFS Agent started"); // Start a thread to keep the token alive keepAlive(bclient); } asyncResult.setAsyncResult(bclient, ex); } }); if (log.isDebugEnabled()) log.debug(")connectToJSFS"); } /** * Connect to the JSFS Dispatcher after RECONNECT_AFTER_MILLIS. This function * is called if the connection to the JSFS Dispatcher is lost. */ private static void reconnectToJSFS(final BClient_JSFS bclient) { if (log.isDebugEnabled()) log.debug("reconnectToJSFS(bclient=" + bclient); trayIcon.showInfo("Lost connection, retry after " + (RECONNECT_AFTER_MILLIS / 1000) + "s"); if (log.isDebugEnabled()) log.debug("bclient.done"); bclient.done(); if (log.isDebugEnabled()) log.debug("start reconnect thread"); Thread thread = new Thread() { public void run() { try { if (log.isDebugEnabled()) log.debug("sleep before reconnect, ms=" + RECONNECT_AFTER_MILLIS); Thread.sleep(RECONNECT_AFTER_MILLIS); connectToJsfsDispatcherShowStatus(); } catch (InterruptedException e) { if (log.isDebugEnabled()) log.debug("reconnect interrupted"); } } }; thread.setName("reconnect"); thread.setDaemon(true); thread.start(); if (log.isDebugEnabled()) log.debug(")reconnectToJSFS"); } /** * This function starts a thread that repeatedly calls keepAlive(). * * @param bclient * Client object */ private static void keepAlive(final BClient_JSFS bclient) { if (log.isDebugEnabled()) log.debug("keepAlive(" + bclient); Thread thread = new Thread() { public void run() { try { while (!isInterrupted()) { if (log.isDebugEnabled()) log.debug("wait before keep alive, ms=" + CHECK_CONNECTION_AFTER_MILLIS); Thread.sleep(CHECK_CONNECTION_AFTER_MILLIS); if (log.isDebugEnabled()) log.debug("send keep alive requests"); JsfsAuthentication auth = (JsfsAuthentication) bclient.getAuthentication(); final String oldToken = auth.getToken(); if (log.isDebugEnabled()) log.debug("auth=" + auth + ", oldToken=" + oldToken); // Send a request to Your Web Application in order to keep the // application server session alive. // As long as JSFS Agent is running, the token should not change. // Otherwise the browser looses the connection to the agent. final Thread keepAliveThread = Thread.currentThread(); if (log.isDebugEnabled()) log.debug("keepAliveThread=" + keepAliveThread); auth.keepAlive(new BAsyncResult<String>() { public void setAsyncResult(String newToken, Throwable e) { if (log.isDebugEnabled()) log.debug("KeepAlive.setAsyncResult(newToken=" + newToken + ", e=" + e); try { if (e != null) throw e; // Token has changed? if (!oldToken.equals(newToken)) throw new IllegalStateException("Need to reconnect, oldToken=" + oldToken + ", newToken=" + newToken); // Keep session in JSFS Dispatcher alive if (log.isDebugEnabled()) log.debug("dispatcherService.keepAlive()"); bclient.dispatcherService.keepAlive(newToken); } catch (Throwable ex) { if (log.isDebugEnabled()) log.debug("interrupt keepAliveThread" + keepAliveThread, ex); keepAliveThread.interrupt(); if (log.isDebugEnabled()) log.debug("reconnectToJSFS"); reconnectToJSFS(bclient); } if (log.isDebugEnabled()) log.debug(")KeepAlive.setAsyncResult"); } }); } // while if (log.isDebugEnabled()) log.debug("keepAliveThread=" + Thread.currentThread() + " interrupted - 1"); } catch (InterruptedException ignored) { if (log.isDebugEnabled()) log.debug("keepAliveThread=" + Thread.currentThread() + " interrupted - 2"); } } }; thread.setName("keep-alive"); thread.setDaemon(true); thread.start(); if (log.isDebugEnabled()) log.debug(")keepAlive"); } }