Skip navigation links

A Common Application Framework 2.0

The Common Application Framework (ACAF) is used as base to implement technology-free, robust and clean business classes of an application, which should be connected to different other frameworks, without affecting the business classes itself.

See: Description

Packages 
Package Description
de.tmasoft.acaf.attributes
This package contains interfaces and concrete classes for simple attributes.
de.tmasoft.acaf.attributes.comparators
This package contains classes, which can be used to compare attributes implementing the interface AttributeInterface.
de.tmasoft.acaf.base
This package is the core package of ACAF containing the base interfaces and classes, which define the component-attribute-container composite to build up business classes and the Visitor interface, which may be implemented by visitor classes to walk through the composite.
de.tmasoft.acaf.containers
This package contains the concrete container attribute classes implementing the interfaces ComponentContainerInterface and ContainerReferenceInterface Containers contain components implementing the interface ComponentInterface and play the composite role for components in the composite design pattern.
de.tmasoft.acaf.exception
This package contains the exception interface and base class of the framework.
de.tmasoft.acaf.io
This package contains interfaces and classes used to store and retrieve business objects in/from external datastores.
de.tmasoft.acaf.jdbc
This package contains the O/R mapping classes used to store and retrieve business objects in/from a relational database.
de.tmasoft.acaf.msgcat
This package contains the message catalog interface and base class used to internationalize your application.
de.tmasoft.acaf.parser
This package contains visitor classes, which can be used to manage business objects using command lines.
de.tmasoft.acaf.util
This package contains several utility classes like the classes for duration handling Duration and SimpleDurationFormat, the extended Tokenizer with look ahead a state reset functionality, the ThreadLocale used to manage a thread specific locale, the UUIDGenerator generating unique identifiers according to the Leach-Salz specification and many others.
de.tmasoft.acaf.visitors
This package contain general purpose visitor classes, used to reset all attribute values to their default value (ClearAttributesVisitor), the visitor to create a deep copy of a business object tree (CopyComponentsVisitor), the visitor to retrieve a component from a business object composite (GetComponentVisitor) and many others.
de.tmasoft.acaf.xml
This package contains the classes to serialize/deserialize a business object tree to/from a XML document.

The Common Application Framework (ACAF) is used as base to implement technology-free, robust and clean business classes of an application, which should be connected to different other frameworks, without affecting the business classes itself.

A Common Application Framework

The Common Application Framework promotes clean business classes, which contain only code for business relevant processes. It keeps the business classes free from code, required by technical frameworks, e.g. for O/R mapping, serialization or GUIs. This helps to adopt the software design principle "Separation of Concerns".

Beside the base interfaces and classes used to build business classes, the framework contains a package for O/R mapping, used to store business objects in relational databases, a package for XML serialization/deserialization of business objects, and many other utility classes. The following list highlights the main features and concepts of ACAF:

Software Architecture

The Component-Attribute-Container Composite

The main purpose of this framework is to build business classes based on certain interfaces and classes of the framework. These interfaces and classes are:

These interfaces and classes define a composite, where the class Component plays the composite role for attributes implementing the interface AttributeInterface and attribute classes implementing the interfaces ComponentContainerInterface or ContainerReferenceInterface play the composite role for components implementing the interface ComponentInterface. This composite may be walked through by a visitor implementing the interface Visitor. The visitor design pattern is one of the main concepts of the framework and visitors are used by many packages to access the business objects and attributes of a business object tree.

ComponentInterface and Component, the Base for Business Classes

Business classes participating in the framework must implement the interface ComponentInterface. They may extend the base class Component, which implements most of the methods defined by the interface. In the following, the term component is used as an equivalent for business class.

The main properties of a component are the component type and the component name. The component type identifies a business class in the class composite. For many business classes, the relationship between the class and the component type is a 1..1 relationship, which means, that the class name could be used as component type. However, for business classes, which are used more than once in the class composite, different component types are required. E.g. a business class Address may represent the address of a person and the address of a company. In this case two component types, PersonAddress and CompanyAddress are required by the framework to differentiate the address business objects. Such a discriminator is especially required by the O/R mapping classes of the package de.tmasoft.acaf.jdbc to map the business objects to the corresponding database tables.

The component name identifies a business object uniquely in a dictionary container implementing the interface ComponentContainerInterface. The component name can be considered as key of a component. When a business object is stored in a database, the component name is usually part of the primary key.

A component may have a relationship to a parent component. All business objects (except the root component of a business object tree) have a parent component. E.g. the class AddressBook contains components of the class Person and a person contains components of the class Address. In this case, the parent component of a person is the address book, which contains the person as contact and the person is the parent component of its addresses. When the application supports only one address book, the address book is a root component and has no parent.

Attributes

Business classes participating in the framework must implement their attributes using the attribute classes of the packages de.tmasoft.acaf.attributes and de.tmasoft.acaf.containers, or with custom attribute classes implementing the interface AttributeInterface.

The main properties of an attribute are the keyword and the value. The keyword identifies the attribute uniquely in its business object. The keyword may be equal or similar to the attribute name. However, in a class hierarchy attributes may have the same name in different classes, when they are declared private! To differentiate these attributes, this framework requires different attribute keywords. Attribute keywords are used by the O/R mapping classes of the package de.tmasoft.acaf.jdbc to map the attribute value to a certain database column and by the XML serialization classes of the package de.tmasoft.acaf.xml to create the attributes in the XML tags.

The value of an attribute is the the classic type specific value encapsulated by the concrete attribute class. The classes of the package de.tmasoft.acaf.attributes provide simple attribute classes for all primitive Java types (String, int, float, Date, ...) and some additional classes for enumeration, set, time, duration and references. The classes of the package de.tmasoft.acaf.containers provide container attribute classes. The value of a container attribute is the component collection in the container.

Attributes must be added to the attribute list of the component base class after construction, calling the method Component.addAttribute(AttributeInterface). This makes the attributes accessible by visitors. When the component base class is not used, a similar mechanism has to be implemented by your business classes to make methods like ComponentInterface.visitAttribute(String,Visitor) or ComponentInterface.visitAttributes(Visitor) work. Attributes are usually created and added in the constructor of the business class. So a typical constructor looks like this:

public Person (String                componentName, 
               ComponentInterface<?> parentComponent)
{
    super (componentName, 
           Constants.ComponentTypes.PERSON,
           parentComponent);
    firstName = new StringAttribute (Constants.Attributes.Person.FIRST_NAME);
    addAttribute (firstName);
    lastName = new StringAttribute (Constants.Attributes.Person.LAST_NAME);
    addAttribute (lastName);
}

The simple attributes may be initialized with an initial value and a default value. The initial value is assigned once at construction time. The default value is used by the method AttributeInterface.clear() to reset the attribute value to its default. The visitor class ClearAttributesVisitor uses this feature to reset all attributes of a business object tree.

One of the features provided by the simple attribute classes is the conversion of the value to/from a string representation. This feature may be used by serialization or user-interface frameworks to exchange attribute values between text based files or a GUI. It is used by the XML serialization classes of the package de.tmasoft.acaf.xml and by the command line parser classes of the package de.tmasoft.acaf.parser.

Containers

Container attributes make the component-attribute-container composite complete. Container attributes implement the interfaces ComponentContainerInterface or ContainerReferenceInterface, which extend the interface AttributeInterface. So they can be added to the attribute list of the component base class and they play the composite role for components implementing the interface ComponentInterface. The components contained in the container can be considered as value of the attribute. In contrast to the simple attributes, container attributes do not support a conversion of the value to/from a string representation.

Containers are used to implement a composition relationship between a parent component owning the container and the so called child-components in the container. A composition is a strong relationship, which means, if the parent component is deleted, the child-components are deleted as well. There are three different types of container attributes, which are defined and implemented by the following interfaces and classes:

  1. ContainerReferenceInterface, ContainerReferenceAttribute - used for 0..1 relationships
  2. DictionaryContainerBase - used for 0..n relationships and dictionary containers, which use the component name as unique key
  3. SequenceContainerInterface, SequenceContainerBase - used for 0..n relationships with a user defined ordering schema

The container classes in the package de.tmasoft.acaf.containers apply the strategy design pattern. The base classes DictionaryContainerBase and SequenceContainerBase define the strategy, and the derived classes implement the concrete strategy by initializing the base classes with the appropriate collection class.

The container classes implement a runtime type-checking using the component type of the components to be inserted into the container. Valid component types must be passed to the constructor of the container classes or registered by the method ComponentContainerInterface.registerType(String).

Reference Attributes

Reference Attributes are a special kind of simple attributes and used to implement associations between business objects. Reference attributes implement the interface ComponentReferenceInterface. ACAF provides two implementations of this interface, the classes ComponentReferenceAttribute and WeakReferenceAttribute. Reference attributes have a string representation, as all simple attributes, which is denoted as reference path. The reference path consists of container attribute keywords and component names or index expressions separated by a dot. The reference path specifies the referenced component, starting at a so called root component.

The sample competition management application shows, how reference attributes may be used. The class TeamClassification contains the reference attribute team, which references either a Team registered for the competition or another TeamClassification. The reference attribute is initialized with the competition, the TeamClassification belongs to as root component so the reference path starts at the competition. When a Team with the component name Team-4711 is referenced, which is contained in the Teams container of the competition the reference path looks like this: Teams.Team-4711. Another TeamClassification is referenced to define, which team of a Group or Pairing continues the competition in the next round. In this case the reference path references a certain index in the Classification container of a Group or Pairing and may look like this: Rounds.QR.Groups.Group-A.Classification[0]. In this case, the winner (first index: 0) of Group-A in the qualifying round QR is referenced.

Note, that the team reference attribute is implemented as WeakReferenceAttribute. Weak reference attributes resolve the reference path every time the referenced component is accessed via the method WeakReferenceAttribute.getComponent(). This is useful in this case, because the referenced component may change, when the position of the team classifications change in a group. Weak references should be generally used, when the reference path may contain index expressions.

In contrast to weak references, strong references are implemented by the class ComponentReferenceAttribute. This class expects, that the referenced component exists, when the path value is set. The reference path is immediately resolved and an exception is thrown, when the specified component doesn't exist.

The Component Factory

The framework uses the ComponentFactory to create business objects with their component type, e.g. when they are de-serialized or created by the classes of the O/R mapping package de.tmasoft.acaf.jdbc or the parser package de.tmasoft.acaf.parser. The component factory is implemented as singleton and applies the prototype and abstract factory design patterns to create business objects. For this purpose the business classes must implement the method ComponentInterface.cloneComponent(String,String,ComponentInterface). This is the only technical method in a business class, which is required by ACAF. A typical implementation of this method looks like this:

public Person cloneComponent (String                componentName, 
                              String                componentType,
                              ComponentInterface<?> parentComponent)
{
    return new Person (componentName, parentComponent);
}

Note, that the component type has to be passed to the constructor only, when the business class represents more than one component type. Prototypes of all business classes must be registered at the factory in the initialization phase of the application calling the method ComponentFactory.registerPrototype(ComponentInterface).

