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.
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:
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);
...
}
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.
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
}