Bean Extender Guide to Features


The Dipping Framework

To begin, you must understand what dips are. Dips are beans (reusable software components) that react to state changes in dippable beans to which they are attached. Dippable beans are beans that have been run through a morphing tool, in which the bean is wrapped with an API, so that dips can be applied. Dips modify the runtime behavior of the beans to which they are attached, but they do not provide new interfaces on beans to which they are attached. Dips are attached through the EventListener model. This one-way communication mechanism, while limiting, can be used to add behaviors to a dippable bean. However, if the original bean was thread safe, its dippable version remains thread safe. If the original bean was immutable, the original bean part of the dippable bean remains immutable. The original bean part is what is visible to the clients of the bean. Though dips can add behavior, they cannot add new properties, events, or methods to the bean.

The Dipping Framework allows the user to take an existing Java class and produce a new class to which dips can be applied. This can be done in one of the following ways:

In each case, the new dippable class implements the Dippable interface. If desired, the new dippable class could implement the DippableExtended interface, which is a child of the Dippable interface. Therefore, all dippable classes, even those implementing the DippableExtended interface, are implementations of the Dippable interface.

During the creation of a new dippable class, dips can be added to the class definition of the dippable bean. Because dips can be added to an instance of a dippable bean, instances of the same class can have different behaviors applied to them through the dipping process. The dipping concept creates a step between development and deployment for Java applications. This step allows new behaviors to be added to existing binary objects.

A dippable bean allows dips to intercept properties, methods, and events. All set<PropertyName>() (1) methods are treated as property-related. All fire<EventName>() or process<EventName>() methods are treated as event-related.

The Dipping Framework works outside of a beans environment and works on any Java class that follows the JavaBeans naming strategy.

Security and the Dippable Bean

There may exist a case where the designer of the original bean doesn't want the original bean to be used. Maybe the dippable bean will be used with a license server dip or a security dip. Maybe the designer just doesn't want the original bean without the dips applied to be used. There are two approaches to handling this type of security in the dippable bean, but both approaches mean the design of the original bean must be effected.

Lightweight security

Lightweight security is the easiest to implement. This approach is intended for original beans with simply general security issues. To implement lightweight security the original bean could be created with package, not public, access. The dippable bean will be generated with public access. So general users will be able to use the dippable bean, but not the original bean.

This approach is considered lightweight because a hacker could do what the BeanMorpher does - create a public child of the original bean in the same package. The hacker's public child could then be used as any public class.

Heavyweight security

Heavyweight security requires a lot more programming on the part of the original bean. This approach is intended for original beans who do not want it even remotely possible for the original bean to be used alone. To implement heavyweight security, the original bean will have to implement an initialize(key) method, and only a corresponding Dip knows the key. Unless the initialize(key) method is called with the correct key, the original bean should not allow the methods to run correctly. In this case, it is obviously better for the dippable bean to be created with the corresponding Dip already applied. See the help on the different options available while using BeanMorpher for information on how a dip can be pre-applied to a dippable bean.

Creating a Dippable Bean

Making an existing bean dippable is called morphing. Morphing is usually done by using a DippableGenerator class that creates a Dippable class either through aggregation or inheritance, as described above. The DippableGenerator class creates a BeanInfo class for the dippable version of the original, parent class. The generated BeanInfo class allows for the customization of dip properties for dips attached to the dippable bean.

There are restrictions on creating a dippable bean as a child of the original class. These restrictions are:

There are restrictions on creating a dippable bean using an interface and an implementation class. These restrictions are:

Creating the new dippable class as a final class

The dippable generator creates final classes to prevent subversion of the dips through further inheritance. However, there is an option to not have the new dippable class be final.

Class based Dips

Usually Dips are applied to a specific instance of a dippable class. However, there is an option to the dippable generator to allow dips to be applied to the class definition of the dippable bean. This means all instances of the new dippable bean would have the dip applied. When a dippable bean is pre-dipped with dips, the dippable bean can be customized so that only the methods needed by the dips are overridden in the new dippable bean.

Properties added to the Dippable Bean