Visitors

The component-attribute-container composite may be walked through by a visitor implementing the interface Visitor. Components, attributes and containers play the element role in the visitor design pattern. ACAF uses visitors in many packages to access the attributes of the business objects. Visitors may also be used, when the framework is extended or connected to other frameworks. Visitors should off course not be used to bypass information hiding and to access attributes through the backdoor! When a business process requires access to an attribute value, an according access method should be implemented in the business class.

However, the visitor pattern may be extended to business process visitors. For this purpose an abstract base class for your business classes should be implemented, which defines a method, which accepts the business visitor:

public abstract class BaseComponent<C extends BaseComponent<?>> extends Component<C>
{
    protected BaseComponent (String                componentName, 
                             String                componentType,
                             ComponentInterface<?> parentComponent)
    {
        super (componentName, componentType, parentComponent);
    }
    
    public abstract void acceptBusinessVisitor (BusinessVisitor visitor) 
        throws AcafException;
}

Than you may implement an abstract base class for your business visitors. In the following example, the visitor is used to visit business objects of the classes Address, AddressBook, Person and PhoneNumber. These classes are derived form the base class BaseComponent and implement the method acceptBusinessVisitor(BusinessVisitor). In this method they should call the appropriate callback method of the business visitor passing itself (this) as parameter. When the business visitor is sent to a component, it redirects itself to the business component in the method visitComponent(ComponentInterface) because it can only be used with business components. When the visitor is sent to a container, it forwards itself to all components in the container. Note, that the business visitor doesn't process simple attributes! Concrete business visitors may override the empty callback methods for the business classes and process the passed business objects. Here the sample implementation:

public abstract class BusinessVisitor extends VisitorBase
{
    public void visitAddress (Address address)  throws AcafException
    {}
    
    public void visitAddressBook (AddressBook addressBook) throws AcafException
    {}
    
    public void visitBusinessObject (BaseComponent businessObject)
        throws AcafException
    {
        businessObject.acceptBusinessVisitor (this); 
    }
    
    public void visitComponent (ComponentInterface<?> component)
        throws AcafException
    {
        visitBusinessObject ((BaseComponent) component);
    }
    
    public <C extends ComponentInterface<?>> void visitComponentContainer 
        (ComponentContainerInterface<?, C> container) throws AcafException
    {
        container.visitComponents (this);
    }
    
    public <C extends ComponentInterface<?>> void visitContainerReference 
        (ContainerReferenceInterface<?, C> componentReference) throws AcafException
    {
        componentReference.visitComponent (this);
    }
    
    public void visitPerson (Person person) throws AcafException
    {}
    
    public void visitPhoneNumber (PhoneNumber phoneNumber) throws AcafException
    {}
    
    public <C extends ComponentInterface<?>> void visitSequenceContainer 
        (SequenceContainerInterface<?, C> container) throws AcafException
    {
        container.visitComponents (this);
    }
}

Attribute Observers

Attribute play the subject role in the observer design pattern and are observable by concrete observers implementing the interface AttributeObserver. Observers are especially useful to update a GUI element when the attribute value has been changed. The observer interface may be implemented by mediators, which exchange the value between the attribute and the GUI element (see mediator design pattern). To attach an observer to an attribute, a visitor may be used, which is sent to the attribute of a business object calling the method ComponentInterface.visitAttribute(String,Visitor):

public class ObserverAttacher extends VisitorBase
{
    private AttributeObserver observer;
    
    public ObserverAttacher (AttributeObserver observer)
    {
        this.observer = observer;
    }
    
    public void visitAttribute (AttributeInterface<?> attribute)
    {
        attribute.attachObserver (observer);
    }
    
    public <C extends ComponentInterface<?>> void visitComponentContainer
                                    (ComponentContainerInterface<?, C> container)
    {
        container.attachObserver (observer);
    }
    
    public <C extends ComponentInterface<?>> void visitComponentReference
                                    (ComponentReferenceInterface<?, C> reference)
    {
        reference.attachObserver (observer);
    }
    
    public <C extends ComponentInterface<?>> void visitContainerReference
                                    (ContainerReferenceInterface<?, C> reference)
    {
        reference.attachObserver (observer);
    }
    
    public <C extends ComponentInterface<?>> void visitSequenceContainer
                                    (SequenceContainerInterface<?, C> container)
    {
        container.attachObserver (observer);
    }
}

Thread-Safeness

All classes of ACAF are thread safe by synchronizing the methods, which modify the state of a class, except documented otherwise. This means for example, that attributes cannot be modified by another thread, when their observers are notified. So attribute observers should not perform any long running or blocking operations!

Also the methods accepting a visitor are synchronized. This means, that a component cannot be modified while a visitor is sent to its attributes and containers cannot be modified while a visitor is sent to its components. So, using the visitor is the thread-safe way to iterate through a container in contrast to an iterator! The framework itself uses only visitors and never iterators to iterate through the components in a container.

Internationalization

The package de.tmasoft.acaf.msgcat contains some interfaces and classes, which are used to internationalize applications using the framework. The attribute classes and the component base class de.tmasoft.acaf.base.ComponentBase can be initialized with a message catalog implementing the interface MessageCatalogInterface, which is used to get the localized attribute keyword or component type.

Message catalogs are used to get a localized text for a key. In Java usually property resource files are used for this purpose, and the base class MessageCatalogBase can be used to access such a resource file by a {link java.util.ResourceBundle}. This class uses the utility class ThreadLocale to get the texts with the locale of the current thread. So, in a multi-threaded application a thread specific locale can be set. This is especially useful in server applications, which should use the locale of the client, which issues the service request (i.e. a browser).

However, applications are free to implement the message catalog interface in any other way to get the localized texts from other sources.

ACAF itself uses property resource files with the base name AcafMessageTable as source for the localized error messages and texts. These resource files are located in the package de.tmasoft.acaf.msgcat. The package contains currently the following localizations:

When other localizations are required, the appropriate resource file has to be created and placed in this package. To create a new property resource file, the message source file AcafMessageTable.msg, located in the same package, may be extended and compiled with the Message Compiler . The author would be pleased if you sent the extended message file to him, so that other users may benefit from your localization.

ACAF and Project Management

The following project plan shows the activities of the implementation phase, which can be started as soon as the business class model has reached a stable state. Implementation starts with defining constants for attribute keywords and component types. When the applications should be localized, the localized texts for attribute keywords, component types and enumeration values should be defined. Then the enumeration values has to be implemented. After that, all prerequisites are complied to implement the business classes and optionally a facade to serialize/deserialize the business objects to/from XML.

When the business classes are implemented, the dependent business processes may be implemented.

When the business objects should be stored in a relational database, the database tables and constants for the database table and column names can be immediately defined. These database constants, the constants for attributes and component types and the enumeration values are required to implement the O/R mapping using the mediators of the package de.tmasoft.acaf.jdbc. Then the database access facade, used to retrieve and store business objects, can be completely implemented. Note, that it is not necessary that any business classes are implemented to start implementing the O/R mapping or the database access facade!

Define component type constants

Define attribute keyword constants

Define localized component types (optional)

Define localized attribute keywords (optional)

Define localized enumeration values (optional)

Implement enumeration values

Implement business classes

Implement XML serialization facade

Implement business processes

Define database tables

Define database table constants

Implement O/R mapping

Implement persistence access facade

How to Use The Framework

This section illustrates how top implement an application step by step. For this purpose two sample applications are used.

The first sample applications implements an address book, which contains contact persons. To each person an arbitrary number of phone numbers and addresses can be added.

The second sample application implements a competition management, which can be used to run a competition where teams play against each other like a basketball, soccer or football competition. The teams can be registered with their players for the competition. The competition may be structured with rounds, groups and pairings. Team-classifications represent a team playing in a group or pairing. Teams are usually distributed to the groups of the first round by a draw. So, the team-classifications in the groups of the first round usually reference a registered team. In the following rounds teams, which reached a certain place in the preceding round play against each other. So the team-classifications reference a team-classification at this position in a group or pairing of the preceding round, e.g. "First of group A" against "Second of group B".

The complete source code of the sample applications used here can be downloaded from the following locations:

The following implementation steps may start, when the business class model has reached a stable state.

Define Component Type Constants

One of the first steps is to define string constants for the component types. Component types are used by the ComponentFactory to create business objects, as XML tags by XML serialization and by O/R mapping to map the business objects to the appropriate database tables. The naming conventions for component types may is up to you. When the application is localized, the component type strings are usually not shown to the user of the application and so you may use a technical notation like a class name. Otherwise you should use a more user friendly notation. Component names are also used as XML node names by the XML serialization classes of the package de.tmasoft.acaf.xml. When you have a naming conventions for XML tags, this may also influence the notation of the component type strings. However, component types should not contain blanks or special characters like quotes or dots.

The following shows the definition of the component type constants of the address book application. The constants should be defined in a central package and component, because they are usually needed by many other components, which work with the business classes. It is a matter of taste, if the constants are defined in a separate class or in the related business class itself. In the sample applications separate classes are used to define the business constants.

public class Constants
{
    ...
    public static class ComponentTypes
    {
        public static final String ADDRESS       = "Address";
        public static final String ADDRESS_BOOK  = "Address-book";
        public static final String PERSON        = "Person";
        public static final String PHONE_NUMBER  = "Phone-Number";
    }
}

Define Attribute Keyword Constants

Another initial step is to define string constants for attribute keywords. Attribute keywords are used by the XML serialization as attribute names inside the XML tags and by O/R mapping to map an attribute value to one or more database columns. They may also be used to send visitors to a certain attribute of a business class. When the application is not localized, attributes are inserted into the error messages of the exceptions thrown by the attribute classes. In this case attribute keywords may be visible to a user and should have a user friendly notation. When attribute keywords are localized anyway, a technical notation may be used. When you intend to store the business objects in a relational database, the column names may be used as attribute keywords. This makes the implementation of O/R mapping a bit easier, because there is no need to initialize the attribute mediators of the de.tmasoft.acaf.jdbc package with separate column names.

The following shows the definition of the attribute keyword constants of the address book application. The constants should be defined in a central package and component, because they are usually needed by many other components, which work with the business classes. It is a matter of taste, if the constants are defined in a separate class or in the related business class itself. In the sample applications separate classes are used to define the business constants. So the business classes are not polluted with the constant definitions.

public class Constants
{
    public static class Attributes
    {
        public static class Address
        {
            public static final String CITY        = "City";
            public static final String COUNTRY     = "Country";
            public static final String DESCRIPTION = "Description";
            public static final String STREET      = "Street";
            public static final String ZIP_CODE    = "Zip-code";
        }
        
        public static class AddressBook
        {
            public static final String CONTACTS    = "Contacts";
        }
        
        public static class Person
        {
            ...
        }
        
        public static class PhoneNumber
        {
            ...
        }
    }
}

Define Localized Component Types

