Java Dynamic Management Kit 5.1 Tutorial

Chapter 13 Relation Service

The relation service defines and maintains logical relations between MBeans in an agent. It acts as a central repository of relation types and relation instances, and it ensures the consistency of all relations it contains. Local and remote applications that access the agent can define new types of relations and then declare a new instance of a relation between MBeans.

You can only create relations that respect a known relation type, and the service allows you to create new relation types dynamically. Then you can access the relations and retrieve the MBeans in specific roles. The relation service listens for MBeans being unregistered and removes relation instances that no longer fulfill their relation type. It sends notifications of its own to signal when relation events occur.

Java Dynamic Management Kit (Java DMK) also exposes the interface classes for defining your own relation objects. By creating relation instances as objects, you can implement them as MBeans that can expose operations on the relation they represent.

Like the other services, the relation service is instrumented as an MBean, allowing remote access and management.

The code samples in this topic are taken from the files in the Relation directory located in the main examplesDir (see “Directories and Classpath” in the Preface).

This chapter covers the following topics:

13.1 Defining Relations

A relation is composed of named roles, each of which defines the cardinality and class of MBeans that will be put into association with the other roles. A set of one or more roles defines a relation type. The relation type is a template for all relation instances that associate MBeans representing its roles. We use the term relation to mean a specific instance of a relation that associates existing MBeans according to the roles in its defining relation type.

For example, we can say that Books and Owner are roles. Books represents any number of owned books of a given MBean class, and Owner is a single book owner of another MBean class. We might define a relation type containing these two roles and call it Personal Library. It represents the concept of book ownership.

The following diagram represents this sample relation, as compared to the unified modeling language (UML) modeling of its corresponding association.

Figure 13–1 Comparison of the Relation Models

Diagram comparing the JMX and UML relation models

There is a slight difference between the two models. The UML association implies that each one of the Books can only have one owner. Our relation type only models a set of roles, guaranteeing that a relation instance has one Owner MBean and any number of MBeans in the role of Books.


Note –

The relation service does not do inter-relation consistency checks; if they are needed they are the responsibility of the designer. In our example, the designer needs to ensure that the same book MBean does not participate in two different Personal Library relations, while allowing it for an owner MBean.


In the rest of this topic, we will see how to handle the roles, relation types and relation instances through the relation service. We will use the names and relations presented in the programming example. First of all, we instantiate the relation service as we would any other MBean and register it in our agent's MBean server.


Example 13–1 Relation Service MBean

// Build ObjectName of RelationService
	//        
String relServClassName = RelationService.class.getName();
ObjectName relServObjName = createMBeanName(relServClassName, 1);

[...]

Object[] params = new Object[1];
params[0] = new Boolean(true);
String[] signature = new String[1];
signature[0] = "boolean";

server.createMBean(theRelServClassName, theRelServObjName,
                   params, signature);

The relation service exposes an attribute called Active that is false until its MBean is registered with the MBean server. All of the operations that handle relation or role MBeans, either directly or indirectly, will throw an exception when the service is not active.


13.1.1 Defining Role Information

Before we can create relation instances, we need a relation type, and before we can define a relation type, we need to represent the information about its roles. This information includes:

The name can be any string that is manipulated by the Java String class. It will be used to retrieve the corresponding MBeans when accessing a relation instance. The multiplicity is limited to the range between the minimum and maximum number of MBeans required for this role to fulfilled. The read-write permissions apply to all of the MBeans in the role, since the value of a role is read or written as a complete list.

The role information is represented by the RoleInfo class. In our example, we define two different roles.


Example 13–2 Instantiating RoleInfo Objects

// Define two roles in an array
// - container: SimpleStandard class/read-write access/multiplicity: 1..1
// - contained: SimpleStandard class/read-write access/multiplicity: 0..n

RoleInfo[] roleInfoArray = new RoleInfo[2];
String role1Name = "container";
roleInfoArray[0] =
    createRoleInfo( role1Name, "SimpleStandard",
                  true, true,
                  1, 1,
                  null);

String role2Name = "contained";
roleInfoArray[1] =
    createRoleInfo( role2Name, "SimpleStandard",
                  true, true,
                  0, -1,
                  null);

We build an array of RoleInfo objects that is intended to define a relation type, so it needs to define a valid set of roles. All role names must be unique within the array, and none of the array's elements can be null. Also, the minimum and maximum cardinalities must define a range of at least one integer.