When a method is constrained by a DipVetoableMethodListener (see "Creating a Dip"), the listener in the dippable bean throws an exception (MethodVetoException) to prevent the method from executing. However, the dippable bean has options as to what it should do about the method return. These return options are:

Property methods in the new dippable bean allow the instantiator of the dippable bean to choose which of these options to use. However, if no programmatic decision is made, the following is the default:

If the listener vetoes a method by throwing a MethodVetoException, the dippable bean checks to see if the listener provided a return object for the method in the MethodVetoException. If a return object is provided, it uses this object as the return code for the vetoed method. If no return object is provided, then the vetoes method return 0 or false or null, depending on its type.
Note:Because event and property methods always define a void return, their veto exceptions do not need to contain a return object.

Overriding Events

Generation of an event can be overridden only if the original bean declared a fire<EventName>() method to fire the event. This pattern is in addition to the add<ListenerType>() and remove<ListenerType>() method patterns described in JavaBeans.

For example, the only way the new dippable class can override PropertyChangeEvent's, is if the original class used a firePropertyChange() method to fire the event. Notice that the fire<EventName>() method does not end in the word "Event". If the method had been called firePropertyChangeEvent(), the method would not have been recognized as a method that fires an event.

The BeanMorpher application

A convenient application named BeanMorpher implements a command line interface and a constructor for morphing a bean class. The BeanMorpher accepts a fully qualified class name for the original bean and creates two Java files: a Dippable child class and a DippableBeanInfo class. The Dippable child class is created by the DippableGenerator. Help on the different options available while using the BeanMorpher can be viewed by typing:

java com.ibm.beans.dip.BeanMorpher -h

Creating an Implementation of DippableSupport

The new dippable bean implements the Dippable interface by using a class that implements the DippableSupport interface. The DippableSupport interface defines how a dippable bean implements the Dippable interface and manages the dips applied to the bean. The DippableSupportImp class is the default implementation of the DippableSupport interface. You can create your own implementation of the DippableSupport interface and use that implementation on your dippable bean.

If the new dippable bean implements the DippableExtended interface:

  • The implementation strategy is defined by the DippableExtendedSupport interface.

  • The implementation strategy is implemented by the DippableExtendedSupportImp class.

This approach is an extension to the previously described Dippable strategy:

  • DippableExtended is a child of Dippable.
  • DippableExtendedSupport is a child of DippableSupport.
  • DippableExtendedSupportImp is a child of DippableSupportImp.


* Figure dipfw01 not displayed.

All implementations of DippableSupport manage how beans interact with dips. These interactions include:

All implementations of DippableSupport must implement the DippableSupport interface and provide the implementation for the dippable bean of the methods in the Dippable and MergeCustomizable interfaces. All implementations of DippableSupport need to consider the following:

Creating a Dip

A dip consists of the following parts:

  1. The part of the API that supports the policy and implementation of all dips.
  2. The part of the dip that makes the dip unique (the kind of work the dip is to perform).

    For example, a dip on versioning might have separate methods to set or to get the version number, or a dip on security might check a secure database.

The Dip interface describes the policy and implementation needed in all dips. The policy dictates when a dip can interact with a dippable class. The Dipping Framework approach to policy mirrors the existing JavaBeans approach to bound and constrained properties. A dip can bind and constrain the properties, methods, and events of a given dippable bean. Constraining a property, method, or event means the dip has veto authority over the execution of that property, method, or event. Binding a property, method, or event means the dip is notified after changes to the property, method, or event.

A dip indicates its interest by providing the following listeners:

By returning different listeners or null, the create*Listener() methods in the Dip interface determine the policy for each dip.

The policy of the dip is one of its strategic design points. Another strategic design point is that the two parts of the dip do not have to be in the same class. When a new dip is created, the createImplementation() method is used to create a new instance of the second part of the dip. If the dip decides not to have a separate implementation, the createImplementation() method returns null. This return is passed to the create*Listener() methods that manage this parameter for the specific dip.