When your application should be localized, or you want that user friendly strings for the component types are used by the framework (e.g. in error messages), you should define localized component types. In the sample applications message files are used to define the localized strings. These files are compiled by the Message Compiler to create the resource property files and enumeration constants for the keys to access the localized texts.

The following shows the definition of the component type for an address in English and German.

.MESSAGEKEY=CNT_TXT_COMPTYPE_ADDRESS
.SEVERITY=INFORMATION
.LANGUAGE=en
.TEXT
Address
.END
.LANGUAGE=de
.TEXT
Adresse
.END

Define Localized Attribute Keywords

In localized applications you should also define locale specific strings for the attribute keywords. Localized attribute keywords are returned by the attribute classes as display keyword and inserted into the error messages of the exceptions thrown by the attribute classes and so be visible to a user. Localized attribute keywords may also be used as label texts on a GUI.

In the sample applications message files are used to define the localized strings. These files are compiled by the Message Compiler to create the resource property files and enumeration constants for the keys to access the localized texts. The following shows the definition of the first name for a person in English and German.

.MESSAGEKEY=CNT_TXT_ATTR_PERSON_FIRST_NAME
.SEVERITY=INFORMATION
.LANGUAGE=en
.TEXT
First name
.END
.LANGUAGE=de
.TEXT
Vorname
.END

Define Localized Enumeration Values

In localized applications the values of enumeration values should be localized as well. The localized string of an enumeration value is returned by the methods EnumValue.toString() and EnumValue.toString(Locale) of an enumeration value and by the method EnumAttribute.getValueAsString() of an enumeration attribute. Localized enumeration values may also be used as label texts on a GUI.

In the sample applications message files are used to define the localized strings. These files are compiled by the Message Compiler to create the resource property files and enumeration constants for the keys to access the localized texts. The following shows the definition of the enumeration value mister for the enumeration salutation in English and German.

.MESSAGEKEY=CNT_TXT_ENUM_SALUTATION_MISTER
.SEVERITY=INFORMATION
.LANGUAGE=en
.TEXT
Mister
.END
.LANGUAGE=de
.TEXT
Herr
.END

Implement the Application Specific Message Catalog

To access the localized text strings you need to implement a message catalog, which is used to initialize the component base class and the attributes. The message catalog must implement the interface MessageCatalogInterface and may extend the base class MessageCatalogBase. The following shows the implementation of the message catalog singleton of the address book application. Note, that the enumeration ContactsTextTable, which defines the keys to access the localized texts, is created by the Message Compiler from the message source file.

public class ContactsTextCatalog extends MessageCatalogBase
{
    private static final String MESSAGE_FILE_NAME = "de.tmasoft.acaf.sample.contacts.msgcat.ContactsTextTable";
    
    public static final ContactsTextCatalog theInstance = new ContactsTextCatalog ();
    
    private ContactsTextCatalog () 
    {
        super (MESSAGE_FILE_NAME);     
    }
    
    public synchronized String getAndFormatMessage (ContactsTextTable messageKey, 
                                                    Object[]          parameters) 
    {
        return super.getAndFormatMessage (messageKey, parameters);
    }
    
    public synchronized String getMessage (ContactsTextTable messageKey) 
    {
        return super.getMessage (messageKey);
    }
}

Implement Enumeration Values

After the localized enumeration values has been defined the enumeration values can be implemented, which are a prerequisite to initialize the enumeration attributes of the business classes. Enumeration values participating at the framework must at least implement the interface EnumValue and may extend the base class EnumValueBase. Enumeration values can also be implemented as Java enums. The following shows the implementation of the enumeration Salutation used for the salutation attribute of a person in the address book application. Note, that the toString() methods of this enumeration class return the localized text values from the message catalog. If you don't intend to localize your application, you may also initialize the enumeration values with text constants returned by these methods.

public enum Salutation implements EnumValue<Salutation>
{
    MISS (ContactsTextTable.CNT_TXT_ENUM_SALUTATION_MISS),
    MISTER (ContactsTextTable.CNT_TXT_ENUM_SALUTATION_MISTER),
    UNDEFINED (ContactsTextTable.CNT_TXT_ENUM_SALUTATION_UNDEFINED);
    
    private ContactsTextTable valueKey;

    private Salutation (ContactsTextTable valueKey)
    {
        this.valueKey = valueKey;
    }

    public String toString ()
    {
        return ContactsTextCatalog.theInstance.getMessage (valueKey);
    }

    public String toString (Locale locale)
    {
        return ContactsTextCatalog.theInstance.getMessage (valueKey, locale);
    }
}

Implement Business Classes

At this point the initial work is done and we are ready to implement the business classes. All business classes of the sample applications extend the component base class Component and initialize this class with the parent component, the component type, the message catalog and the message key to get the localized component type, also denoted as display type. They further implement the method cloneComponent(), which just returns an instance of the class itself. In the constructor the attributes are created and added to the attribute lists of the base class.

The following code block shows the basic implementation of the business class Person of the address book application. All attributes are initialized with the attribute keyword, the message catalog and the message key to get the localized display keyword. The enumeration attribute salutation is initialized with the value list of the enumeration and the initial and default value UNDEFINED. The date attribute dateOfBirth is initialized with a minimum date of 1900-Jan-01, a maximum date of 9999-Dec-31 and a dynamic date format, which formats the date value with a medium format according to the locale of the current thread. The container attributes addresses and phoneNumbers are initialized with the component types used for runtime type checking and component creation.

public class Person extends Component<Person>
{
    private OrderedComponentContainer<Address> addresses;
    
    private DateAttribute dateOfBirth;
    
    private StringAttribute firstName;
    
    private StringAttribute lastName;
    
    private OrderedComponentContainer<PhoneNumber> phoneNumbers;
    
    private EnumAttribute<Salutation> salutation;

    public Person (String                componentName, 
                   ComponentInterface<?> parentComponent)
    {
        super (componentName, 
               Constants.ComponentTypes.PERSON,
               ContactsTextTable.CNT_TXT_COMPTYPE_PERSON,
               ContactsTextCatalog.theInstance,
               parentComponent);
        ContactsTextCatalog    messageCatalog = ContactsTextCatalog.theInstance;
        DynamicDateFormat      dateFormat     = new DynamicDateFormat (DateFormat.MEDIUM);
        
        salutation = new EnumAttribute<Salutation> (Constants.Attributes.Person.SALUTATION,
                                                    ContactsTextTable.CNT_TXT_ATTR_PERSON_SALUTATION,
                                                    messageCatalog,
                                                    Arrays.asList (Salutation.values ()),
                                                    Salutation.UNDEFINED,
                                                    Salutation.UNDEFINED);
        addAttribute (salutation);
        
        firstName = new StringAttribute (Constants.Attributes.Person.FIRST_NAME,
                                         ContactsTextTable.CNT_TXT_ATTR_PERSON_FIRST_NAME,
                                         messageCatalog);
        addAttribute (firstName);
        
        lastName = new StringAttribute (Constants.Attributes.Person.LAST_NAME,
                                        ContactsTextTable.CNT_TXT_ATTR_PERSON_LAST_NAME,
                                        messageCatalog);
        addAttribute (lastName);
        
        dateOfBirth = new DateAttribute (Constants.Attributes.Person.DATE_OF_BIRTH,
                                         ContactsTextTable.CNT_TXT_ATTR_PERSON_DATE_OF_BIRTH,
                                         messageCatalog,
                                         null,
                                         null,
                                         false,
                                         CalendarDateFunctions.getMinimumDate (1900),
                                         CalendarDateFunctions.getMaximumDate (9999),
                                         dateFormat);
        addAttribute (dateOfBirth);
       
        addresses = new OrderedComponentContainer<Address> (Constants.Attributes.Person.ADDRESSES,
                                                            ContactsTextTable.CNT_TXT_ATTR_PERSON_ADDRESSES,
                                                            messageCatalog,
                                                            Constants.ComponentTypes.ADDRESS);
        addAttribute (addresses);
        
        phoneNumbers = new OrderedComponentContainer<PhoneNumber> (Constants.Attributes.Person.PHONE_NUMBERS,
                                                                   ContactsTextTable.CNT_TXT_ATTR_PERSON_PHONE_NUMBERS,
                                                                   messageCatalog,
                                                                   Constants.ComponentTypes.PHONE_NUMBER);
        addAttribute (phoneNumbers);
    }

    public Person cloneComponent (String                componentName, 
                                  String                componentType,
                                  ComponentInterface<?> parentComponent)
    {
        return new Person (componentName, parentComponent);
    }
}

Register Prototypes at the Component Factory

After the business classes has been implemented, a prototype for each component type has to be registered at the ComponentFactory. This has to be be done once in the initialization phase of the application. The factory is used to create the business objects by the O/R mapping package de.tmasoft.acaf.jdbc, the XML serialization package de.tmasoft.acaf.xml and the command line parser package de.tmasoft.acaf.parser. The following code block shows the prototype registration method of the address book application.

public static void registerPrototypes ()
{
    ComponentFactory factory = ComponentFactory.theInstance;
    factory.registerPrototype (new Address ("Prototype", null));
    factory.registerPrototype (new Person ("Prototype", null));
    factory.registerPrototype (new PhoneNumber ("Prototype", null));
}

Test Creation and Initialization of Business Classes

Now you may create business objects and set their attribute values using the command line parser visitors of the package de.tmasoft.acaf.parser. You may implement this as JUnit tests to test your business processes, to test O/R mapping or to test XML serialization. The following code block shows how this may be implemented. First a ParseAndSetVisitor, a PrintVisitor and an empty AddressBook are created. The setCommand visitor is initialized with a command line, which creates one person with an address and a phone number. Note, that the locale of the current thread is set to ENGLISH before the visitor is sent to the address book. This is required, because the command contains a date value in the English locale format and the English enumeration value Mister for the salutation. After the setCommand visitor has been sent to the address book, it contains one contact person. This may be verified by sending the printer visitor to the address book and printing the result on the console.

String             command;
ParseAndSetVisitor setCommand  = new ParseAndSetVisitor ();
PrintVisitor       printer     = new PrintVisitor (true, 20);
AddressBook        addressBook = new AddressBook ("Address Book");
//
// Create the command with English locale
//
ThreadLocale.theInstance.set (Locale.ENGLISH);
command = "Contacts P1 Salutation Mister First-name John Last-name Smith Date-of-birth \"Aug 23, 1977\"" +
          " Addresses A1 Description Home Street \"Fleetstreet 32\" Zip-Code 88765 City Middletown Country \"United Kingdom\";" +
          " Phone-numbers PN1 Description Private Number \"0887 / 8473522897\"";
setCommand.setCommand (command);
//
// Send the set-command visitor to the address book
//
addressBook.acceptVisitor (setCommand);
//
// Print the content of the address book on the console
//
addressBook.acceptVisitor (printer);
System.out.println (printer.getResult ());

Implement XML Serialization of Business Classes

As soon as the business classes are implemented, they may be serialized/ deserialized to/from a XML document or stored and updated to/from a XML file. The class XMLHandler may be used for this purpose. The following code block shows a simple XML facade implemented as singleton, which can be used to store and update any component in/from a XML file.

