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.
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):
BeanBuilderFactory
to be used by the application is created
from the configuration and installed.
BeanContext
of the application is created and initialized. It
is populated with the default bean definitions shipped with the framework.
ClassLoaderProvider
is created.
ApplicationContext
and its helper objects are created from the
global
BeanContext
.
CommandQueue
is created and installed.
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:
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 ornet.sf.jguiraffe.configURL
for obtaining the URL
pointing to the configuration file.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> ...
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.
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.)
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.
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.