If the output from the createImplementation() method is serializable, it is saved so the original output from the createImplementation() method is serialized and used to recreate the listeners during deserialization. If the output from the createImplementation() method is not serializable, it is not saved and not serialized. In this case, if the dippable bean is serialized, the createImplementation() method of the dip is called during deserialization to be used to recreate the listeners during deserialization.

If a dip is going to be part of the class definition of a dippable bean, the dip must have a constructor without parameters. This means that every time the dippable bean is constructed, the dip is immediately applied to the bean. The dippable bean uses the Beans.instantiate() method to get an instance of the dip.

Designing a Dip

To write a dip, you need to decide whether to subclass the SimpleDip class or create your own implementation of a dip. The SimpleDip class provides default behavior for many of the design considerations. The class name for your dip is important. The class name must end in "Dip" to help other tools determine that your bean is a dip.
Note: If you make your dip dippable using any of the BeanExtender tools for morphing a bean, be sure to provide a name for the new dippable dip that ends in "Dip". The default name for new dippable beans will end in "Dippable" which may make it unusable with some tools.

You need to decide whether to split the dip implementation into separate pieces. Separate pieces increase the complexity but can give more flexibility. For additional information on splitting the implementation, see "Creating a Dip".

Before you write a dip, you must decide how it will work. The following sections discuss items to consider when you design a dip.

Adding and Removing Function

You should add function instead of removing or restricting it. Dips are best suited for adding function to an existing bean. Restricting function is possible, but the restriction can sometimes be circumvented by using a different instance of the bean. A dip can interact only with a particular instance of a bean.

Intercepting Events, Properties, and Methods

A dip should intercept only the specific items in which it is interested. Specifying what properties, methods, and events to intercept is done in the create*Listener() methods. For additional information, see the Dip.create*Listener() methods in the Bean Extender API Reference.

Care must be taken when choosing methods to veto. Some methods cannot be intercepted at all while others cannot be intercepted for veto. The following interceptions are special cases:

Choosing Applicability

Choose applicability. Some dips are applicable to all beans and provide general extensions. Other dips focus on hooking specific interfaces to add or control function.

Care must be taken to prevent the dip from being applied to a bean that does not have the desired interface.

Controlling Dip Interoperability

Applied dips can control the addition of other dips by returning true or false from the worksWith() method. Dips cannot control the removal of other dips. For additional information, see the Dip.worksWith() method in the Bean Extender API Reference.

If a dip needs to control its own addition, it must do it within the createImplementation() method. The bean to which the dip is being applied may be examined for suitability and a DipRejectAdditionException is thrown if dipping is unreasonable. For additional information, see the Dip.createImplementation() method in the Bean Extender API Reference.

A dip uses the isRemovable method to control its own removal. For additional information, see the Dip.isRemovable() method in the Bean Extender API Reference.

Care must be taken to not retain a reference to the dippable bean. The dippable bean will retain references to the dips. If a reference loop is made, garbage collection may not destroy the bean even when the last external reference to it has been removed.

Serializing and Deserializing

All dips are serializable and should implement a readObject and a writeObject method, if necessary.

The readObject() method of the dip is different than the dip binding of the readObject() method of the dippable bean. The readObject() method of the dip should recreate the state of the dip and not interact with the dippable bean. The bound listener to the readObject() method is invoked after the readObject() method of the dippable bean has completed, thus the entire state of the dippable bean, including the dips, is initialized and ready for use.

When a dip is being deserialized, it should realize that an associated bean may not have completed its deserialization. If an associated bean is not finished deserializing, the dip must wait before it starts to interact with the bean.

Dips can determine when a bean has restored its state by binding the readObject() method call. When the readObject() method call is complete, the dip can be assured that the dippable bean and all applied dips have completed their restoration and are ready. The readObject() method may not be vetoed.

All dippable beans have a readObject() and writeObject() method. These methods are added to the dippable bean by the BeanMorpher.

Using Dippable Bean Methods

You may need to determine which dippable bean method was invoked. The MethodCallEvent class provides information on which method was invoked. The getMethodId() method of the MethodCallEvent class returns the signature of the method. Method names are qualified by their most derived class. For example, the method might be reported as:

public boolean java.lang.Object.equals(java.lang.Object)

if it has not been overridden by a class in the bean's class hierarchy, or it may appear as:

public boolean com.my.package.MyClass.equals(java.lang.Object)

if the bean has overridden it.

Vetoing Methods, Events, and Properties

The vetoing of methods, events, and properties use similar processes, but each vetoing process calls different methods and throws different exceptions. The following vetoing sequence occurs when a method is vetoed:

  1. The handleVetoableMethod() method of the dip is called and passes a method ID string using the MethodCallEvent class.

  2. If that dip wants to veto the method call on the original bean, it throws a MethodVetoException. If that dip wants to affect the return code of the dippable bean, it can affect the return code by using the constructor of the MethodVetoException that allows the dip to specify the return object.

  3. If an exception is thrown, The Dipping Framework catches that exception and calls the recoverFromMethodVeto method for all dips on which it called handleVetoableMethod. The same method ID string is passed in.

  4. The framework then throws the MethodVetoException without calling the remaining handleVetoableMethod() method of the dip, or any of the post hook method calls.

  5. The dippable bean catches the MethodVetoException, and if the return object is in the exception, this value is used to set the return value of the vetoed method. If the return object in the exception is null, the vetoed method returns 0 or false or null, depending of the return value type.

Vetoing for events and properties is similar.

Applying a Dip

Dips must be able to be configured at design time and runtime.

Customizing a Dip

A dip cannot change the set of properties, events, or methods a bean exposes. The dip properties are not exposed outside of the dippable bean and cannot be set through normal property mechanisms. The following are customization choices for dips.

Using the Dip User Interface

There is no mechanism for a dip to merge a user interface into the dipped bean's user interface. Therefore, all dialogs used by the dip must be created programmatically and run in their own AWT frame.

The dip can hook an existing event in the target bean. For example, you can use ButtonClicked, but a dip may not add its own event such as PrintButtonClicked.

Dip runtime dialogs may be displayed when the dipped bean is first activated. Typically each dialog is an independent frame. If a dip is knowledgeable about the bean to which it is applied, it may add itself to the bean's interface. However, this requires intimate knowledge of the bean's layout and event processing.

A bean might not be able to run in an environment where a user interface is available. Therefore, dips should in general split their function and presentation. For additional information on the splitting of function, see "Creating a Dip".

Presenting a Dip

The following are descriptions of dip interface presentations:

The Configuration Interface Presentation

The configuration interface is handled by the property sheet or a customizer. A customizer can always run in a user interface. However, dip properties can be modified outside of a customizer by a property sheet. Any constraints the dip has on the property setting must be enforced by the dip and not by the customizer.

Runtime Dialog Presentation

A runtime dialog, such as a security logon panel, must be driven programmatically from within the dip. Care must be taken to handle whether the dip is running in a user interface environment.

The function that the dialogs provide should be encapsulated in methods on the dip. The dip may be driven by some other means, such as property settings. The dip must be able to handle the lack of dialog information when a user interface is not present. Therefore, a bean with the appropriate settings applied to it while in a user interface environment can be deployed to a non-user interface environment.

Packaging Dipped Beans

Packaging of your deployed dippable bean must include the class implementations of the original bean, the dippable bean classes, and the classes of your dip. If classes are present on your CLASSPATH statement and inside of a JAR file, most class loaders will pick up the classes from the CLASSPATH. This might cause a runtime IllegalAccessError for package protected classes within your dip.

A suggested naming convention is to end the name of all JAR files containing dips with "dip". This simply makes them easier to distinguish.


Footnotes:

(1) The get<PropertyName>() methods are treated as general methods, because they do not change the property.

(2) These methods are not implemented because the implementation class could be implementing more than the interface associated with it.

(3) Printing the call stack trace is a useful option, but it is not Serializable. Because java.io.OutputStream does not implement java.io.Serializable, the OutputStream cannot be serialized. When the dippable bean is deserialized, the OutputStream is set to null, and nothing is written to the stream.


[ Top of Page | Previous Page | Next Page | Table of Contents | Documentation Homepage ]