13.1.2 Defining Relation Types

We define a relation type in the relation service by associating a name for the relation type with a non-empty array of RoleInfo objects. These are the parameters to the service's createRelationType method that we call through the MBean server.


Example 13–3 Defining a Relation Type

try {
    String relTypeName = "myRelationType";

    Object[] params = new Object[2];
    params[0] = theRelTypeName;
    params[1] = theRoleInfoArray;
    String[] signature = new String[2];
    signature[0] = "java.lang.String";
    // get the string representing the "RoleInfo[]" object
    try {
         signature[1] =
                   (theRoleInfoArray.getClass()).getName();
         } catch (Exception exc) {
             throw exc;
         }
         server.invoke(theRelServObjName,
                         "createRelationType",
                         params,
                         signature);
    } catch (Exception e) {
         echo("\tCould not create the relation type " 
              + relTypeName);
         printException(e);
    }
}

The relation type name given by the calling process must be unique among all relation type names already created in the relation service. Relations will refer to this name to define their type, and the service will verify that the roles of the relation match the role information in this type.

The relation service provides methods for managing the list of relation types it stores. The removeRelationType removes the type's definition from the relation service and also removes all relation instances of this type. This mechanism is further covered in 13.2.3 Maintaining Consistency.

Other methods give the list of all currently defined relation types or the role information associated with a given type. Role information is always obtained through the name of the relation type where it is defined. Here we show the subroutine that our example uses to print out all role and type information.


Example 13–4 Retrieving Relation Types and Role Information

try {
    echo("\n-> Retrieve all relation types");
    Object[] params1 = new Object[0];
    String[] signature1 = new String[0];
    ArrayList relTypeNameList = (ArrayList)
    (server.invoke( relServObjName, "getAllRelationTypeNames",
                    params1, signature1));

    for (Iterator relTypeNameIter = relTypeNameList.iterator();
         relTypeNameIter.hasNext(); ) {
        String currRelTypeName = (String)(relTypeNameIter.next());
        echo("\n-> Print role info for relation type " +
             currRelTypeName);
        Object[] params2 = new Object[1];
        params2[0] = currRelTypeName;
        String[] signature2 = new String[1];
        signature2[0] = "java.lang.String";
        ArrayList roleInfoList = (ArrayList)
            (server.invoke( relServObjName,"getRoleInfos",
                            params2, signature2));
        printList(roleInfoList);
    }
} catch (Exception e) {
    echo("\tCould not browse the relation types");
    printException(e);
}

13.1.3 Creating Relations

Now that we have defined a relation type, we can use it as a template for creating a relation. A relation is a set of roles that fulfills all of the role information of the relation type. The Role object contains a role name and value which is the list of MBeans that fulfills the role. The RoleList object contains the set of roles used when setting a relation or getting its role values.

In order to create a relation we must provide a set of roles whose values will initialize the relation correctly. In our example we use an existing SimpleStandard MBean in each role that we have defined. Their object names are added to the value list for each role. Then each Role object is added to the role list.


Example 13–5 Initializing Role Objects and Creating a Relation

[...] // define object names and create SimpleStandard MBeans

// Instantiate the roles using the object names of the MBeans
ArrayList role1Value = new ArrayList();
role1Value.add(mbeanObjectName1);
Role role1 = new Role(role1Name, role1Value);

ArrayList role2Value = new ArrayList();
role2Value.add(mbeanObjectName2);
Role role2 = new Role(role2Name, role2Value);

RoleList roleList1 = new RoleList();
roleList1.add(role1);
roleList1.add(role2);

String relId1 = relTypeName + "_internal_1";

try {
    Object[] params = new Object[3];
    params[0] = theRelId1;
    params[1] = theRelTypeName;
    params[2] = roleList1;
    String[] signature = new String[3];
    signature[0] = "java.lang.String";
    signature[1] = "java.lang.String";
    signature[2] = "javax.management.relation.RoleList";
    server.invoke(theRelServObjName, "createRelation",
                  params, signature);
} catch(Exception e) {
    echo("\tCould not create the relation " + RelId1);
    printException(e);
}

The createRelation method will raise an exception if the provided roles do not fulfill the specified relation type. You can omit Role objects for roles that allow a cardinality of 0; their values will be initialized with an empty list of object names. The relation service will check all provided object names to ensure they are registered with the MBean server. Also, the relation identifier name is a string that must be unique among all relations in the service.