public final class XmlFacade
{
    public static final XmlFacade theInstance = new XmlFacade ();
    
    private XMLHandler xmlHandler;
    
    private XmlFacade ()
    {
        xmlHandler = new XMLHandler ();
    }
    
    public void saveToFile (File               directory,
                            String             fileName,
                            ComponentInterface component) throws AcafException
    {
        if (!directory.exists ())
        {
            directory.mkdirs ();
        }
        xmlHandler.saveToFile (new File (directory, fileName), component);

    }
    
    public void updateFromFile (File               xmlFile,
                                ComponentInterface component) throws AcafException
    {
        xmlHandler.updateFromFile (xmlFile, component);
    }
}

This XML facade stores the whole business object tree under the passed component in the XML file. The created XML file contains the string representation of the attribute values returned by the method AttributeInterface.getValueAsString(). This means, that the XML value of the date attribute dateOfBirth of the business class Person and the XML value of the enumeration attribute salutation depends on the locale of the thread, which calls the method saveToFile(). This is probably not desired. To solve this problem, so called attribute value converters of the package de.tmasoft.acaf.io has to be registered at the XML handler. To be able to do this, the XML handler has to be initialized with a ComponentAttributeValueConverterMap:

public final class XmlFacade
{
    ...    
    private ComponentAttributeValueConverterMap componentAttributeConverterMap;

    private XmlFacade ()
    {
        componentAttributeConverterMap = new ComponentAttributeValueConverterMap ();
        xmlHandler                     = new XMLHandler (componentAttributeConverterMap);
    }
}

The attribute value converters can now be registered at the component attribute converter map. Date values in XML documents should usually conform to the standard XML date format <xsd:date>. So we register a date attribute value converter for the attribute dateOfBirth of the business class Person. For the salutation attribute we register an enumeration value converter. For this purpose string constants should be defined for the XML representation of the enumeration values.

You may have noticed, that the XML facade does not reference any business class. So it may be completely implemented after the attribute keyword and component type constants has been defined and enumeration values has been implemented.

public final class XmlConstants
{
    ...
    public static class Values
    {
        public static class Salutation
        {
            public static final String MISS      = "MSS";
            public static final String MISTER    = "MST";
            public static final String UNDEFINED = "UDF";
        }
    }
}

public final class XmlFacade
{   
    ...
    private DateAttributeValueConverter xmlDateValueConverter = new DateAttributeValueConverter ("yyyy-MM-dd");
    ...
    private void registerAttributeValueConvertersPerson ()
    {
        AttributeValueConverterMap              attributeValueConverterMap;
        List<EnumValuePair<Salutation>>         salutationValuePairs;
        EnumAttributeValueConverter<Salutation> salutationAttributeConverter;
        //
        // Create and register the attribute value converter map for the person
        //
        attributeValueConverterMap = new AttributeValueConverterMap ();
        componentAttributeConverterMap.registerAttributeValueConverter (Constants.ComponentTypes.PERSON,
                                                                        attributeValueConverterMap);
        //
        // Register the date value converter for the attribute dateOfBirth
        //
        attributeValueConverterMap.registerAttributeValueConverter (Constants.Attributes.Person.DATE_OF_BIRTH, 
                                                                    xmlDateValueConverter);
        //
        // Register the enumeration value converter for the attribute salutation
        //
        salutationValuePairs = Arrays.asList (new EnumValuePair<Salutation>(Salutation.MISS, 
                                                                            XmlConstants.Values.Salutation.MISS),
                                              new EnumValuePair<Salutation>(Salutation.MISTER, 
                                                                            XmlConstants.Values.Salutation.MISTER),
                                              new EnumValuePair<Salutation>(Salutation.UNDEFINED,
                                                                            XmlConstants.Values.Salutation.UNDEFINED));
        salutationAttributeConverter = new EnumAttributeValueConverter<Salutation> (salutationValuePairs);
        attributeValueConverterMap.registerAttributeValueConverter (Constants.Attributes.Person.SALUTATION, 
                                                                    salutationAttributeConverter);
     }
}

Implementing a Business Visitor

This section illustrates how the visitor design pattern can be used to implement a business class specific visitor. Business visitors can be used for business processes, which operate on the the whole business object tree or a part of it. The may be used to transform the business object tree, to validate the business objects or to extract data for external systems.

Here we implement a visitor, which searches the address book for persons with a specified part of the first name, last name, city or street. First we implement the base class BaseComponent for all business classes of the address book application and the base class BusinessVisitor as described in the section Visitors above. Then all business classes are derived from this base class and implement the method acceptBusinessVisitor(). In the Person class we further implement two business methods, which return the first and last name:

public class Person extends BaseComponent<Person>
{
    ...
    public void acceptBusinessVisitor (BusinessVisitor visitor) throws AcafException
    {
        visitor.visitPerson (this);
    }
        
    public String getFirstName ()
    {
        return firstName.getValue ();
    }
    
    public String getLastName ()
    {
        return lastName.getValue ();
    }
    ...
}

Now we implement a concrete private visitor in the class AddressBook, which searches for all persons, which contain a search string in their first or last name or in the city or street of an address. The persons are collected in a hash set and returned by the method getSearchResult():

public final class AddressBook extends BaseComponent<AddressBook>
{
    ...
    private class SearchVisitor extends BusinessVisitor
    {
        private String searchString;
        
        private HashSet<Person> searchResult = new HashSet<Person> ();
            
        private SearchVisitor (String searchString)
        {
            this.searchString = searchString;
        }
        
        private Collection<Person> getSearchResult ()
        {
            return searchResult;
        }
        
        public void visitAddress (Address address)  throws AcafException
        {
            if (address.getCity ().contains (searchString) ||
                address.getStreet ().contains (searchString))
            {
                searchResult.add ((Person) address.getParent ());
            }
        }
           
        public void visitAddressBook (AddressBook addressBook)  throws AcafException
        {
            addressBook.visitAttribute (Constants.Attributes.AddressBook.CONTACTS, this);
        }
        
        public void visitPerson (Person person)  throws AcafException
        {
            if (person.getFirstName ().contains (searchString) ||
                person.getLastName ().contains (searchString))
            {
                searchResult.add (person);
            }
            else
            {
                person.visitAttribute (Constants.Attributes.Person.ADDRESSES, this);
            }
        }
    }
}

This visitor may now be used by the method findContacts() in the business class AddressBook:

public final class AddressBook extends BaseComponent<AddressBook>
{
    ...
    public Collection<Person> findContacts (String searchString) throws AcafException
    {
        SearchVisitor visitor = new SearchVisitor (searchString);
        visitor.visitAddressBook (this);
        return visitor.getSearchResult ();
    }
}

Database Persistence and O/R Mapping

The package de.tmasoft.acaf.jdbc contains classes, which can be used to store and retrieve business objects in/from a relational database. This is also denoted as object-relational mapping. The classes use simple JDBC statements and result sets for this purpose, which are created by a JDBC driver and accessed by the interfaces defined in the package java.sql. The JDBC package of ACAF provides two main different ways to store business objects in the database:

  1. Storing with updatable result sets
  2. Storing with SQL insert, update and delete statements

The first method, storing with updatable result sets, is the easier one and more elegant. This method requires only SQL select statements, which are used to create the result sets. When the business objects should be stored, the related rows in the result sets are updated, when new business objects should be inserted into the database, new rows are inserted into the result sets and when business objects should be deleted, the related rows are removed from the result sets. The method requires, that the JDBC driver supports updatable result sets, which is quite common today. This is also the preferred method to be used with ACAF because it is very transparent for you. When you use this method, you should also define foreign key constraints with the option ON DELETE CASCADE between the tables containing the child and parent business objects. Otherwise orphaned child business objects remain in the database. When your database doesn't support cascaded deletes or you cannot define such constraints you need to implement and call an explicit method to delete a business object with all children from the database.

The second method, storing with insert, update and delete statements may be used, when your JDBC driver doesn't support updatable result set, or you think this is more performant. You don't have to implement these statements yourself, they are generated by the classes of the JDBC package, as we will see in the following.

A Simple Database Facade

To illustrate, how O/R mapping works with ACAF, we start with the definition of a simple database facade, which provides methods to store and update a single business object, the direct child components of a business object, the child components with all subjacent child components and the full business object tree. The database facade is implemented as singleton and may be initialized with a Connection, a DataSource or the JDBC driver class name, URL, user name and password to create a connection via a JDBC driver.

public final class SimpleDatabaseFacade
{
    public static final SimpleDatabaseFacade theInstance = new SimpleDatabaseFacade ();
    
    private JdbcConnectionAccessInterface connectionAccess;

    private SimpleDatabaseFacade ()
    {}
    
    public void initDatabaseFacade (Connection connection) throws JdbcException 
    {
        connectionAccess = new JdbcSimpleConnectionAccess (connection);
    }
    
    public synchronized void initDatabaseFacade (DataSource dataSource) throws JdbcException
    {
        connectionAccess = new JdbcThreadManagedDataSourceConnectionAccess (dataSource);
    }
    
    public void initDatabaseFacade (String jdbcDriverClassName, 
                                    String databaseUrl, 
                                    String username, 
                                    String password) throws JdbcException 
    {
        try
        {
            Class.forName (jdbcDriverClassName).newInstance (); 
        }
        catch (Exception exc)
        {
            throw new JdbcGeneralError (exc);
        }
        try
        {
            Connection connection = java.sql.DriverManager.getConnection (databaseUrl, username, password);
            initDatabaseFacade (connection);
        }
        catch (SQLException exc)
        {
            throw new JdbcGeneralError (exc);
        }     
    }
    
    public void storeChildComponents (ComponentInterface<?> component) throws AcafException,
                                                                              JdbcException
    {
        ...
    }
    
    public void storeChildComponentsFull (ComponentInterface<?> component) throws AcafException,
                                                                                  JdbcException
    {
        ...
    }
    
    public void storeComponentAttributes (ComponentInterface<?> component) throws AcafException,
                                                                                  JdbcException
    {
        ...
    }
    
    public void storeComponentFull (ComponentInterface<?> component) throws AcafException,
                                                                            JdbcException
    {
        ...
    }

    public void updateChildComponents (ComponentInterface<?> component) throws AcafException
    {
        ...
    }
    
    public void updateChildComponentsFull (ComponentInterface<?> component) throws AcafException
    {
        ...
    }
    
    public void updateComponentAttributes (ComponentInterface<?> component) throws AcafException
    {
        ...
    }
    
    public void updateComponentFull (ComponentInterface<?> component) throws AcafException
    {
        ...
    }
}

This facade provides already a very flexible interface to store and retrieve business object sub-trees. You may notice, that it is very generic and doesn't reference any concrete business classes, so you may provide this facade in a very early project implementation phase! However, to implement the store and update methods some initial work is required.

Define Database Constants

The first step is to define constants for your database table and column names and for the database representation of enumeration values:

class DatabaseConstants
{   
    private DatabaseConstants()
    {}
    
    public static class Columns
    {
        public static class Persons
        {
            public static final String PERSON_ID        = "PERSON_ID";
            public static final String DATE_OF_BIRTH    = "DATE_OF_BIRTH";
            public static final String FIRST_NAME       = "FIRST_NAME";
            public static final String LAST_NAME        = "LAST_NAME";
            public static final String SALUTATION       = "SALUTATION";
        }
        ...
    }
    
    public static class Tables
    {
        public static final String ADDRESSES      = "Addresses";
        public static final String PERSONS        = "Persons";
        public static final String PHONE_NUMBERS  = "PhoneNumbers";
        ...
    }
    
    public static class Values
    {
        public static class Salutation
        {
            public static final String MISS      = "MSS";
            public static final String MISTER    = "MST";
            public static final String UNDEFINED = "UDF";
        }
        ...
    }
}

Define SQL Select Statements

The next step is to define the SQL select statements, which are used to create the JDBC result sets. The class SqlSelectStatements in the following sample code block is initialized with a delimiter character, which is used to enclose the table and column names in the SQL select statements. The delimiter character can be retrieved from the database meta-data calling the method DatabaseMetaData.getIdentifierQuoteString(). The class implements two select statements to select persons of the address book application. The method selectPerson() creates a select statement, which selects exactly one person with the primary key. This statement is used by the store and update methods when exactly one person component should be stored or updated. The method selectPersonsOfAddressBook() selects all persons of the address book. Note, that the sample address book application supports only one address book, so there is no foreign key for the address book in the PERSONS table. This select statement is used, when all persons of the address book should be stored or updated. According statements has to be implemented for all database tables.

When a business object is stored in more than one table, don't use SQL joins in the select statements! This may produce non-updatable result sets. Instead define a separate select statement for each table. A general recommendation is, to keep the select statements as simple as possible. This restriction does of course not apply, when you do not intend to store your business objects in the database via the result sets.

class SqlSelectStatements
{
    private String dl;

    SqlSelectStatements (String sqlDelimiter)
    {
        this.dl = sqlDelimiter;
    }
    
    private String getColumnsPersonAll ()
    {
        return dl + 
               DatabaseConstants.Columns.Persons.PERSON_ID      + dl + "," + dl +
               DatabaseConstants.Columns.Persons.DATE_OF_BIRTH  + dl + "," + dl +
               DatabaseConstants.Columns.Persons.FIRST_NAME     + dl + "," + dl + 
               DatabaseConstants.Columns.Persons.LAST_NAME      + dl + "," + dl + 
               DatabaseConstants.Columns.Persons.SALUTATION     + dl;
    }
    
    String selectPerson ()
    {
        String columns = getColumnsPersonAll ();
        return "SELECT " + columns + " FROM " + dl + DatabaseConstants.Tables.PERSONS + dl + 
               " WHERE " + dl + DatabaseConstants.Columns.Persons.PERSON_ID           + dl + "=?";
    }
    
    String selectPersonsOfAddressBook ()
    {
        String columns = getColumnsPersonAll ();
        return "SELECT " + columns + " FROM " + dl + DatabaseConstants.Tables.PERSONS + dl;
    }
    ...
}

Create Attribute Mediators

The JDBC attribute mediators are used to exchange the attribute values between an attribute of a business class and a result set created by a SQL select statement. The class AttributeMediators, which is shown in the following code block implements methods, which create attribute mediator list for the business classes, in this case for the business class Person. These mediator lists are used later by other mediator classes to exchange the attribute values between a business object and a result set row. The lists returned by this class contain a mediator for each attribute of the business class. Note, that it doesn't matter if the mediator list is combined later with a result set, which contains not all columns for the attributes mediators. The related attributes are just not updated from or stored in the database in this case, but no error occurs.

Simple attributes are usually mapped to one database column. However, there are also attributes, which may be mapped to more than one column like attributes implementing the interfaces DurationAttributeInterface, PrecisionDateAttributeInterface or SetAttributeInterface. In the following example, the JdbcDateAttributeMediator is used to map the date attribute dateOfBirth to a SQL date column and for the other attributes the JdbcSimpleAttributeMediator is used to map the values to a SQL string column. Note, that the mediator for the salutation attribute is initialized with an attribute value converter, to convert the enumeration values to the string representation used as database value.

class AttributeMediators
{
    private List<? extends JdbcAttributeMediatorInterface> mediatorsPerson = null;
    ...
    
    List<? extends JdbcAttributeMediatorInterface> getMediatorsPerson ()
    {
        List<EnumValuePair<Salutation>>         salutationValuePairs;
        EnumAttributeValueConverter<Salutation> salutationAttributeConverter;
        if (mediatorsPerson == null)
        {
            //
            // Create an enumeration attribute value converter for the
            // salutation
            //
            salutationValuePairs = Arrays.asList (new EnumValuePair<Salutation>(Salutation.MISS,
                                                                                      DatabaseConstants.Values.Salutation.MISS),
                                                  new EnumValuePair<Salutation>(Salutation.MISTER,
                                                                                      DatabaseConstants.Values.Salutation.MISTER),
                                                  new EnumValuePair<Salutation>(Salutation.UNDEFINED,
                                                                                      DatabaseConstants.Values.Salutation.UNDEFINED));
            salutationAttributeConverter = new EnumAttributeValueConverter<Salutation> (salutationValuePairs);
            //
            // Create the attribute mediator list
            //
            mediatorsPerson = Arrays.asList (new JdbcDateAttributeMediator (Constants.Attributes.Person.DATE_OF_BIRTH,
                                                                            DatabaseConstants.Columns.Persons.DATE_OF_BIRTH), 
                                             new JdbcSimpleAttributeMediator (Constants.Attributes.Person.FIRST_NAME,
                                                                              DatabaseConstants.Columns.Persons.FIRST_NAME), 
                                             new JdbcSimpleAttributeMediator (Constants.Attributes.Person.LAST_NAME,
                                                                              DatabaseConstants.Columns.Persons.LAST_NAME), 
                                             new JdbcSimpleAttributeMediator (Constants.Attributes.Person.SALUTATION,
                                                                              salutationAttributeConverter,
                                                                              DatabaseConstants.Columns.Persons.SALUTATION));
        }
        return mediatorsPerson;
    }
    ...
}

Create Component Mediators

The JDBC component mediators are used to exchange the component name, the component type and the index of the component in a sequence container between the business class and a result set created by a SQL select statement. They are further used to fill the parameters of the WHERE clause in the SQL select statements. So the order of the column names specified for the component name must correspond to the order of the parameters specified in the select statement! The component type needs only be stored in the database, when the database column contains business objects with different component types. The component name of a component and optionally for its parent component(s) is usually mapped to the key columns of the database table.

The class ComponentMediators, which is shown in the following code block implements methods, which create a component mediator for the business classes of the address book application. The component name of a Person is mapped to the primary key column PERSON_ID. The component names of the business classes Address and PhoneNumber are mapped to the primary key columns ADDRESS_ID and PHONE_NUMBER_ID, and the component name of their parent component, which is a Person are mapped to the foreign key column PERSON_ID. For these business classes it is not required to store the component type or the index.

class ComponentMediators
{ 
    private JdbcComponentMediatorInterface mediatorAddress = null;
    
    private JdbcComponentMediatorInterface mediatorPerson = null;
    
    private JdbcComponentMediatorInterface mediatorPhoneNumber = null;
     
    JdbcComponentMediatorInterface getMediatorAddress ()
    {
        if (mediatorAddress == null)
        {
            mediatorAddress = new JdbcSimpleComponentMediator (new String[] { DatabaseConstants.Columns.Addresses.ADDRESS_ID, 
                                                                              DatabaseConstants.Columns.Addresses.PERSON_ID });
        }
        return mediatorAddress;
    }
     
    JdbcComponentMediatorInterface getMediatorPerson ()
    {
        if (mediatorPerson == null)
        {
            mediatorPerson = new JdbcSimpleComponentMediator (new String[] { DatabaseConstants.Columns.Persons.PERSON_ID });
        }
        return mediatorPerson;
    }
     
    JdbcComponentMediatorInterface getMediatorPhoneNumber ()
    {
        if (mediatorPhoneNumber == null)
        {
            mediatorPhoneNumber = new JdbcSimpleComponentMediator (new String[] { DatabaseConstants.Columns.PhoneNumbers.PHONE_NUMBER_ID, 
                                                                                  DatabaseConstants.Columns.PhoneNumbers.PERSON_ID });
        }
        return mediatorPhoneNumber;
    }
}

Create Component Attribute Mediators

Component attribute mediators are used to exchange simple attribute values of a component with a JDBC result set. The result set is expected to contain one row, which represents the component and contains the values for the attributes. The class JdbcResultSetComponentAttributesMediator is used for this purpose. This class is initialized with the database connection used to execute the SQL select statement, which creates the result set, the component mediator for the component, the SQL select statement and the attribute mediator list used to exchange the attribute values. The component mediator is used, to fill the parameters in the WHERE clause of the SQL select statement and to update the result set fields with the component name and component type.

Usually you will create one component attribute mediator for each business class with a select statement, which selects the columns for all attributes of the business class. However, when a specific business process doesn't require all attributes of a business object, you may decide to implement a statement, which selects only the columns for the required attributes (e.g. for performance reasons). In this case you have to instantiate an additional mediator with the alternative select statement. Note, that the other initialization parameters remain the same, also the attribute mediator list. The attribute mediators, for which no columns are present in the result set are just ignored.

The following code block shows the instantiation of a component attribute mediator for the business class Person of the address book application. The classes implemented so far are used to initialize the mediator.

class ComponentAttributeMediators
{
    private JdbcConnectionAccessInterface databaseConnectionAccess;
    private SqlSelectStatements selectStatements;
    private ComponentMediators componentMediators;
    private AttributeMediators attributeMediators;
    private JdbcResultSetComponentAttributesMediator mediatorAddress = null;
    private JdbcResultSetComponentAttributesMediator mediatorPerson = null;
    private JdbcResultSetComponentAttributesMediator mediatorPhoneNumber = null;
    
    ComponentAttributeMediators (JdbcConnectionAccessInterface databaseConnectionAccess,
                                 ComponentMediators            componentMediators,
                                 AttributeMediators            attributeMediators,
                                 SqlSelectStatements           selectStatements)
    {
        this.databaseConnectionAccess = databaseConnectionAccess;
        this.attributeMediators       = attributeMediators;
        this.componentMediators       = componentMediators;
        this.selectStatements         = selectStatements;
    }
    
    JdbcResultSetComponentAttributesMediator getMediatorPerson ()
    {
        if (mediatorPerson == null)
        {
            mediatorPerson = new JdbcResultSetComponentAttributesMediator (databaseConnectionAccess, 
                                                                           componentMediators.getMediatorPerson (),
                                                                           selectStatements.selectPerson (),
                                                                           attributeMediators.getMediatorsPerson ());
        }
        return mediatorPerson;
    }
    ...
}

