The central Application class

Each JGUIraffe application is represented by an object derived from the central Application class. Application provides fundamental services that can be used by concrete applications, e.g. life-cycle events, configuration support, handling of a main window, and many more. It also serves as the entry point in the application, i.e. it provides a main() method that can directly be used.

Starting an application is a complex operation. A lot of initialization steps have to be performed until the main window is displayed, and the application is ready for user interaction. This section describes these steps and discusses hooks for customization.

After that we discuss what happens when an application terminates.

Initializations at startup

The main entry point into a JGUIraffe application is the static startup() method of Application. This method is called with the Application instance to launch. This can either be a direct instance of the Application class or of a derived class. Application is fully functional and can be used out of the box. In this case there is no need to define a custom main() method; the one provided by Application can be used directly. Only if special customization is required, a sub class can be created, which overrides some of the hook methods.

During the startup phase the following initializations are performed (in this order):

  • The location of the main configuration file of the application is determined. For this purpose system properties are evaluated.
  • A hook method for command line processing is invoked.
  • The main configuration file of the application is read.
  • The BeanBuilderFactory to be used by the application is created from the configuration and installed.
  • The global BeanContext of the application is created and initialized. It is populated with the default bean definitions shipped with the framework.
  • The global ClassLoaderProvider is created.
  • Further bean definition files that can be specified in the main configuration file are read.
  • The ApplicationContext and its helper objects are created from the global BeanContext.
  • The main builder script defining the UI of the application is determined from the configuration. If it is specified, it is processed, and the main application window is created from it. However, this is optional. An application may choose not to define its main window this way.
  • If a main window is defined, it is initialized. This includes setting its position and size to the values stored at the last execution of the application.
  • The applications's CommandQueue is created and installed.
  • If a main window is defined, it is displayed now.

Hooks

The initialization steps outlined above are performed by well-defined methods of the Application class. In many cases it is possible to hook into the initialization process by overriding corresponding methods. This section discusses the methods that can be used as hooks and provides further information about the initialization procedure.

Determining the main configuration file
A JGUIraffe application uses the Commons Configuration library for reading configuration data. It expects the configuration data to be defined in a so-called configuration definition file, which is an XML document referring to an arbitrary number of configuration files (or other configuration sources). The name of this configuration definition file must be known in order to read it. It can be specified in the following ways:

  • As resource name: In this case it will be searched in the class path. This is appropriate, for instance, if the whole configuration can be shipped with the application in a jar archive.
  • As URL: Using this approach an arbitrary URL to the configuration file can be specified, e.g. pointing to a file or to a web server.
The Application class has the properties configResourceName and configURL for specifying the location of the configuration file either as resource name or as URL. They can be set before the Application to be started is passed to the static startup() method. If they are not set, the framework reads the values of the following system properties:
  • net.sf.jguiraffe.configName for obtaining the resource name of the configuration file or
  • net.sf.jguiraffe.configURL for obtaining the URL pointing to the configuration file.
If neither the properties of the Application instance nor the system properties are defined, the framework uses the default name config.xml and tries to load the file from the class path.

Command line processing
For this purpose the processCommandLine(String[] args) method is invoked. The default implementation of this method is empty. A class derived from Application can override this method to perform arbitrary evaluations of the command line arguments passed to the application at startup.

Reading the configuration
The application's configuration file is actually read by the createConfiguration() method. An application that needs a completely different mechanism of defining the location of the configuration file can override this method. The HierarchicalConfiguration object returned by this method will become the global configuration of this application.

Setting up the BeanBuilderFactory
The BeanBuilderFactory is needed for obtaining a reference to the BeanBuilder, i.e. the component that processes bean definition files. Because the BeanBuilderFactory cannot be specified in bean definition file itself, it is obtained from the application's configuration. An application may specify a custom factory class; a fragment of a configuration file which does exactly this could look as follows:

<config>
  <framework>
    <builder>
      <beanBuilderFactory config-class="my.bean.FactoryClass">
      </beanBuilderFactory>
      ...
    </builder>
    ...
  </framework>
  ...
         
However, using a custom bean builder factory should only be necessary for applications with very specific requirements.

Setting up the BeanContext
With the BeanBuilderFactory up and running the bean definition files can now be processed and a BeanContext allowing access to the beans defined there can be created. At first a bean definition file with default declarations for fundamental service beans is read. This file is shipped with the framework and produces a default configuration. It is possible to override these default beans with custom ones, which is explained in a few minutes.