The corresponding removeRelation method is also exposed for management operations. It is also called internally to keep all relations coherent, as described in 13.2.3 Maintaining Consistency. In both cases, removing a relation means that you can no longer access it through the relation service, and the isRelation operation will return false when given its relation identifier. Also, its participating MBeans will no longer be associated through the roles in which they belonged. The MBeans continue to exist unaltered otherwise and can continue to participate in other relations.

13.2 Operations of the Relation Service

In addition to the creation and removal methods for relation types and instances, the relation service provides operations for finding related MBeans, determining the role of a given MBean, and accessing the MBeans of a given role or relation.

13.2.1 Query Operations

Once relations have been defined, the relation service allows you to do searches based on the association of objects that the relations represent. The following operations perform queries on the relations:

13.2.2 Accessing Roles

Once you have a relation identifier, you will probably want to access its role values. The relation service provides getters and setters for roles, as well as bulk operations to get or set several or all roles in a relation. Remember that the value of a role is a list of MBeans, and a role is identified by a name string.

Input parameters to setter operations are the same Role and RoleList classes that are used to create relations. Bulk getters and setters return a RoleResult object that contains separate lists for successful and failed individual role operations.

Inside a role result, the list of roles and values that were successfully accessed are given in a RoleList instance. The information about roles that could not be read or written is returned in a RoleUnresolvedList. This list contains RoleUnresolved objects that name the role that could not be accessed and an error code explaining the reason, such as an invalid role name or the wrong cardinality for a role. The error codes are defined as static final fields in the RoleStatus class.

13.2.3 Maintaining Consistency

All relation service operations that set the role of a relation always verify that the role value fulfills its definition in the relation type. An incorrect MBean type, the wrong cardinality of the role, or an unknown role name will prevent that role from being modified, guaranteeing that the relation always fulfills it relation type.

As we shall see in 13.3 Objects Representing Relations, relations and relation types can also be objects that are external to the relation service. In this case, they are responsible for maintaining their own role-consistency. The relation service MBean exposes methods to assist these objects in verifying that their roles conform to their defined relation type. In all cases of an external implementation of a relation, the object designer is responsible for ensuring its coherence.

Removing a relation type can cause existing relations to cease to have a defining type. The policy of the removeRelationType operation is to assume that the caller is aware of existing relations of the given type. Instead of forbidding the operation, this method removes the relations that were defined by the given type. It is the designer's responsibility to first call the findRelationsOfType operation to determine if any existing relations will be affected.

MBeans participating in a relation can be removed from the MBean server by some other management operation, thereby modifying the cardinality of a role where they were referenced. In order to maintain consistency in this case, the relation service must remove all relations in which a role no longer has the cardinality defined in its relation type. The process of determining invalid relations and removing them is called purging.

The relation service listens for unregistration notifications of the MBean server delegate, and will need to purge its MBeans whenever one is received. It must determine if the MBean removed was involved in any relations, and if so, whether its removal violates the cardinality of each role where it appears. The relation service exposes the boolean PurgeFlag attribute that the programmer must set to determine whether purges are done automatically or not.

When the purge flag is true, the relation service will purge its data immediately after every unregistration notification. However, the purge operation can be resource intensive for large sets of relation data. In this case the managing application can set the purge flag to false and only call the purgeRelations operation to purge the data when needed.

For example, if there are many MBean unregistrations and few relation operations, it might make sense to only purge the relation service manually before each operation. Or the automatic purge flag can be temporarily set to false while executing time-critical operations that need to remove MBeans, but that will not access the relation service.

There are two possible consequences of an unpurged relation service. Roles in a relation can reference object names that no longer have an associated MBean. Or worse, the object name might have been reassigned to another MBean, leading to a totally incoherent state. The designer of the management solution is responsible for setting the purge policy so that operations will always access consistent relation values.

13.2.4 Relation Service Notifications

The relation service is a notification broadcaster that notifies listeners of events affecting relations. It sends RelationNotification objects in the following cases:

There are three equivalent notification types for events affecting relations defined as external MBeans (see 13.3 Objects Representing Relations). In addition to the role and relation information, these notifications contain the object name of this MBean.

13.3 Objects Representing Relations

When creating relation types and instances, their representations are handled internally and only accessed through the interface of the relation service. However, the service also allows you to create external objects that represent relations, and then add them under the service's control to access them through its operations.

