View Javadoc

1   package com.melloware.jukes.gui;
2   
3   import java.awt.AWTException;
4   import java.awt.Color;
5   import java.awt.Dimension;
6   import java.awt.Image;
7   import java.awt.SystemTray;
8   import java.text.MessageFormat;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.Locale;
12  import java.util.Properties;
13  import java.util.ResourceBundle;
14  import java.util.logging.LogManager;
15  
16  import javax.sound.sampled.spi.FormatConversionProvider;
17  import javax.swing.UIManager;
18  import javax.swing.plaf.DimensionUIResource;
19  
20  import org.apache.commons.lang.StringUtils;
21  import org.apache.commons.lang.SystemUtils;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.apache.log4j.Level;
25  import org.apache.log4j.Logger;
26  import org.hibernate.HibernateException;
27  import org.hibernate.exception.JDBCConnectionException;
28  
29  import com.jgoodies.looks.Options;
30  import com.jgoodies.uif.AbstractFrame;
31  import com.jgoodies.uif.action.ActionManager;
32  import com.jgoodies.uif.application.Application;
33  import com.jgoodies.uif.application.ApplicationConfiguration;
34  import com.jgoodies.uif.application.ApplicationDescription;
35  import com.jgoodies.uif.application.ResourceIDs;
36  import com.jgoodies.uif.osx.OSXApplicationMenu;
37  import com.jgoodies.uif.splash.ImageSplash;
38  import com.jgoodies.uif.splash.Splash;
39  import com.jgoodies.uif.splash.SplashProvider;
40  import com.jgoodies.uif.util.ResourceUtils;
41  import com.jgoodies.uifextras.convenience.DefaultApplicationStarter;
42  import com.melloware.jintellitype.JIntellitype;
43  import com.melloware.jintellitype.JIntellitypeException;
44  import com.melloware.jukes.db.Database;
45  import com.melloware.jukes.db.HibernateUtil;
46  import com.melloware.jukes.exception.InfrastructureException;
47  import com.melloware.jukes.gui.tool.Actions;
48  import com.melloware.jukes.gui.tool.MainController;
49  import com.melloware.jukes.gui.tool.MainModule;
50  import com.melloware.jukes.gui.tool.Resources;
51  import com.melloware.jukes.gui.tool.Settings;
52  import com.melloware.jukes.gui.tool.logging.AwtLogHandler;
53  import com.melloware.jukes.gui.view.MainFrame;
54  import com.melloware.jukes.tray.ITrayIcon;
55  import com.melloware.jukes.tray.JukesTrayIcon;
56  import com.melloware.jukes.util.NoFlickerSplashWrapper;
57  import com.sun.media.sound.JDK13Services;
58  
59  /**
60   * This is the main class of the Jukes application. It utilizes the default
61   * application startup process from the JGoodies UI framework.
62   * <p>
63   * Copyright (c) 1999-2007 Melloware, Inc. <http://www.melloware.com>
64   * @author Emil A. Lefkof III <info@melloware.com>
65   * @version 4.0
66   * <p>
67   * @see Actions
68   * @see MainController
69   * @see MainModule
70   * @see MainFrame
71   */
72  public final class Jukes extends DefaultApplicationStarter {
73  
74     private static final Log LOG = LogFactory.getLog(Jukes.class);
75     private static ApplicationDescription description;
76     private static ApplicationConfiguration configuration;
77     private static MainModule mainModule;
78  
79     /**
80      * Defines a bunch of application wide constants, and launches the boot
81      * process for the Jukes application.
82      */
83     public static void main(String[] arguments) {
84        LOG.info("Application Started...");
85        try {
86           // assigns a custom handler for catching uncaught exceptions
87           System.setProperty("sun.awt.exception.handler", AwtLogHandler.class.getName());
88  
89           // set the resource bundle path now, so that we can internationalize
90           // some parameters
91           ResourceUtils.setBundlePath("Resource");
92           
93  
94           // verify that the user has the correct version of the JRE
95           verifyJREVersion();
96  
97           // load the setup information for the application
98           getConfiguration();
99           getDescription();
100 
101          if (SystemUtils.IS_OS_WINDOWS) {
102             fixWindowsTimingBug();
103 
104             if (JIntellitype.checkInstanceAlreadyRunning(Resources.APPLICATION_NAME)) {
105                System.exit(0);
106             }
107          }
108 
109          final Jukes launcher = new Jukes();
110          launcher.boot(description, configuration);
111       } catch (Throwable ex) {
112          LOG.error("Unexpected Exception starting application.\n\n" + ex, ex);
113          System.exit(1);
114       }
115 
116    }
117 
118    /*
119     * (non-Javadoc)
120     * @see com.jgoodies.swing.convenience.DefaultApplicationStarter#getDefaultLogFilePattern()
121     */
122    protected String getDefaultLogFilePattern() {
123       return "%h/.jukes/gui.log";
124    }
125 
126    /**
127     * Configures the splash to set a brown progress bar.
128     */
129    protected void configureSplash() {
130       super.configureSplash();
131       // Create image splash
132       final Image image = ResourceUtils.getIcon(ResourceIDs.SPLASH_IMAGE).getImage();
133       final ImageSplash splash = new ImageSplash(image, true);
134 
135       // Wrap with de-flicker "filter"
136       final SplashProvider splashWrapper = new NoFlickerSplashWrapper(splash);
137       Splash.setProvider(splashWrapper);
138       splash.setNoteEnabled(true);
139       splash.setAlwaysOnTop(true);
140       splash.setProgressBarBounds(60);
141       splash.setTextColor(Color.WHITE);
142       splash.setForeground(Color.YELLOW);
143       splash.setBackground(Color.WHITE);
144    }
145 
146    /**
147     * Configures the user interface.
148     */
149    protected void configureUI() {
150       Options.setUseSystemFonts(true);
151       Options.setDefaultIconSize(new Dimension(16, 16));
152       Options.setPopupDropShadowEnabled(true);
153       UIManager.put("ToolBar.separatorSize", new DimensionUIResource(6, 18));
154       UIManager.put("FileChooser.useSystemIcons", Boolean.TRUE);
155       LOG.info("Adding Look and Feel [net.beeger.squareness.SquarenessLookAndFeel]");
156       UIManager.installLookAndFeel("Squareness", "net.beeger.squareness.SquarenessLookAndFeel");
157       LOG.info("Adding Look and Feel [net.sourceforge.napkinlaf.NapkinLookAndFeel]");
158       UIManager.installLookAndFeel("Napkin", "net.sourceforge.napkinlaf.NapkinLookAndFeel");
159       LOG.info("Adding Look and Feel [com.nilo.plaf.nimrod.NimRODLookAndFeel]");
160       UIManager.installLookAndFeel("NimROD", "com.nilo.plaf.nimrod.NimRODLookAndFeel");
161       LOG.info("Adding Look and Feel [org.jvnet.substance.SubstanceLookAndFeel]");
162       UIManager.installLookAndFeel("Substance", "org.jvnet.substance.SubstanceLookAndFeel");
163       LOG.info("Adding Look and Feel [com.lipstikLF.LipstikLookAndFeel]");
164       UIManager.installLookAndFeel("Lipstik", "com.lipstikLF.LipstikLookAndFeel");
165       super.configureUI();
166    }
167 
168    /*
169     * (non-Javadoc)
170     * @see com.jgoodies.uifextras.convenience.DefaultApplicationStarter#createMainFrame()
171     */
172    @Override
173    protected AbstractFrame createMainFrame() {
174       final MainFrame mainFrame = new MainFrame(mainModule);
175       Application.setDefaultParentFrame(mainFrame);
176       return mainFrame;
177    }
178 
179    /**
180     * Initializes the actions used in this application.
181     */
182    @Override
183    protected void initializeActions() {
184       LOG.debug("Initialize Actions");
185       OSXApplicationMenu.register(ActionManager.get(Actions.HELP_ABOUT_DIALOG_ID), ActionManager
186                .get(Actions.PREFERENCES_ID), ActionManager.get(Actions.EXIT_ID));
187       OSXApplicationMenu.setAboutName(Resources.APPLICATION_NAME);
188 
189    }
190 
191    /**
192     * Load all the SPI sound codecs and log them.
193     */
194    protected void initializeCodecs() {
195       LOG.info("Loading Codecs");
196       final List codecs = JDK13Services.getProviders(FormatConversionProvider.class);
197       for (Iterator iter = codecs.iterator(); iter.hasNext();) {
198          FormatConversionProvider codec = (FormatConversionProvider) iter.next();
199          LOG.info("Sound Codec: " + codec.toString());
200       }
201    }
202 
203    /**
204     * Brings up the application, it therefore initializes the main frame, checks
205     * the setup process, initializes all actions, then builds the main frame,
206     * and finally opens it.
207     */
208    protected void launchApplication() {
209       // Create the module that provides all high-level models.
210       Splash.setNote("Creating Models...", 20);
211       LOG.info("Creating Models...");
212       mainModule = new MainModule();
213 
214       // initialize logging
215       Splash.setNote("Init Logging...", 30);
216       LOG.info("Init Logging...");
217       initializeLogging();
218 
219       // Now add a more sophisticated handler.
220       addMessageHandler();
221 
222       // initialize language from prefs
223       ResourceUtils.setBundle(ResourceBundle.getBundle("Resource", new Locale(MainModule.SETTINGS.getLocale()),
224                Jukes.class.getClassLoader()));
225 
226       // initialize database and hibernate
227       Splash.setNote("Init DB...", 35);
228       LOG.info("Init DB...");
229       initializeDatabase();
230 
231       // Create the controller that provides the major operations.
232       Splash.setNote("Controller...", 40);
233       LOG.info("Creating Controller...");
234       final MainController mainController = new MainController(mainModule);
235 
236       // initialize sound codecs
237       Splash.setNote("Sound Codecs...", 45);
238       initializeCodecs();
239 
240       // Initialize all Actions
241       Splash.setNote("Init Actions...", 50);
242       LOG.info("Init Actions...");
243       Actions.initializeFor(mainModule, mainController);
244       initializeActions();
245 
246       // Create and build the main frame.
247       Splash.setNote("Create Mainframe...", 55);
248       LOG.info("Create Mainframe...");
249       final AbstractFrame mainFrame = createMainFrame();
250 
251       checkSetup();
252 
253       // initialize tray icon if on Windows
254       Splash.setNote("Init Tray...", 60);
255       LOG.info("Init Tray...");
256       intializeTrayIcon((MainFrame) mainFrame);
257 
258       // initialize any browsers on the system
259       Splash.setNote("Init Jintellitype...", 70);
260       LOG.info("Init Jintellitype...");
261       intializeJIntellitype((MainFrame) mainFrame);
262 
263       // add the shutdown listener
264       shutdownHook();
265 
266       Splash.setNote("Building UI...", 80);
267       LOG.info("Building UI...");
268       mainFrame.build();
269 
270       Splash.setNote("Finishing...", 90);
271       LOG.info("Finishing...");
272       mainFrame.open();
273       mainController.checkForOpenTipOfTheDayDialog();
274       LOG.info("Application Finished Loading.");
275    }
276 
277    /**
278     * Return JGoodies UIF ApplicationConfiguration for specified prefix. The
279     * prefix is used as a subdirectory under the .bb directory to have separate
280     * persistent areas, and is also used as a prefix node in the Preferences
281     * (registery) to keep those separate as well.
282     * @return application configuration object.
283     */
284    private static synchronized ApplicationConfiguration getConfiguration() {
285       if (configuration == null) {
286          configuration = new ApplicationConfiguration(Resources.APPLICATION_LOCATION, 
287                   "", // resource properties URL
288                   "docs/help/global/Help.hs", // helpset URL
289                   "docs/help/tips/index.txt"); // Tips index path
290       }
291 
292       return configuration;
293    }
294 
295    /**
296     * Creates application description object.
297     * @return application description.
298     */
299    private static synchronized ApplicationDescription getDescription() {
300       // get the version from the manifest file
301       final String buildFullLabel = getProjectVersion();
302 
303       // trim off the build number
304       final String buildLabel = StringUtils.substringBeforeLast(buildFullLabel, ".");
305       if (description == null) {
306          LOG.info("Jukes Version: " + buildFullLabel);
307          description = new ApplicationDescription(Resources.APPLICATION_NAME, 
308                   Resources.APPLICATION_NAME, // Application long name
309                   buildLabel, // Version
310                   buildFullLabel, // Full version
311                   Resources.APPLICATION_DESCRIPTION, // Description
312                   Resources.APPLICATION_COYPRIGHT, // Copyright
313                   Resources.APPLICATION_VENDOR, // Vendor
314                   Resources.APPLICATION_URL, // Vendor URL
315                   Resources.APPLICATION_EMAIL); // Vendor email
316       }
317 
318       return description;
319    }
320 
321    /**
322     * Attempts to read the version number out of the pom.properties. If not
323     * found then RUNNING.IN.IDE.FULL is returned as the version.
324     * <p>
325     * @return the full version number of this application
326     */
327    private static String getProjectVersion() {
328       String version;
329 
330       try {
331          final Properties pomProperties = new Properties();
332          pomProperties.load(Jukes.class.getResourceAsStream("/META-INF/maven/com.melloware/jukes/pom.properties"));
333          version = pomProperties.getProperty("version");
334       } catch (Exception e) {
335          version = "RUNNING.IN.IDE.FULL";
336       }
337       return version;
338    }
339 
340    /**
341     * Listens for application shutdown and cleans up any resources and properly
342     * compacts the HSQLDB to prevent restart problems with a .lck file being
343     * leftover. Also is a good citizen by properly cleaning up Windows DLL's
344     * used.
345     */
346    private static void shutdownHook() {
347       // Set shutdown hook for HSQLDB and DLLS
348       Runtime.getRuntime().addShutdownHook(new Thread() {
349          public void run() {
350             LOG.info("Shutdown hook.");
351             if (JIntellitype.isJIntellitypeSupported()) {
352                LOG.info("Cleaning up JIntellitype");
353                JIntellitype.getInstance().cleanUp();
354             }
355 
356             // shut down database and hibernate
357             try {
358                HibernateUtil.shutdown();
359             } catch (RuntimeException ex) {
360                LOG.error("Error shutting down database.", ex);
361             }
362          }
363       });
364    }
365 
366    /**
367     * If the Java version installed is not AT LEAST the correct version then
368     * throw error and terminate. Using common-lang SystemUtils.
369     */
370    private static void verifyJREVersion() {
371       final float requiredVersion = 1.60f;
372 
373       LOG.info("Checking Java version...");
374       LOG.info("Current Java version: " + SystemUtils.JAVA_VERSION);
375 
376       if (!SystemUtils.isJavaVersionAtLeast(requiredVersion)) {
377          LOG.info("Java version is not sufficient to run this application!");
378          LOG.info("Current Java version: " + SystemUtils.JAVA_VERSION);
379          LOG.info("REQUIRED Java version: " + requiredVersion);
380 
381          final String errorMsg = ResourceUtils.getString("application.java.version.error.text");
382          final String displayMsg = MessageFormat.format(errorMsg, new Object[] { SystemUtils.JAVA_VERSION,
383                   Float.toString(requiredVersion) });
384          LOG.error(displayMsg);
385          System.exit(1);
386       }
387    }
388 
389    /**
390     * According to Sun Bug 6435126 found here:
391     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6435126 On some windows
392     * machines the clocks speeds up like crazy while using the Jukes and
393     * multiple timers.
394     */
395    private static void fixWindowsTimingBug() {
396       LOG.info("Fixing Windows Timing Bug...");
397       new Thread() {
398          {
399             this.setDaemon(true);
400             this.start();
401          }
402 
403          public void run() {
404             while (true) {
405                try {
406                   Thread.sleep(Integer.MAX_VALUE);
407                } catch (InterruptedException ex) {
408                }
409             }
410          }
411       };
412    }
413 
414    /**
415     * Initalize HSQLDB and Hibernate. If any problems exit the application.
416     */
417    @SuppressWarnings("deprecation")
418    private void initializeDatabase() {
419       LOG.info("Initializing Database.");
420       final String remoteURL = MainModule.SETTINGS.getRemoteDatabaseURL();
421 
422       try {
423          String dbLocation = MainModule.SETTINGS.getDatabaseLocation().getAbsolutePath();
424          dbLocation = dbLocation + SystemUtils.FILE_SEPARATOR + Resources.APPLICATION_LOCATION;
425          // start database
426          Database.startup(dbLocation, Resources.APPLICATION_LOCATION);
427 
428          // if there is a remote URL then we need to set it in the Hibernate
429          // config
430          if ((StringUtils.isNotBlank(remoteURL)) && (!Settings.DEFAULT_REMOTE_DATABASE_URL.equals(remoteURL))) {
431             HibernateUtil.setRemoteUrl(remoteURL);
432          }
433          // initialize Hibernate
434          HibernateUtil.initialize();
435 
436          // set the write delay on the HSQL database so writes are immediate
437          if (HibernateUtil.isHSQLDialect()) {
438             // initializes Hibernate
439             HibernateUtil.getSession();
440             Database.setWriteDelay(HibernateUtil.getSession().connection(), "FALSE");
441          }
442       } catch (JDBCConnectionException ex) {
443          LOG.error(remoteURL + " connection not established.  Make sure the server is running.");
444          System.exit(1);
445       } catch (InfrastructureException ex) {
446          LOG.error("InfrastructureException", ex);
447          System.exit(1);
448       } catch (HibernateException ex) {
449          LOG.error("HibernateException", ex);
450          System.exit(1);
451       } catch (Exception ex) {
452          LOG.error("Exception", ex);
453          System.exit(1);
454       }
455    }
456 
457    /**
458     * Initalize java.util.logging for any libraries used in the application that
459     * do not use commons-logging.
460     */
461    private void initializeLogging() {
462       try {
463          super.configureLogging();
464 
465          // configure java.util.logging
466          LogManager.getLogManager().readConfiguration(ClassLoader.getSystemResourceAsStream("java.logging.properties"));
467 
468          // set log4j level from prefs
469          Logger.getRootLogger().setLevel(Level.toLevel(MainModule.SETTINGS.getLogLevel()));
470          Logger.getLogger("com.melloware").setLevel(Level.toLevel(MainModule.SETTINGS.getLogLevel()));
471          Logger.getLogger("com.melloware.jukes.gui").setLevel(Level.toLevel(MainModule.SETTINGS.getLogLevel()));
472       } catch (Exception ex) {
473          LOG.warn("Error configuring logging.", ex);
474       }
475    }
476 
477    /**
478     * Initializes the JIntellitype library if on Windows. Else do nothing.
479     * <p>
480     * @param mainFrame the MainFrame of the application to store the
481     *           JIntellitype ref
482     */
483    private void intializeJIntellitype(MainFrame mainFrame) {
484       if (JIntellitype.isJIntellitypeSupported()) {
485          try {
486             mainFrame.setJintellitype(JIntellitype.getInstance());
487          } catch (JIntellitypeException ex) {
488             LOG.error("JIntellitypeException", ex);
489          }
490       }
491    }
492 
493    /**
494     * Initializes the tray icon if on Windows. Else do nothing.
495     * <p>
496     * @param mainFrame the MainFrame of the application to store the TrayIcon
497     *           ref
498     */
499    private void intializeTrayIcon(MainFrame mainFrame) {
500       if (SystemTray.isSupported()) {
501          try {
502             final ITrayIcon trayIcon = new JukesTrayIcon(mainFrame);
503             mainFrame.setTrayIcon(trayIcon);
504          } catch (AWTException ex) {
505             LOG.error("AWTException", ex);
506          }
507       }
508    }
509 
510 }