Setting up the ClassLoaderProvider
A ClassLoaderProvider plays an important role in a complex application setup using multiple class loaders: In bean definition files the classes of the beans to be created are defined using their fully qualified names. When the beans are actually created these names have to be resolved to concrete Class objects. For each bean it is possible to specify a concrete ClassLoader to be used for this purpose. A ClassLoaderProvider is a kind of registry for class loaders. Class loaders can be registered under a symbolic name. In bean declarations these symbolic names can be used again for referring to a specific class loader. If no name for a class loader is provided in a bean definition, the default class loader is used. For the most desktop applications this default should be appropriate.

If an enhanced class loader setup is required, the initClassLoaderProvider() method can be overriden. This method is passed the default ClassLoaderProvider object, which was created from the default bean definitions. A custom implementation of this method can register application-specific class loaders at the passed in provider, or, alternatively, create a completely different ClassLoaderProvider - the object returned from this method will become the global ClassLoaderProvider used by all bean builder operations.

Custom bean definitions
After the creation of the ClassLoaderProvider the framework searches the configuration for additional bean definitions to be read. Per default, a special section of the application's configuration file is evaluated for determining the names of additional bean definition files. The following example shows a fragment of a configuration file that defines additional bean definition files:

<config>
  <framework>
    <builder>
      <beandefinitions>
        <beandefinition>classpath:/jelly_scripts/applicationbeans.jelly</beandefinition>
        <beandefinition>url:http://www.myserver.com/config.jelly</beandefinition>
      </beandefinitions>
      ...
  

Under <beandefinitions> an arbitrary number of <beandefinition> elements can occur. Each specifies the name of a bean definition file. Bean definition files define service beans that are available for the application. We will provide more information about this topic when we discuss the dependency injection builder in a later chapter. Here we just focus on the way such bean definitions can be passed to the application.

The easiest way of adding further bean definitions is to list the files to be processed in this section in the configuration file. If an application needs more flexibility, it can override the findBeanDefinitions() method. This method returns a collection of Locator objects pointing to the bean definitions to be processed. The base implementation of the findBeanDefinitions() method uses an instance of the LocatorConverter class to transform textual representations to concrete Locator implementations. Such textual representations start with a prefix (e.g. classpath: or url:) followed by the actual data of the Locator. Refer to the documentation of the LocatorConverter class for more details.

Providing custom bean definitions is a powerful means of defining the application's behavior. It is good practice to define central services of the application as beans, which can be accessed by all application components acting as clients of these services. It is also possible to override any or all of the default beans defined by the framework. To do this, just define a bean with the same name as a standard bean in an additional bean definition file. The new bean will replace the standard bean. This also works if the bean that was replaced is referenced by other standard beans: these beans will also use the new bean. More information about standard beans can be found in the sub section Application standard beans.

Sometimes, it may not be possible to define a bean in a bean definition script. For instance, the bean may have to be looked up from an external service dynamically, or complex logic is required for its construction. In cases like that Application offers a method that can be used to add already existing beans (created manually by the application) to the global bean context: addBeanDuringApplicationStartup(). As the name implies, this method can only be called during application startup. An application typically overrides some of the hooks described in this section and can then invoke this method passing in custom beans. The last possible point in time is the initialization of the main window (see below). Here the beans in the global context are accessed; therefore, all custom beans must be in place.

Setting up the ApplicationContext
Each application is associated with a single instance of the ApplicationContext class. This instance especially holds references to some fundamental service objects (like the resource manager, the configuration, or the GUI synchronizer), and provides some important functionality often needed by applications.

Creation of the ApplicationContext is done by the createApplicationContext(). The default implementation of createApplicationContext() performs some of the initializations already discussed (reading the configuration, setting up the BeanContext and the ClassLoaderProvider, processing additional bean definition files). Finally, it has a fully initialized BeanContext. From this context the application context is obtained. So for applications that want to replace the standard ApplicationContext by a custom implementation, the easiest way is to override the bean definition for the application context bean. To do this a bean with the name jguiraffe.applicationContext has to be defined in an additional bean definition file.

Initializing the main window
An important feature of the JGUIraffe library is its support for builder scripts defining UI elements. So it is naturally that this feature is also used here for defining the application's main GUI. Initialization of the GUI is performed by the initGUI() method. The default implementation obtains the name of the builder script with the GUI definition from the main configuration file. It can be defined there as in the following example:

<config>
  <framework>
    <builder>
      <mainScript>/jelly_scripts/mainWindow.jelly</mainScript>
      ...
  

If the mainScript property is defined, the referenced builder is load from the class path and executed. If this results in a window, setMainWindow() is called on the ApplicationContext to install the application's main window. Applications that want to use a different way of defining their GUI can override initGUI(). It does not matter how the actually create their main window. All they have to do is calling setMainWindow() to register the main window with the application context.

Setting up the CommandQueue
Longer-running tasks must not be run on the event dispatch thread because otherwise the GUI will block. JGUIraffe uses an object implementing the CommandQueue interface for offering support for background tasks. The concrete implementation of this interface is created by the createCommandQueue() method. The default implementation just obtains the queue object from the global BeanContext.

It should rarely be necessary to replace the CommandQueue implementation. In such cases, the best was is to override the jguiraffe.commandQueue bean definition. Overriding createCommandQueue() may make sense, for instance, if an application wants to register specific listeners at the queue; these listeners can receive notifications e.g. when commands are executed or if the queue changes from the idle to the busy state or vice versa.

So far the most important hook methods defined by the Application class. Note that for all of these methods fully functional default implementations are provided, which should be sufficient in many cases. Only if specific behavior is required, some of these methods can be overridden.

Shutdown

The Application class is responsible for the whole life-cycle of the application it represents. Consequently, it also provides methods for terminating the application: the shutdown() methods.

There are two overloaded variants of the shutdown() method: one is interactive, the other is not. Interactive in this context means that the method first checks whether it is safe to exit the application now and prompts the user if necessary. A JGUIraffe application can start long-running tasks in a background thread. It is possible that such a background task is still running when the user decides to exit the application (e.g. by clicking the close icon of the main window). In this case the shutdown() method displays a message box with a corresponding warning and asks whether the user really wants to exit the application. Only if the user confirms this message box, the shutdown sequence continues. (For more information about background tasks refer to the Commands section.)

The content of the message to be displayed to the user if there are still background threads running is determined by the arguments passed to the shutdown() method. Here a resource ID for the caption and one for the actual message can be specified. The sub section Convenience methods provides more information about these resource IDs.

The overloaded shutdown() method does not take any arguments. It does not check whether background tasks are running. So this step of the shutdown sequence is skipped.

If there are no more background tasks or the user decided to exit anyway, Application processes all registered shutdown listeners. At any time of the lifetime of the central Application object before shutdown() is called event listeners of type ApplicationShutdownListener can be registered by calling the addShutdownListener() method. The ApplicationShutdownListener interface defines two methods:

  • canShutdown() is invoked first and checks whether the application can shutdown now. If an ApplicationShutdownListener implementation returns false, the shutdown sequence is aborted. A use case for this method could be to check whether there are still unsafed changes and prompt the user whether the application should really exit. If the user denies this, the listener can abort the operation. (The same could also be achieved by registering a listener at the application's main window.)
  • Only if all registered ApplicationShutdownListener objects have returned true in their canShutdown() method, the second method, shutdown(), is invoked on all listeners. This method notifies the listeners that the application is actually going to exit. An implementation could for instance perform some cleanup or free resources used by the application.

In the next phase the protected onShutdown() method of Application is called. The base implementation of this method takes care that the user configuration is stored correctly. The user configuration is a configuration object managed by the application which can be populated with arbitrary data. Here also the position and the size of the main window are stored so that they can be restored when the application starts the next time. The user configuration is stored in the user's home directory. So if the application is started by different users, they all have their own user configuration.

After that some internal cleanup is performed. Resources obtained during the execution of the builder scripts executed in the initialization phase are released. Eventually the exitApplication() method is invoked which calls System.exit() and terminates the application.

Exit handlers

Well, in fact calling System.exit() is just the default behavior at the end of a successful shutdown operation. There may be scenarios in which it is not desired to terminate the whole Java virtual machine, e.g. when the application is running in a managed environment. For such cases Application allows setting a so-called exit handler.

An exit handler simply has to implement the Runnable interface. It can be passed to the setExitHandler() method of Application. At the end of a shutdown operation - if there is no veto from a shutdown listener - the exit handler is invoked. This is the last operation performed by the Application object. It is then in the responsibility of the concrete exit handler implementation to actually complete the shutdown. As was already mentioned, the default exit handler calls System.exit(). If you need other shutdown logic, create a Runnable object implementing the desired behavior and pass it to Application's setExitHandler() method.