Create Component Container Mediators

The JDBC component container mediators are used to exchange the data of the business classes in containers with the rows of JDBC result sets. Each row in a result set represents one component in the container and the relationship is determined by the component name. The class JdbcResultSetComponentContainerMediator is used fo this purpose. When a component container should be stored in the database, this class updates the result set rows with the attribute values of the components in the container, when new components has been inserted into the container, id adds rows in the result set and when components has been removed from the container it deletes the corresponding rows in the result set. When the container should be updated from the database, it just works the other way round; attributes of existing components are updated with the data from the result set, non existing components are created in the container, and components, which are not represented by a row in the result set are removed from the container.

For this purpose, the mediator is initialized with a database connection, used to execute the SQL statement, which creates the result set, the parent component mediator, used to fill the parameters in the WHERE clause of the select statement, the select statement, the component mediator for the components in the container and a list of attribute mediators, used to exchange the values between the attributes of the components in the container and the result set. See also the class description for further information how this mediator works in detail.

One instance of this mediator is required for a container and certain SQL select statement. Usually you will implement a select statement, which selects the columns, which are required to update and store all attributes of the business objects in the container. However, when a specific business process doesn't require all attributes of the business objects, you may decide to implement a statement, which selects only the columns for the required attributes (e.g. for performance reasons). In this case you have to instantiate an additional mediator with the alternative select statement. Note, that the other initialization parameters remain the same, also the attribute mediator list. The attribute mediators, for which no columns are present in the result set are just ignored.

The following code block shows how the component container mediator for the addresses container of the business class Person is initialized using the classes we implemented so far.

class ComponentContainerMediators
{
    private JdbcConnectionAccessInterface databaseConnectionAccess;
    
    private AttributeMediators attributeMediators;
    
    private ComponentMediators componentMediators;
    
    private SqlSelectStatements selectStatements;
    
    private JdbcResultSetComponentContainerMediator mediatorAddresses = null;
    ...
    
    public ComponentContainerMediators (JdbcConnectionAccessInterface databaseConnectionAccess,
                                        ComponentMediators            componentMediators,
                                        AttributeMediators            attributeMediators,
                                        SqlSelectStatements           selectStatements)
    {
        this.databaseConnectionAccess   = databaseConnectionAccess;
        this.attributeMediators         = attributeMediators;
        this.componentMediators         = componentMediators;
        this.selectStatements           = selectStatements;
    }
    
    JdbcResultSetComponentContainerMediator getMediatorAddresses ()
    {
        if (mediatorAddresses == null)
        {
            mediatorAddresses = new JdbcResultSetComponentContainerMediator (databaseConnectionAccess, 
                                                                             componentMediators.getMediatorPerson (), 
                                                                             Constants.Attributes.Person.ADDRESSES, 
                                                                             true, 
                                                                             selectStatements.selectAddressesOfPerson (), 
                                                                             componentMediators.getMediatorAddress (), 
                                                                             attributeMediators.getMediatorsAddress ()); 
        }
        return mediatorAddresses;
    }
    ...
}

Transaction Handling

Before we can put it all together, we need some methods for transaction handling. As you may have seen in the beginning of this chapter, a connection access object has been created by the initialization methods. When the facade is initialized with a DataSource, the class JdbcThreadManagedDataSourceConnectionAccess is used, otherwise JdbcSimpleConnectionAccess. The first class manages the database connection (and so the transactions) on a per thread basis. This means each thread has its own connection and so the persistence facade can be used in a multi-threaded environment like an application server. The connection is retrieved from the data source. The simple connection access just wraps the connection object and can only be used in single user applications.

The following code block shows the quite straight forward implementation of the transaction methods. Note, that the method closeTransaction() releases the connection, which means, that connections retrieved from a data source are put back into the connection pool. Releasing a simple database connection has no effect.

private void startTransaction () throws JdbcException
{
    Connection connection = connectionAccess.getConnection ();
    try
    {
        databaseConnection.setAutoCommit (false);
        databaseConnection.setTransactionIsolation (Connection.TRANSACTION_READ_COMMITTED);
    }
    catch (JdbcException exc)
    {
        closeTransaction ();
        throw exc;
    }
}

private void commitTransaction () throws JdbcException
{
    Connection connection;
    try
    {
        connection = connectionAccess.getConnection ();
        connection.commit ();
    }
    catch (SQLException sqlException)
    {
        throw new JdbcGeneralError (sqlException);
    }
}

private void rollbackTransaction () throws JdbcException
{
    Connection connection;
    try
    {
        connection = connectionAccess.getConnection ();
        connection.rollback ();
    }
    catch (SQLException sqlException)
    {
        throw new JdbcGeneralError (sqlException);
    }                        
}

private void closeTransaction () throws JdbcException
{
    if (connectionAccess != null)
    {
        connectionAccess.releaseConnection ();
    }
}

Putting it all Together

Now we can use all these mediator factory classes to initialize the database facade. In an initialization method all factories are created. The delimiter for table and column names is retrieved from the meta-data of the database connection and used to initialize the SQL statement factory.

In the last two lines two instances of the class IOFacade are created. The IO facade provides an interface, which can be used to store, insert, update and delete components in/from an arbitrary datastore. This may be a file, a database or anything else. The IO facade must be initialized with processors, which do the actual work. The facade ioFacadeComponentAttributes is used to store and update only the simple attributes of a business object (not the containers). The facade ioFacadeChildComponents is used to store and update only the container attributes of a business object. This provides the condition to implement the store and update methods of the persistence facade as we will see later.

public final class SimpleDatabaseFacade
{
    private String sqlDelimiter = "";
    private SqlSelectStatements selectStatements;
    private ComponentMediators componentMediators;
    private AttributeMediators attributeMediators;
    private ComponentAttributeMediators componentAttributeMediators;
    private ComponentContainerMediators componentContainerMediators;
    private IOFacade ioFacadeComponentAttributes;
    private IOFacade ioFacadeChildComponents;
    
    ...
    private void initDatabaseFacade () throws JdbcException
    {
        Connection connection = connectionAccess.getConnection ();
        try
        {
            componentMediators = new ComponentMediators ();
            attributeMediators = new AttributeMediators ();
            sqlDelimiter       = connection.getMetaData ().getIdentifierQuoteString ();
            selectStatements   = new SqlSelectStatements (sqlDelimiter);
            componentAttributeMediators = new ComponentAttributeMediators (connectionAccess,
                                                                           componentMediators,
                                                                           attributeMediators,
                                                                           selectStatements);
            componentContainerMediators = new ComponentContainerMediators (connectionAccess,
                                                                           componentMediators,
                                                                           attributeMediators,
                                                                           selectStatements);
            ioFacadeComponentAttributes = new IOFacade ();
            ioFacadeChildComponents     = new IOFacade ();
            ...
        }
        catch (SQLException exc)
        {
            throw new JdbcGeneralError (exc);
        }
        finally
        {
            connectionAccess.releaseConnection();
        }
    }
}

Now we still need to initialize the IO facades. The IO facade ioFacadeComponentAttributes, which is used to store and update the simple attributes of a business object, is initialized with the component attributes mediators as store and update processors:

ioFacadeComponentAttributes.registerStoreComponentProcessor (Constants.ComponentTypes.ADDRESS, 
                                                             componentAttributeMediators.getMediatorAddress ());
ioFacadeComponentAttributes.registerStoreComponentProcessor (Constants.ComponentTypes.PERSON, 
                                                             componentAttributeMediators.getMediatorPerson ());
ioFacadeComponentAttributes.registerStoreComponentProcessor (Constants.ComponentTypes.PHONE_NUMBER, 
                                                             componentAttributeMediators.getMediatorPhoneNumber ());
                                                             
ioFacadeComponentAttributes.registerUpdateComponentProcessor (Constants.ComponentTypes.ADDRESS, 
                                                              componentAttributeMediators.getMediatorAddress ());
ioFacadeComponentAttributes.registerUpdateComponentProcessor (Constants.ComponentTypes.PERSON, 
                                                              componentAttributeMediators.getMediatorPerson ());
ioFacadeComponentAttributes.registerUpdateComponentProcessor (Constants.ComponentTypes.PHONE_NUMBER, 
                                                              componentAttributeMediators.getMediatorPhoneNumber ());

The IO facade ioFacadeChildComponents, which is used to store and update the container attributes of a business object, is initialized with the component container mediators as store and update processors:

ioFacadeChildComponents.registerStoreComponentProcessor (Constants.ComponentTypes.ADDRESS_BOOK, 
                                                         componentContainerMediators.getMediatorPersons ());
ioFacadeChildComponents.registerStoreComponentProcessor (Constants.ComponentTypes.PERSON, 
                                                         componentContainerMediators.getMediatorAddresses ());
ioFacadeChildComponents.registerStoreComponentProcessor (Constants.ComponentTypes.PERSON, 
                                                         componentContainerMediators.getMediatorPhoneNumbers ());
                                                         
ioFacadeChildComponents.registerUpdateComponentProcessor (Constants.ComponentTypes.ADDRESS_BOOK, 
                                                          componentContainerMediators.getMediatorPersons ());
ioFacadeChildComponents.registerUpdateComponentProcessor (Constants.ComponentTypes.PERSON, 
                                                          componentContainerMediators.getMediatorAddresses ());
ioFacadeChildComponents.registerUpdateComponentProcessor (Constants.ComponentTypes.PERSON, 
                                                          componentContainerMediators.getMediatorPhoneNumbers ());

The last step is to complete the implementation of the store and update methods. Here you can see how the IO facades are used to store the different business object trees. The first method just stores the simple attributes of the business object using the IO facade ioFacadeComponentAttributes. The second method stores the direct child business objects in the containers of the business object using the IO facade ioFacadeChildComponents. The third method stores all child business objects (grandchildren, ...) calling the method storeComponentTree() of the IO facade ioFacadeChildComponents and the last method stores the whole business object tree combining method one and tree.

Note, that all methods, which store child business objects, also delete the business objects from the database, which has been removed from the container of their parent. When foreign keys with the option ON DELETE CASCADE has been defined between the tables containing the child business objects and the tables containing their parents, all children are deleted automatically from the database.

