Custom Implementations of JMI Interfaces

Note: The API described in this document is not an official contract and may change without preserving a backward compatibility. Such changes will be announced in advance on MDR mailing lists.
Content:
1. Overview
2. Providing a Simple Custom Implementation
3. Inheritance Issues
4. Modeled Constraints Support

1. Overview

MDR allows users to provide their own implementation of JMI interfaces. Providing such an implementation is mandatory for those model elements containing operations and/or derived features. Providing a custom implementation for other model elements is optional - it can be used, e.g., for modeled constraints checking.

2. Providing a Simple Custom Implementation

In the simplest case, to provide a custom implementation of a JMI interface, create an abstract implementation class called <NameOfTheJMIInterface>Impl located in the package that is derived in the same way as the package for JMI interfaces, only instead of the value of javax.jmi.packagePrefix, the following package prefixes are considered:

  1. If there is a org.netbeans.implPackagePrefix tag attached to the outermost package of the metaobject, the value of this tag is considered to be the package prefix.
  2. If only javax.jmi.packagePrefix tag is present, its value with the following changes to it is taken as the package prefix:
  3. If neither org.netbeans.implPackagePrefix nor javax.jmi.packagePrefix tags are present, "impl" is assumed to be the package prefix.
The implementation class should implement the selected JMI interface, extend the appropriate handler, and provide a protected constructor accepting a reference to the appropriate storable passing it to its parent (for the appropriate handlers and storables see the table below). All JMI methods that correspond to the model element's operations and derived features are required to be implemented by this class. Implementation of other methods is optional.

ElementAppropriate HandlerAppropriate Storable
Packageorg.netbeans.mdr.handlers.PackageProxyHandler org.netbeans.mdr.storagemodel.StorablePackage
ClassProxyorg.netbeans.mdr.handlers.ClassProxyHandler org.netbeans.mdr.storagemodel.StorableClass
Instanceorg.netbeans.mdr.handlers.InstanceHandler org.netbeans.mdr.storagemodel.StorableObject
Associationorg.netbeans.mdr.handlers.AssociationHandler org.netbeans.mdr.storagemodel.StorableAssociation

As an example, consider a custom implementation of the javax.jmi.model.ModelElement interface:

package org.netbeans.jmiimpl.mof.model;

import javax.jmi.model.*;
import org.netbeans.mdr.handlers.*;
import org.netbeans.mdr.storagemodel.*;

public abstract class ModelElementImpl extends InstanceHandler implements ModelElement {

  protected ModelElementImpl(StorableObject storable) {
    super(storable);
  }

  public boolean isFrozen() { ... }

  public Collection findRequiredElements(Collection kinds, boolean recursive) { ... }

  public boolean isVisible(ModelElement otherElement) { ... }

  public boolean isRequiredBecause(ModelElement otherElement, String[] reason) { ... }

  public List getQualifiedName() { ... }

}

All JMI methods that do not require custom implementation are implemented during the runtime using a dynamically generated bytecode. The generated default implementation transfers JMI calls to Handlers, which process them by accessing the persistence SPI. Figure 1 illustrates the inheritance relationships between Handlers, custom implementations, and the generated bytecode.

Figure 1.

If a non mandatory implementation for a JMI method is present in the custom implementation class, bytecode generator generates the super_<methodName> method so that user can call this method from the custom implementation to invoke the default implementation. In such a case, due to inheritance relationships depicted above, the custom implementation class should provide an abstract declaration of the protected super_<methodName> method.

As an example, consider a custom implementation of the ModelElement's setName(...) method inside the ModelElementImpl class:

  protected abstract void super_setName(String name);

  public void setName(String name) {
    ...
    // do your stuff here and optionally call the default implementation
    super_setName(name);
    ...
  }

3. Inheritance Issues

Generating default implementations of JMI interfaces gets more complicated in the situation when the custom implementations exist for JMI interfaces related by inheritance. Thus, the exact behavior of the bytecode generator has to be specified for this case together with a set of simple rules for providing custom implementations of such interfaces.

(1) Generated default implementation class of a JMI interface inherits either from
- custom implementation class of the JMI interface if it exists (see the generated implementation of JMIInterfaceA in Figure 2),
- custom implementation class of the nearest JMI interface's ancestor that has a custom implementation (see the generated implementation of JMIInterfaceB in Figure 2),
- or an appropriate Handler (see the generated implementation of JMIInterfaceC in Figure 3).

Figure 2.

(2) If a JMI interface inherits from two or more direct ancestors (multiple inheritance), the bytecode generator generates its default implementation class if the default implementation classes exist for all the direct ancestors and only one of these classes contains custom implementation(s) in its inheritance tree (see the generated implementation of JMIInterfaceD in Figure 3).

Figure 3.

(3) If a JMI interface inherits from two or more direct ancestors and custom implementations exist in more of their implementation inheritance trees, user is required to provide a custom implementation class for this interface (see the implementation of JMIInterfaceD in Figure 4).

Figure 4.

(4) Creating a custom implementation class for a JMI interface that inherits from other interface(s) with custom implementation(s), user can
- inherit the custom implementation class from an implementation class of the selected interface ancestor,
- delegate method calls from the custom implementation class to implementation classes of the selected interface ancestors,
- or implement the custom implementation class from scratch.

4. Modeled Constraints Support

A possibility to provide custom implementations of JMI interfaces can help users to implement checking of modeled constraints. For instance consider the following example. User wants to change the ModelElement's behavior so that, whenever a ModelElement's name changes, the new name is ensured to be unique among names of all the "sibling" elements in the ModelElement container's namespace. Such a behavior can be easily implemented using a custom implementation of the ModelElement's setName(...) method.

  protected abstract void super_setName(String name);

  public void setName(String name) {
    if (getContainer() != null && !name.equals(getName()) {
      if (!getContainer().isNameValid(name))
        throw new ConstraintViolationException(...);
    }
    super_setName(name);
  }

The second possibility to implement some constraint checks is implementing the _verify(...) method. This method is invoked as a callback from refVerifyConstraints(...), thus, it serves for an "on demand" verification of element's constraints. A correct implementation of the _verify(...) method should always call super._verify(...).

  protected Collection _verify(Collection verify) {
    super._verify(verify);
    // do your stuff here
  }