One advantage of representing relation types and instances as classes is that they can perform their initialization in the class constructor. Applications can then instantiate the classes and add them to the relation service directly, without needing to code for their creation. The relation type classes can be downloaded to agent applications for use throughout the management architecture.

Another advantage is that relations are represented as MBeans and must be registered in the MBean server. This means that management applications can get role information directly from the relation MBean instead of going through the relation service.

The main advantage of an external relation class is that it can be extended to add properties and methods to a relation. They can be accessible through attributes and operations in the relation MBean so that they are also exposed for management. These extensions can represent more complex relations and allow more flexible management architectures.

With the power of manipulating relations comes the responsibility of maintaining the relation model. A relation MBean can expose an operation for adding an object name to a role, but its implementation must first ensure that the new role value will conform to the relation's type. Then, it must also instruct the relation service to send a role update notification. The relation service MBean exposes methods for maintaining consistency and performing required operations. It is the programmer's responsibility to call the relation service when necessary in order to maintain the consistency of its relation data.

13.3.1 RelationTypeSupport Class

A class must implement the RelationType interface in order to be considered a representation of a relation type. The methods of this interface are used to access the role information that makes up a relation type. Since relation types are immutable within the relation service, there are no methods for modifying the role information.

The RelationTypeSupport class is the implementation of this interface that is used internally by the relation service to represent a relation type. By extending this class, you can quickly write new relation type classes with all the required functionality. The class has a method for adding roles to the information that is exposed; this method can be called by the class constructor to initialize all roles. Our simple example does just this, and there is little other functionality that can be added to a relation type object.


Example 13–6 Extending the RelationTypeSupport Class

import javax.management.relation.*;

public class SimpleRelationType extends RelationTypeSupport {

    // Constructor
    public SimpleRelationType(String theRelTypeName) {

        super(theRelTypeName);

        // Defines the information for two roles
        // - primary: SimpleStandard class/read-write/cardinality=2
        // - secondary: SimpleStandard class/read-only/cardinality=2
        try {
            RoleInfo primaryRoleInfo =
                new RoleInfo("primary", "SimpleStandard",
                             true, true,
                             2, 2,
                             "Primary description");
            addRoleInfo(primaryRoleInfo);

            RoleInfo secondaryRoleInfo =
                new RoleInfo("secondary", "SimpleStandard",
                             true, false,
                             2, 2,
                             "Secondary description");
            addRoleInfo(secondaryRoleInfo);
        } catch (Exception exc) {
            throw new RuntimeException(exc.getMessage());
        }
    }
}

We now use our class to instantiate an object representing a relation type. We then call the relation service's addRelationType operation to make this type available in the relation service. Thereafter, it is manipulated through the service's operations and there is no way to distinguish it from other relation types that have been defined.


Example 13–7 Adding an Externally Defined Relation Type

String usrRelTypeName = "SimpleRelationType";
SimpleRelationType usrRelType =
    new SimpleRelationType("SimpleRelationType");
try {
    Object[] params = new Object[1];
    params[0] = usrRelType;
    String[] signature = new String[1];
    signature[0] = "javax.management.relation.RelationType";
    server.invoke( relServObjName, "addRelationType",
                   params, signature);
} catch(Exception e) {
    echo("\tCannot add user relation type");
    printException(e);
}

The role information defined by a relation type should never change once the type has been added to the relation service. This is why the RelationTypeSupport class is not an MBean: it would make no sense to manage it remotely. All of the information about its roles is available remotely through the relation service MBean.

13.3.2 RelationSupport Class

The external class representation of a relation instance must implement the Relation interface so that it can be handled by the relation service. The RelationSupport class is the implementation provided which is also used internally by the service.

The methods of the relation interface expose all of the information needed to operate on a relation instance: the getters and setters for roles and the defining relation type. Because the relation support class must represent any possible relation type, it has a constructor that takes a relation type and a role list, and it uses a generic mechanism internally to represent any roles.

You could implement a simpler relation class that implements a specific relation type, in which case it would know and initialize its own roles. The class could also interact with the relation service to create its specific relation type before adding itself as a relation.

However, the simplest way to define a relation object is to extend the RelationSupport class and provide any additional functionality you require. In doing so, you can rely on the relation support class's own methods for getting and setting roles, thereby taking advantage of their built-in consistency mechanisms.