public void storeComponentAttributes (ComponentInterface<?> component) throws AcafException,
                                                                              JdbcException
{
    try
    {
        startTransaction ();
        ioFacadeComponentAttributes.storeComponent (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

public void storeChildComponents (ComponentInterface<?> component) throws AcafException,
                                                                          JdbcException
{
    try
    {
        startTransaction ();
        ioFacadeChildComponents.storeComponent (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

public void storeChildComponentsFull (ComponentInterface<?> component) throws AcafException,
                                                                              JdbcException
{
    try
    {
        startTransaction ();
        ioFacadeChildComponents.storeComponentTree (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

public void storeComponentFull (ComponentInterface<?> component) throws AcafException,
                                                                        JdbcException
{
    try
    {
        startTransaction ();
        ioFacadeComponentAttributes.storeComponent (component);
        ioFacadeChildComponents.storeComponentTree (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

Here you can see how the IO facades are used to update the different business object trees. The implementation is analog to the store methods, calling the update methods of the IO facades.

public void updateComponentAttributes (ComponentInterface<?> component) throws AcafException
{
    try
    {
        startTransaction ();
        ioFacadeComponentAttributes.updateComponent (component);
        commitTransaction ();
    }
    catch (AcafException exception)
    {
        rollbackTransaction ();
        throw exception;
    }
    finally
    {
        closeTransaction ();
    }
}

public void updateChildComponents (ComponentInterface<?> component) throws AcafException
{
    try
    {
        startTransaction ();
        ioFacadeChildComponents.updateComponent (component);
        commitTransaction ();
    }
    catch (AcafException exception)
    {
        rollbackTransaction ();
        throw exception;
    }
    finally
    {
        closeTransaction ();
    }
}

public void updateChildComponentsFull (ComponentInterface<?> component) throws AcafException
{
    try
    {
        startTransaction ();
        ioFacadeChildComponents.updateComponentTree (component);
        commitTransaction ();
    }
    catch (AcafException exception)
    {
        rollbackTransaction ();
        throw exception;
    }
    finally
    {
        closeTransaction ();
    }
}

public void updateComponentFull (ComponentInterface<?> component) throws AcafException
{
    try
    {
        startTransaction ();
        ioFacadeComponentAttributes.updateComponent (component);
        ioFacadeChildComponents.updateComponentTree (component);
        commitTransaction ();
    }
    catch (AcafException exception)
    {
        rollbackTransaction ();
        throw exception;
    }
    finally
    {
        closeTransaction ();
    }
}

When no foreign keys with cascaded delete option can be defined between the child and parent database tables, you may need to implement an explicit delete method to avoid, that orphaned children remain in the database. Note, that this method has to be called explicitly when a business object should be deleted from the database. The following code block shows a sample implementation of such a method. Note, that first all children of the business object are deleted by the method deleteComponentTree() and then the business object itself, just in case that foreign keys are defined between the child and parent tables. The DatabaseFacade of the sample competition management application implements such a method.

public void deleteComponent (ComponentInterface<?> component) throws AcafException,
                                                                     JdbcException
{
    try
    {
        startTransaction ();
        ioFacadeChildComponents.deleteComponentTree (component);
        ioFacadeComponentAttributes.deleteComponent (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

Using Insert, Update and Delete Statements

When you cannot use updatable result sets to store business objects, you may register instances of the classes JdbcStoreComponentStatements and JdbcDeleteComponentStatements as store and delete processors at the IO facades. These classes issue explicit SQL INSERT, UPDATE and DELETE statements to store or delete business objects in/from the database.

The persistence facade StatementDatabaseFacade of the address book application illustrates how these classes are used. They are initialized with the component and attribute mediators already created and used in the previous steps. For each business class a store processor is registered at the IO facade used to store the business objects. Here the sample registration for a Person:

JdbcStoreComponentStatements storeStatement;
storeStatement = new JdbcStoreComponentStatements (connectionAccess, sqlDelimiter);
storeStatement.registerStatement (DatabaseConstants.Tables.PERSONS,
                                  componentMediators.getMediatorPerson (),
                                  attributeMediators.getMediatorsPerson ());
ioFacade.registerStoreComponentProcessor (Constants.ComponentTypes.PERSON,
                                          storeStatement);

To delete business objects from the database, delete processors has to be registered at the IO facade used to delete the business objects. Here the sample registration for a Person:

JdbcDeleteComponentStatements deleteStatement;
deleteStatement = new JdbcDeleteComponentStatements (connectionAccess, sqlDelimiter);
deleteStatement.registerStatement (DatabaseConstants.Tables.PERSONS,
                                   componentMediators.getMediatorPerson ());
ioFacade.registerDeleteComponentProcessor (Constants.ComponentTypes.PERSON,
                                           deleteStatement);

The method to store a business object completely is implemented like this:

public void storeComponent (ComponentInterface<?> component) throws AcafException,
                                                                    JdbcException
{
    try
    {
        startTransaction ();
        ioFacade.storeComponentTree (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

Note, that the store method doesn't delete business objects from the database, which has been removed from a container. To delete a business object from the database an explicit delete method has to be implemented and called. This doesn't work as transparent as with updatable result sets. Here the implementation of the delete method, which deletes the whole business object tree from the database. This works, too, even when the database doesn't support cascaded deletes:

public void deleteComponent (ComponentInterface<?> component) throws AcafException,
                                                                     JdbcException
{
    try
    {
        startTransaction ();
        ioFacade.deleteComponentTree (component);
        commitTransaction ();
    }
    catch (JdbcException jdbcException)
    {
        rollbackTransaction ();
        throw jdbcException;
    }
    finally
    {
        closeTransaction ();
    }
}

Implementing a Custom JDBC Mediator

For the most cases, the mediator classes of the de.tmasoft.acaf.jdbc can be used to exchange the data between the business objects and the result sets or statements. Sometimes you have to implement your own special mediator. One example for such a case is found in the competition management application. The rounds in this application may belong to a competition or a super-round. In the first case, the foreign key column COMPETITION_ID of the database table ROUNDS contains the component name of the parent competition in the second case, the foreign key column SUPER_ROUND_ID contains the component name of the parent super-round. The other column is set to NULL. This situation cannot be handled by the JdbcSimpleComponentMediator. So the mediator JdbcRoundComponentMediator is derived, which overrides the method updating the result set.

The rounds container is a multi-type sequence container. So the component type and the index of the rounds in the container are stored in the database. For the component type a component type converter is used, because the database value is different from the type string. So the constructor initializes the simple component mediator with the key columns ROUND_ID, COMPETITION_ID and SUPER_ROUND_ID, the column ROUND_TYPE containing the component type of the round the component type converter, the column SEQUENCE_NUMBER containing the index of the round and the start index value one.

The method updateResultSetWithComponentKey() updates the key columns of the result set with the component name of the round and its parent component. The first key column (ROUND_ID) is set to the component name of the round. When the parent component of the round is a competition, the second key column (COMPETITION_ID) is set with the component name of the competition and the third key column (SUPER_ROUND_ID) to NULL. Otherwise the parent component of the round is a super-round and the second key column is set to NULL and the third key column is set with the component name of the super-round.

public class JdbcRoundComponentMediator extends JdbcSimpleComponentMediator
{    
    public JdbcRoundComponentMediator ()
    {
        super (new String[] {DatabaseConstants.Columns.Rounds.ROUND_ID,
                             DatabaseConstants.Columns.Rounds.COMPETITION_ID,
                             DatabaseConstants.Columns.Rounds.SUPER_ROUND_ID},
               DatabaseConstants.Columns.Rounds.ROUND_TYPE,
               getRoundTypeConverter (),
               DatabaseConstants.Columns.Rounds.SEQUENCE_NUMBER, 
               1);
    }
    
    static private ComponentTypeConverter getRoundTypeConverter ()
    {
        ComponentTypeConverter.TypeMapping[] typeMappings;
        typeMappings = new ComponentTypeConverter.TypeMapping[]
        {
            new ComponentTypeConverter.TypeMapping(Constants.ComponentTypes.GROUP_ROUND,
                                                   DatabaseConstants.Values.RoundType.GROUP_ROUND),
            new ComponentTypeConverter.TypeMapping(Constants.ComponentTypes.PLAYOFF_ROUND,
                                                   DatabaseConstants.Values.RoundType.PLAYOFF_ROUND),
            new ComponentTypeConverter.TypeMapping(Constants.ComponentTypes.SUPER_ROUND,
                                                   DatabaseConstants.Values.RoundType.SUPER_ROUND)
        };
        return new ComponentTypeConverter(typeMappings);
    }
    
    public void updateResultSetWithComponentKey (List<Integer>         fieldIndices,
                                                 ResultSet             resultSet, 
                                                 ComponentInterface component) throws JdbcComponentWriteError
    {
        String                componentName    = "";
        ComponentInterface<?> currentComponent = component;
        Iterator<Integer>     iterator         = fieldIndices.iterator ();
        try
        {
            if (iterator.hasNext ())
            {
                componentName = currentComponent.getName ();
                JdbcUtilityFunctions.updateResultSetWithString (componentName,
                                                                resultSet, 
                                                                iterator.next ().intValue ());
            }
            currentComponent = currentComponent.getParent();
            componentName    = currentComponent.getName ();
            if (currentComponent.getType ().equals (Constants.ComponentTypes.COMPETITION))
            {
                if (iterator.hasNext ())
                {
                    JdbcUtilityFunctions.updateResultSetWithString (componentName,
                                                                    resultSet, 
                                                                    iterator.next ().intValue ());
                }
                if (iterator.hasNext ())
                {
                    JdbcUtilityFunctions.updateResultSetWithString (null,
                                                                    resultSet, 
                                                                    iterator.next ().intValue ());
                }
            }
            else
            {
                if (iterator.hasNext ())
                {
                    JdbcUtilityFunctions.updateResultSetWithString (null,
                                                                    resultSet, 
                                                                    iterator.next ().intValue ());
                }
                if (iterator.hasNext ())
                {
                    JdbcUtilityFunctions.updateResultSetWithString (componentName,
                                                                    resultSet, 
                                                                    iterator.next ().intValue ());
                }
            }
        }
        catch (JdbcFieldWriteError exc)
        {
            throw new JdbcComponentWriteError (component.getType (),
                                               component.getName (),
                                               componentName,
                                               exc.getColumnName (),
                                               exc.getException());
        }       
    }
}

Mapping Set Attributes

The mapping of the most simple attributes is straight forward. Set attributes implementing the interface SetAttributeInterface contain a collection of enumeration values and may be mapped in different ways to the database. The most simple way is to use a JdbcSimpleAttributeMediator, which maps the enumeration values as comma separated value string in the database. The enumeration values may be converted to a special database value using the value converter class SetAttributeValueConverter. A sample for this method may be found in the competition management application, where the countries of the business class Competition are mapped in this way:

List getMediatorsCompetition ()
{
    SetAttributeValueConverter<CountryEnumeration> countriesAttributeConverter;
    if (mediatorsCompetition == null)
    {
        countriesAttributeConverter = new SetAttributeValueConverter<CountryEnumeration> (CountryEnumeration.getIso2Converter ());
        mediatorsCompetition = Arrays.asList (new JdbcPrecisionDateAttributeDateMediator (Constants.Attributes.Competition.BEGIN,
                                                                                          DatabaseConstants.Columns.Competitions.BEGIN,
                                                                                          DatabaseConstants.Columns.Competitions.BEGIN_PRECISION),
                                              new JdbcSimpleAttributeMediator (Constants.Attributes.Competition.COMPETITION_CODE,
                                                                               DatabaseConstants.Columns.Competitions.COMPETITION_CODE,
                                                                               true),
                                              new JdbcSimpleAttributeMediator (Constants.Attributes.Competition.COUNTRIES,
                                                                               countriesAttributeConverter,
                                                                               DatabaseConstants.Columns.Competitions.COUNTRIES),
                                              new JdbcPrecisionDateAttributeDateMediator (Constants.Attributes.Competition.END,
                                                                                          DatabaseConstants.Columns.Competitions.END,
                                                                                          DatabaseConstants.Columns.Competitions.END_PRECISION),
                                              new JdbcSimpleAttributeMediator (Constants.Attributes.Competition.NOTE,
                                                                               DatabaseConstants.Columns.Competitions.NOTE,
                                                                               true),
                                              new JdbcSimpleAttributeMediator (Constants.Attributes.Competition.OFFICIAL_NAME,
                                                                               DatabaseConstants.Columns.Competitions.OFFICIAL_NAME,
                                                                               true));
    }
    return mediatorsCompetition;
}

Another way to map set attributes to the database is using the JdbcSetAttributeValueColumnsMediator. This mediator requires, that each enumeration value of the value set is mapped to one boolean column in the database. This approach may be used, when the enumeration contains only a view values.

When the set attribute may contain a lot of enumeration values, a separate database table may be required to store the enumeration values for the set. In this case the mediator class JdbcResultSetComponentSetAttributeMediator can be used. The database table must contain the foreign key columns of the business object the set attribute belongs to and one column for the enumeration value. The mediator is initialized with database connection used to create the SQL select statement for this table, a component mediator to set the parameters in the WHERE clause of the select statement and the key columns in the result set, the attribute keyword of the set attribute, the column name for the enumeration value, an enumeration value converter to convert the enumeration value to the database value and the select statement to create the result set. In the competition management application the nationalities of the business class Player are mapped in this way to the database table PLAYER_NATIONALITIES. The set attribute mediator may be registered as processor, for the business class the set attribute belongs to, at the IOFacade. The following code block shows the methods, which create the SQL statement and the mediators:

private String getColumnsPlayerNationalities ()
{
    return dl +
           DatabaseConstants.Columns.PlayerNationalities.COUNTRY_CODE   + dl + "," + dl +
           DatabaseConstants.Columns.PlayerNationalities.PLAYER_ID      + dl;
}

String selectPlayerNationalities ()
{
    String columns = getColumnsPlayerNationalities ();
    return "SELECT " + columns + " FROM " + dl +
           DatabaseConstants.Tables.PLAYER_NATIONALITIES + dl + 
           " WHERE " + dl +
           DatabaseConstants.Columns.PlayerNationalities.PLAYER_ID + dl + "=?";
}

JdbcComponentMediatorInterface getMediatorPlayer ()
{
    if (mediatorPlayer == null)
    {
        mediatorPlayer = new JdbcSimpleComponentMediator (new String[] {
                                                          DatabaseConstants.Columns.Players.PLAYER_ID,
                                                          DatabaseConstants.Columns.Players.TEAM_ID });
    }
    return mediatorPlayer;
}

JdbcResultSetComponentSetAttributeMediator<CountryEnumeration> getMediatorPlayerNationalities ()
{
    if (mediatorPlayerNationalities == null)
    {
        mediatorPlayerNationalities = new JdbcResultSetComponentSetAttributeMediator<CountryEnumeration>  (databaseConnectionAccess, 
                                                                                                           componentMediators.getMediatorPlayer (),
                                                                                                           Constants.Attributes.Player.NATIONALITIES,
                                                                                                           DatabaseConstants.Columns.PlayerNationalities.COUNTRY_CODE,
                                                                                                           CountryEnumeration.getIso2Converter (),
                                                                                                           selectStatements.selectPlayerNationalities ());
    }
    return mediatorPlayerNationalities;
}

Mapping Reference Attributes

Reference attributes implement a 0..1 relationship between two business objects, so a foreign key may be defined between the database tables containing these objects. When this is the case, the mediator class JdbcReferenceAttributeMediator may be used to fill the foreign key columns with the component name of the referenced component and its parents. See the class description for details how this works. This mediator can only be used, when the reference path contains no index expression and the number of container keyword/component name pairs is fixed. In the competition management application the reference attributes teamA and teamB of the business class Game are mapped in this way. These attributes reference a team-classification representing the teams playing against each other by their component name. So the attribute mediator list for a Game is implemented as shown in the following code block:

List<? extends JdbcAttributeMediatorInterface> getMediatorsGame ()
{
    if (mediatorsGame == null)
    {
        mediatorsGame = Arrays.asList (new JdbcSimpleAttributeMediator (Constants.Attributes.Game.LABEL,
                                                                        DatabaseConstants.Columns.Games.GAME_LABEL,
                                                                        true),
                                       new JdbcIntegerAttributeMediator (Constants.Attributes.Game.SCORE_A,
                                                                        DatabaseConstants.Columns.Games.SCORE_A), 
                                       new JdbcIntegerAttributeMediator (Constants.Attributes.Game.SCORE_B,
                                                                         DatabaseConstants.Columns.Games.SCORE_B),
                                       new JdbcReferenceAttributeMediator (Constants.Attributes.Game.TEAM_A,
                                                                           Constants.Attributes.Group.CLASSIFICATION,
                                                                           DatabaseConstants.Columns.Games.TEAM_A), 
                                       new JdbcReferenceAttributeMediator (Constants.Attributes.Game.TEAM_B,
                                                                           Constants.Attributes.Group.CLASSIFICATION,
                                                                           DatabaseConstants.Columns.Games.TEAM_B));
    }
    return mediatorsGame;
}

Another way to map a reference attribute to the database is, to just store the reference path as string in the database table. This approach is used in the competition management application, where the reference attribute team of the business class TeamClassification is stored as string in the database. The reference path of this attribute contains index expressions and may reference a Team or another TeamClassification and the length of the reference path may vary, depending on how many round levels are involved. The delimiter characters may be replaced in the reference path stored in the database using the value converter ReferenceAttributeValueConverter when desired. The following code block shows the initialization of the simple attribute mediator for this case:

List<? extends JdbcAttributeMediatorInterface> getMediatorsTeamClassification ()
{
    if (mediatorsTeamClassification == null)
    {
        mediatorsTeamClassification = Arrays.asList (new JdbcSimpleAttributeMediator (Constants.Attributes.TeamClassification.LABEL,
                                                                                      DatabaseConstants.Columns.TeamClassifications.CLASSIFICATION_LABEL,
                                                                                      true),
                                                     new JdbcIntegerAttributeMediator (Constants.Attributes.TeamClassification.GAMES_PLAYED,
                                                                                       DatabaseConstants.Columns.TeamClassifications.GAMES_PLAYED),
                                                     new JdbcIntegerAttributeMediator (Constants.Attributes.TeamClassification.POINTS,
                                                                                       DatabaseConstants.Columns.TeamClassifications.POINTS), 
                                                     new JdbcSimpleAttributeMediator (Constants.Attributes.TeamClassification.SCORE_DIFFERENCE,
                                                                                      DatabaseConstants.Columns.TeamClassifications.SCORE_DIFFERENCE),
                                                     new JdbcSimpleAttributeMediator (Constants.Attributes.TeamClassification.TEAM,
                                                                                      new ReferenceAttributeValueConverter ('.', '\0', '[',']'),
                                                                                      DatabaseConstants.Columns.TeamClassifications.TEAM_PATH));
    }
    return mediatorsTeamClassification;
}

Testing the Database Facade

The following code block shows a simple JUnit test method, which stores and retrieves an address book in/from the database. The sample is taken from the address book application. First an address book is created and some contacts are created by the method initializeAddressBook(). Then a copy of the address book is created, which is used after the update from the database to compare the updated address book with the original one. After storing the address book all contacts are removed by the ParseAndClearVisitor and the address book is updated from the database. Now it should be equal to the copied address book.

public void testSimpleTestCase () throws AcafException
{
    AddressBook          addressBookCopy;
    AddressBook          addressBook    = new AddressBook ("Address Book");
    ParseAndClearVisitor clearCommand   = new ParseAndClearVisitor ();
    SimpleDatabaseFacade databaseFacade = SimpleDatabaseFacade.theInstance;
    //
    // Create some contacts
    //
    initializeAddressBook (addressBook);
    //
    // Create a copy and store the address book in the database
    //
    addressBookCopy = addressBook.createCopy ();
    databaseFacade.storeComponentFull (addressBook);
    //
    // Remove all contacts from the address book
    //
    clearCommand.setCommand ("Contacts .*");
    addressBook.acceptVisitor (clearCommand);
    //
    // Update the address book from the database and compare it with the copy
    //
    databaseFacade.updateComponentFull (addressBook);
    Assert.assertTrue (addressBook.isComponentEqual (addressBookCopy));
}

History

The history of ACAF starts in the year 1989, when I joined a software development department at Digital Equipment, where I developed network management software for network operation systems. From that period date the terms component, attribute, component name, component type and container. The manageable items in these network boxes, like lines, circuits, logical units, etc., was internally denoted as components and their properties as attributes. The commands to manage the network components had pretty much the same syntax as command line syntax implemented by the parsing visitors of the package de.tmasoft.acaf.parser. The software was developed in those days with Bliss, a programming language between assembler and C.

In 1992 we started a project, where we implemented a operation system emulation on VMS with C++. To manage the operation system I transferred the network management concepts and implemented them in C++ as a framework. Afterwards I used the concept and the framework in several other projects. Meanwhile design patterns became popular and I redesigned the framework using design patterns like composite, visitor, and the component factory was born.

In 1999 we started a Java project to develop a classic GUI application with a database in the backend. In these days Java was still supported by Microsoft and we used the Microsoft Visual J++ environment with ADO record sets to exchange data between the application and the database. For this project I ported the framework to Java and implemented an O/R mapping with ADO. For this purpose I implemented ADO attribute adapters, which has been initialized with the normal attribute as adaptee and the ADO record set retrieved from the database. When an ADO attribute was modified, it modified the adapted attribute and the field in the record set. The disadvantage of this approach was the tight coupling between ADO and the business classes. To attach the GUI to the business classes I designed a mediator framework, which exchanged the data between the attributes and the GUI elements. The mediators has been attached as oberver to the attributes and as delegate to the GUI elements. So they has been notified when the value of the GUI element or the attribute has been changed, and could adjust the value of the other side.

In 2003 I joined a project, where we developed a web-service backend application on SAP. After I presented the concepts of the framework to the SAP software architect, which supported the project development team, we decided to port parts of the framework to ABAP/Objects and used it as base for the application. Especially the visitor concept was very useful for this project, because we had to parse and create many different XML structures to serialize and deserialize the business objects. We also used a visitor to verify the business objects and to create a list of error messages for the client of the web-service.

In 2008 I used the Java framework to implement a web-service application with a database in the backend. For this application I implemented the O/R mapping with JDBC result sets using mediators instead of adapters. In 2015 I reviewed, refactored and cleared out the framework and improved the documentation to be able to publish it.

Version:
2.0
Author:
Thomas Mayr, TMASoft
Skip navigation links