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:
- 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.
- If only javax.jmi.packagePrefix tag is present, its value with the
following changes to it is taken as the package prefix:
- if the value is "javax.jmi", "org.netbeans.jmiimpl.mof"
is taken instead
- if the value starts with "org.netbeans.jmi.", the
value where ".jmi." is replaced by ".jmiimpl." is taken
- if the value starts with "org.omg." or equals to
"org.omg", the value with replaced "org.omg" by
"org.netbeans.jmiimpl.omg" is taken
- if none of the above is true, the value with added ".impl"
suffix is taken
- 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.
| Element | Appropriate Handler | Appropriate Storable |
| Package | org.netbeans.mdr.handlers.PackageProxyHandler |
org.netbeans.mdr.storagemodel.StorablePackage |
| ClassProxy | org.netbeans.mdr.handlers.ClassProxyHandler |
org.netbeans.mdr.storagemodel.StorableClass |
| Instance | org.netbeans.mdr.handlers.InstanceHandler |
org.netbeans.mdr.storagemodel.StorableObject |
| Association | org.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.
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).
(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).
(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).
(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
}