Example 13–8 Extending the RelationSupport Class

import javax.management.ObjectName;
import javax.management.relation.*;

public class SimpleRelation extends RelationSupport
    implements SimpleRelationMBean {

    // Constructor
    public SimpleRelation(String theRelId,
                          ObjectName theRelServiceName,
                          String theRelTypeName,
                          RoleList theRoleList)
        throws InvalidRoleValueException,
               IllegalArgumentException {

        super(theRelId, theRelServiceName, theRelTypeName, theRoleList);
    }

    [...] // Implementation of the SimpleRelationMBean interface
}

Our MBean's SimpleRelationMBean interface extends the RelationSupportMBean in order to expose its operations for management. In order to represent a relation, the class must be an MBean registered in the MBean server. This allows the relation service to rely on unregistration notifications in order to find out if the object name is no longer valid.

When instantiating our SimpleRelation MBean, we use the relation type defined in Example 13–3. We also reuse the role list from Example 13–5 that contains a single MBean in each of two roles. Before adding the relation to the relation service, we must create it as an MBean in the MBean server. We then call the addRelation operation of the relation service with our relation's object name.


Example 13–9 Creating an External Relation MBean

// Using relTypeName="myRelationType"
// and roleList1={container,contained}

String relMBeanClassName = "SimpleRelation";
String relId2 = relTypeName + "_relMBean_2";
ObjectName relMBeanObjName1 = createMBeanName(relMBeanClassName, 2);
createRelationMBean(relMBeanObjName1,
                    relMBeanClassName,
                    relId2,
                    relTypeName,
                    roleList1,
                    relServObjName);
try {
    Object[] params1 = new Object[4];
    params1[0] = theRelId2;
    params1[1] = theRelServObjName;
    params1[2] = theRelTypeName;
    params1[3] = roleList1;
    String[] signature1 = new String[4];
    signature1[0] = "java.lang.String";
    signature1[1] = "javax.management.ObjectName";
    signature1[2] = "java.lang.String";
    signature1[3] = "javax.management.relation.RoleList";
    server.createMBean(theRelMBeanClassName, theRelMBeanObjName,
                       params1, signature1);

    addRelation(theRelMBeanObjName, theRelServObjName);

} catch(Exception e) {
    echo("\t Could not create relation MBean for relation " + relId2);
    printException(e);
}

// Add our new MBean as a relation to the relation service
try {
    Object[] params2 = new Object[1];
    params2[0] = theRelMBeanObjName;
    String[] signature2 = new String[1];
    signature2[0] = "javax.management.ObjectName";
    server.invoke(theRelServObjName, "addRelation",
                  params2, signature2);
} catch(Exception e) {
    echo("\t Could not add relation MBean " + relMBeanObjName);
    printException(e);
}

After a relation instance is added to the relation service, it can be accessed normally like other relations. Management operations can either operate on the MBean directly or access it through its identifier in the relation service. Two methods of the service are specific to external relation MBeans:

In our example, our MBean does not expose any functionality that interacts with the relation service. It relies fully on the support class and only adds the implementation of its own simple MBean interface. In a more realistic example, the MBean would expose attributes or operations related to its role values.

For example, Personal Library could be a unary relation type containing the Books role. We could then design an Owner MBean to be a relation of this type. In addition to exposing attributes about the owner, the MBean would give the number of books in the library, return an alphabetized list of book titles, and provide an operation for selling a book. All of these would need to access the role list, and the sale operation would need to modify the role to remove a book MBean.

All of these operations would need to keep the consistency of the relation. To do this, the relation service exposes several methods that relation MBeans must call:

13.4 Running the Relation Service Example

The examplesDir/current/Relation directory contains all of the files for the agent application and the associated MBeans.

To Run the Relation Service Example
  1. Compile all files in this directory with the javac command. For example, on the Solaris platform with the Korn shell, type:


    $ cd examplesDir/current/Relation/
    $ javac -classpath classpath *.java
    
  2. Start the relation service application with the following command. Be sure that the classes for the SimpleRelationType and SimpleRelation MBean can be found in its classpath.


    $ java -classpath classpath RelationAgent
    

    When started, the application first creates the RelationService MBean and then begins its long demonstration of operations on roles, types and relations. The output of each step is separated by a horizontal line to detect its starting point in the scrolling display.

  3. Press Enter to step through the example when the application pauses, or press Control-C at any time to exit.