SourceForge.net Logo

openMDX Example Lab Tutorial

Version 1.16.4



www.openmdx.org



Table of Contents

1 Preface 6

1.1 Who this book is for 6

1.2 About the lab examples 6

1.3 How to build and run the labs using Eclipse 6

1.4 Tips, Warnings, etc. 7

2 The Lab model 8

2.1 How to create a UML class diagram for an openMDX based application? 9

3 JMI Mapping Overview 11

3.1 Package Objects and Package Creation 13

3.2 Class Proxy Objects 13

3.3 Instance Objects 14

3.4 Association Objects 14

3.5 JMI Mapping – Package 15

3.6 JMI Mapping – Class 17

3.7 JMI Mapping – Instance 18

3.8 JMI Mapping – References 19

4 Getting started with a JMI client 20

4.1 How to retrieve a package? 20

4.2 How to create instance-level objects? 21

4.3 How to retrieve the openMDX XRI for a given instance? 21

4.4 How to set/retrieve attribute values? 22

4.5 How to set/retrieve referenced objects? 23

4.6 How to invoke an operation? 24

4.7 How do clients interact with application plugins? 24

4.8 Lab Example 26

5 Getting started with Queries 27

5.1 How to filter the result set? 27

5.2 How to specify filter constraints? 28

5.2.1 How to choose filter operators? 28

5.2.2 How to choose the appropriate quantor for a filter constraint? 29

5.2.3 Example filter constraints 29

5.2.4 How to specify filter constraints for null values? 31

5.2.5 How to combine multiple query constraints? 31

5.2.6 Ordering the result 32

5.3 Lab Example 32

6 Getting started with Application Plugins 33

6.1 Overview 33

6.2 How to create an application plugin? 34

6.2.1 How to write a plugin class? 34

6.2.2 How to configure a plugin? 34

6.2.3 How to add business logic? 37

6.2.3.1 Signature of implementation class 37

6.2.3.2 Sample implementation 38

6.2.4 Lab Example 39

7 Plugin context 40

7.1 How to pass a context object? 40

7.2 How to access the context object? 41

8 Getting started with a Reflective JMI client 42

8.1 How to create an instance? 42

8.2 How to set/retrieve attribute values? 43

8.3 How to set/retrieve referenced objects? 44

8.4 How to invoke an operation? 46

8.5 Lab Example 46

9 Deployment 47

9.1 General Overview 47

9.2 How to setup the gateway? 48

9.3 How to setup a provider? 49

9.4 Deployment on JBoss Application Server 49

9.4.1 Prerequisites 49

9.4.2 Steps to run openMDX EJBs on JBoss 49

9.4.3 Preparing JBoss 50

9.4.4 How to deploy EARs? 51

9.4.5 How to write a JBoss client? 51

9.4.6 How to run the openMDX client? 51

9.4.7 How to troubleshoot openMDX EJBs? 51

10 Summary 53

11 Bibliography 54



List of Figures

Figure 1: The lab model org:openmdx:example:lab1 8

Figure 2: JMI Mapping Overview 11

Figure 3: Mapping a model to Java files. 15

Figure 4: Mapping of a model to a JMI package. 15

Figure 5: Package-, class- and instance-level JMI objects and their dependencies. 16

Figure 6: Mapping a model to a class-level object. 17

Figure 7: Mapping of a model to an instance-level object. 18

Figure 8: Mapping of a model to reference features of an instance-level object. 19

Figure 9: openMDX client / server model. 24

Figure 10: A provider with four plugins. 33

Figure 11: Gateway and provider setup. 48



List of Listings

Listing 1: Initializing the JMI package. 16

Listing 2: Setup initial root package and retrieve desired package. 20

Listing 3: Create an instance of class Person. 21

Listing 4: Retrieve the openMDX URI of a given object. 21

Listing 5: Retrieve the object for a given openMDX URI. 22

Listing 6: Set attribute values. 22

Listing 7: Get attribute values. 22

Listing 9: Add a group instance to the reference group of a Person instance. 23

Listing 11: Invoke an operation. 24

Listing 12: Create a Person instance, set some attributes and make it permanent in a UnitOfWork. 26

Listing 13: Simple address filter example. 27

Listing 14: Example constraints for non-optional single-valued attributes with filter operator IS_IN. 29

Listing 15: Example constraints for optional single-valued attributes with filter operator IS_IN. 30

Listing 16: Example constraints for multi-valued attributes with filter operator IS_IN. 30

Listing 17: Example constraints for non-optional single-valued attributes with filter operator IS_GREATER or IS_LESS_OR_EQUAL. 30

Listing 18: Example constraint for non-optional single-valued attributes with filter operator IS_BETWEEN. 30

Listing 19: Retrieve objects for which no attribute value has been set. 31

Listing 20: Retrieve objects for which an attribute value has been set. 31

Listing 21: Combining multiple filter criterias. 31

Listing 22: OR relation with filter operator IS_IN. 32

Listing 23: Specify sort order. 32

Listing 24: JMI Plugin implementation. 34

Listing 25: KERNEL/APPLICATION Example. 35

Listing 26: KERNEL/modelPackage Example. 35

Listing 27: KERNEL/packageImpl Example. 36

Listing 28: Signature of implementation class. 37

Listing 29: Signature of class PersonImpl. 38

Listing 30: Implementation of Person. 38

Listing 31: Pass the context object. 40

Listing 32: Access context object from within JMI object implementation. 41

Listing 33: Create a new instance of class Person. 42

Listing 34: Set attribute values. 43

Listing 35: Get attribute values. 44

Listing 36: Retrieve group instances of the reference group of a Person instance. 44

Listing 37: Add a group instance to the reference group of a Person instance. 45

Listing 38: Remove a group instance from the reference group of a Person instance. 45

Listing 39: Call an operation. 46

Listing 40: Contents of file server.log.properties. 50

Listing 41: Modify JBoss startup script to include the following Java system properties. 50



1 Preface

This tutorial gives an introduction to programming with openMDX. Based on a simple model the tutorial explains:

  • How to create openMDX clients using the JMI binding

  • How to add business logic by creating application plugins

  • How to deploy an openMDX application

1.1 Who this book is for

This tutorial is intended for programmers interested in developing and deploying openMDX applications.

1.2 About the lab examples

To understand the examples, you need a good knowledge of the Java programming language. The deployment of openMDX applications requires basic knowledge about EJB deployment. You can download the labs from www.openmdx.org. The README of the distribution explains how to install and setup the labs for Ant and Eclipse.

To run the labs follow these steps:

  1. Go to the openmdx-example/lab directory.

  2. Execute ant -f run.xml. This will display the available Ant targets.

  3. Execute ant -f run.xml <myLab> to run the desired lab, e.g. ant -f run.xml clientTypedSolution.

1.3 How to build and run the labs using Eclipse

    Before you can set up the labs for Eclipse you must first install and build the labs with Ant as explained in the README of the openMDX/Example Lab distribution. Then you are ready to create an Eclipse project:

    Create a new Java project in Eclipse with the "New Java Project" wizard; use the project settings listed below:

  1. Create the project in directory openmdx-example/lab.

  2. Set openmdx-example/lab/build/eclipse as default output folder instead of using the proposed default output folder.

  3. Eclipse will automatically recognize the Java source folders openmdx-example/lab/src and openmdx-example/lab/build/ant/src and add them to the build path. Also add openmdx-example/lab/resource to the source path.

  4. Eclipse will automatically recognize all the jar libraries in the openmdx-example/lab/lib directory and add them to the build path. Do not change these settings.

That's all! From now on you can complete, build and run the labs in Eclipse. For all the labs you will find two Eclipse launch configurations (these are automatically detected by Eclipse).

Please note that the following steps cannot be done in Eclipse:

  • In case you want to create an EAR, e.g. for the deployment of the EJBs described in chapter Enterprise JavaBeans™ Deployment, you have to execute ant build. You will find the created EAR in the openmdx-example/jre-1.5/lab/deployment-unit directory afterwards.

  • Whenever you change the model, the MOF mappings (JMI, XMI, etc.) must be regenerated. You can do this with ant generate. Do not forget to refresh your project inside Eclipse.

1.4 Tips, Warnings, etc.

We make use the following pictrograms:

Information provided as a “Tip” might be helpful for various reasons: time savings, risk reduction, etc.

You should carefully read information marked with “Important”. Ignoring such information is typically not a good idea.

Warnings should not be ignored (risk of data loss, etc.)



2 The Lab model

Figure 1 shows the UML model org::openmdx::example::lab1. All lab examples are based on this model.


Figure 1: The lab model org:openmdx:example:lab1










The model defines the business classes Person, Group and Address. The class Segment inherits from org::openmdx::base::Segment and serves as container class for all other classes. A Segment instance contains Persons and Groups whereas a Person contains Addresses. Address is an abstract class with concrete subclasses EmailAddress and PostalAddress. Furthermore the reference group allows to assign persons to groups. The reference parentGroup allows to manage nested Groups.

2.1 How to create a UML class diagram for an openMDX based application?

openMDX supports the UML diagrams as shown in the table below:

Structure Diagrams

- Class Diagram
- Object Diagram
- Component Diagram
- Composite Structure Diagram
- Package Diagram
- Deployment Diagram



MOF 1.4 compliant models only
---
---
---
---
---

Behaviour Diagrams

- Use Case Diagram
- Activity Diagram
- State Machine Diagram



Could be supported by a plugin executing activity diagrams and state machines. Plugin is not implemented yet. The recently adopted Business Process Modeling Specification (dtc/06-02-01) seems to be more promising.

Interaction Diagrams

- Sequence Diagram
- Communication Diagram
- Timing Diagram
- Interaction Overview Diagram



---
---
---
---

As shown in the table below openMDX currently supports only MOF 1.4 compliant class diagrams. The class diagrams are used to apply MOF mappings such as JMI and XMI (more on this later). MOF 1.4 compliant class diagrams define the model elements as shown in the table below:

UML Element

MOF Element

MOF Element

- Model «metamodel»
- ElementImport
- Class
- Attribute
- Attribute «reference»
- Operation
- Parameter
- Exception
- Attribute (within Exception)
- Association
- AssociationEnd
- DataType
- DataValue
- Constraint
- Generalization
- TaggedValue
- Qualifier

- Package
- Import
- Class
- Attribute
- Reference
- Operation
- Parameter
- Exception
- Parameter
- Association
- AssociationEnd
- DataType
- Constant
- Constraint
- Generalizes
- Tag
---

?
?
?
?
?
?
?
?
?
?
?
?
---
---
?
?
?

Because the set of model elements required by MOF 1.4 is very limited any UML modeling tool supporting the corresponding UML elements can be used to create an openMDX class diagram. At least a UML 1.4 compliant UML modeling does the job. Below is a list of modeling tools which are supported by the current version of openMDX:

  • Use MagicDraw UML (from No Magic Inc.) to create UML class diagrams and export the diagrams to XMI. Afterwards you can use a command line utility (provided by openMDX) to read the XMI file and generate all the information needed by the openMDX platform. Please note that the Community Edition of MagicDraw UML is offered free of charge and is basically all you need.

  • Use Poseidon for UML (from Gentleware AG) to create UML class diagrams and export the diagrams to XMI. Afterwards you can use a command line utility (provided by openMDX) to read the XMI file and generate all the information needed by the openMDX platform. Please note that the Community Edition of Poseidon for UML is offered free of charge and is basically all you need. If you are using the Standard Edition or above you can also use the openMDX plugin for Poseidon.

  • Use Rational Software Modeler RSM 6 (from IBM) to create UML class diagrams. Use a command line utility (provided by openMDX) to read the EMX files and generate all the information needed by the openMDX platform.

  • Use Together (from Borland Software Corp.) to create UML class diagrams. Generate all the information needed by the openMDX platform directly from within Together by means of a dedicated plugin (provided by openMDX). Please note that the plugin can be used in batch mode.

3 JMI Mapping Overview

In this first part of the tutorial we focus on the MOF-to-Java mapping (JMI) mapping. The JMI mapping the most 'natural' way of programming with openMDX. The class diagrams are mapped to Java interfaces. The interfaces are plain Java interfaces, i.e. they do not contain 'distribution' logic such as EJB or CORBA interfaces do. The JMI mapping is implemented in openMDX as a layer sitting on top of the generic object layer. The JMI layer is also called JMI accessor. The JMI accessor is an implementation of the [17] specification. The JMI accessor is layered on top of the openMDX generic accessor as shown in Figure 5. The JMI externalizer of the MOF repository supports the generation of JMI interfaces and implementations as defined by the JMI specification.

Frame2

The JMI specification defines the purpose and scope of JMI:

"The Java™ Metadata Interface (JMI) Specification defines a dynamic, platform-neutral infrastructure that enables the creation, storage, access, discovery, and exchange of metadata. JMI is based on the Meta Object Facility (MOF) specification from the Object Management Group (OMG), an industry-endorsed standard for metadata management.

The MOF standard provides an open-ended information modeling capability, and consists of a base set of metamodeling constructs used to describe technologies and application domains, and a mapping of those constructs to CORBA IDL (Interface Definition Language) for automatically generating model-specific APIs. The MOF also defines a reflective programming capability that allows for applications to query a model at run time to determine the structure and semantics of the modeled system. JMI defines a Java mapping for the MOF.

As the Java language mapping to MOF, JMI provides a common Java programming model for metadata access for the Java platform. JMI provides a natural and easy-to-use mapping from a MOF-compliant data abstraction (usually defined in UML) to the Java programming language. Using JMI, applications and tools which specify their metamodels using MOF-compliant Unified Modeling Language (UML) can have the Java interfaces to the models automatically generated.

Further, metamodel and metadata interchange via XML is enabled by JMI's use of the XML Metadata Interchange (XMI) specification, an XML-based mechanism for interchanging metamodel information among applications. Java applications can create, update, delete, and retrieve information contained in a JMI compliant metadata service. The flexibility and extensibility of the MOF allows JMI to be used in a wide range of usage scenarios."

As described the scope of JMI is metadata interfaces. However, application-models (Level M1) modeled in UML are mapped according to the 'UML Profile for MOF' to MOF-compliant models. Having a MOF-compliant application-level model allows to apply the MOF-to-JMI mapping.

The MOF-to-JMI mapping has been extended by the following features:

  • Support for queries. MOF allows navigation between instances of classes using references. References are mapped in JMI as get-operations returning collections. This allows the application to iterate on the object set and retrieve all referenced objects. However, in many cases it is desirable to retrieve only a subset of referenced objects. All collections returned by the JMI classes implement the interface org.openmdx.compatibility.base.collection.Container interface which supports filtering of an object collection.

  • Support for qualifiers. The standard JMI mapping does not allow to retrieve objects by application-defined unique identifiers. Objects can only be retrieved by an opaque MOF identifier which is not usable for application-level programming. However, application-level unique identifiers are supported with UML qualifiers. openMDX extends the 'UML Profile for MOF' by 'UML qualifiers' and adds a new model element to MOF. The qualifier is supported in the JMI mapping and allows to get and create objects using their qualifiers. The JMI mapping is extended by void add{feature}(qualifier, object), void remove{feature}(qualifier), RefObject get{feature}(qualifier) operations.

  • Support for object lifecycle. As already mentioned the JMI accessor is mapped to the openMDX generic accessor which offers JDO-like distributed object management and defines an object lifecycle. In order to support this object lifecyle all JMI objects returned by the JMI accessor implement the org.openmdx.base.accessor.jmi.RefObject_1_0 interface which in turn extends the javax.jmi.reflect.RefObject interface. This way all openMDX JMI objects are JMI compliant but still offers object lifecycle management functionality offered by openMDX.

The extension of JMI by these features allows to use JMI not only for metamodel management but also for simple to medium up to enterprise-level applications. The JMI accessors are a thin layer on top of the openMDX generic accessor classes and imply only little performance impact compared to the generic accessor or to platform-specific, native EJB or CORBA programming.

The JMI APIs has a three-layered approach to manage objects:

  • Packages play the role of a class directory, i.e. Packages are class-level object factories.

  • Class objects are the factories (or homes) of instance-level objects.

  • Instance objects are the application-level objects.

3.1 Package Objects and Package Creation

A package object is little more than a 'directory' of operations that give access to the classes described by the model. The outer most package extent (a.k.a. outer most extent) represents the “root” package. All other objects (i.e., instance objects, class proxies, associations, and (nested) package objects) are contained within some outer most extent.

3.2 Class Proxy Objects

A class proxy object serves a number of purposes:

  • It is a factory object for producing instance objects within the Package extent.

  • It is the intrinsic container for instance objects.

  • It holds the state of any classifier-scoped attributes for the class.

  • It provides operations corresponding to classifier-scoped operations.

The interface of a class proxy object provides operations for accessing and updating the classifier scoped attribute state. The interface also provides factory operations that allows the client to create instance objects.





3.3 Instance Objects

An instance object holds the state corresponding to the instance-scoped attributes, and any other “hidden” state implied by the class specification. Generally speaking, many instance objects can exist within a given package object.

Instance objects are always tied to a class proxy object. The class proxy provides a factory operation for creating instance objects. When an instance object is created, it is automatically added to the class proxy container. An instance is removed from the container when it is destroyed.

The interface for an instance object provides:

  • Operations to access and update the instance-scoped and classifier-scoped attributes.

  • Operations corresponding to instance-scoped and classifier-scoped operations.

  • Operations to access and update associations via reference.

3.4 Association Objects

An association object holds a collection of links (i.e. the link set) corresponding to an association defined in the metamodel. The association object is a 'static' container object (similar to a class proxy object) that is contained by a package object. Its interface provides:

  • Operations for querying the link set.

  • Operations for adding, modifying and removing links from the set.

  • An operation that returns the entire link set.

NOTE: Association objects are NOT supported by the openMDX JMI mapping. All assocations between objects must be managed through references.

Figure 3 shows the mapping of a UML model to Java files.

  • The model package {M}is mapped to a Java class called {M}Package.java

  • Each class {C} in the model is mapped to a class-level file called {C}Class.java and an instance-level file called {C}.java


Figure 3: Mapping a model to Java files.




3.5 JMI Mapping – Package

Each model is mapped to a package file as shown in Figure 4. For each class a corresponding getter is created. The returned class is associated with the package and therefore knows the accessor.


Figure 4: Mapping of a model to a JMI package.




A package is created an initialized as shown in Listing 1.

Listing 1: Initializing the JMI package.

RefPackage rootPkg = new RefRootPackage_1(
manager, // manager used for object management (get, create, modify)
null, // impls
null // context
);
lab1Package lab1Pkg = (lab1Package)rootPkg.refPackage("org:openmdx:example:lab1");

The root package has to be initialized only once. Based on the provided manager, the root package creates a basic accessor which is used for all object management (get, create, modify). The root package serves as factory for retrieving model-specific packages which are retrieved by refPackage(). All objects managed by the JMI package (class-level and instance-level objects) delegate to the basic accessor of the root package. This allows a consistent unit of work policy even if multiple accessors delegate to the same manager. As shown in the figure each instance-level JMI object wraps a RefObject which is managed by the basic accessor. Generic objects retrieved from the generic accessor are marshalled (by the root package) to JMI objects, JMI objects forwarded to the generic accessor are marshalled to generic objects. Units of work and transactions are controlled by the manager.

Frame5

3.6 JMI Mapping – Class

Figure 6 shows the mapping of a UML class to a JMI class-level Java class. The class-level class contains various create() operations for instance-level objects. In addition to the standard JMI mapping openMDX supports extend() operations which allow to extend an object of a superclass to an instance of its subclass. This allows to preserve object identity. However all required attributes to perform the extension must be supplied. For a detailed description of the mapping also see [17].


Figure 6: Mapping a model to a class-level object.




3.7 JMI Mapping – Instance

Figure 7 shows the mapping of a UML class to a JMI instance-level Java class. The instance-level class contains set, get, add, remove operations for all attributes depending on their model properties. For a detailed description of the mapping also see [17].


Figure 7: Mapping of a model to an instance-level object.




3.8 JMI Mapping – References

Figure 8 shows the mapping of a UML reference to the JMI instance-level Java class. References are mapped to add, get and remove operations depending on the model properties with the referenced object class as argument. The openMDX mapping supports UML qualifiers which are an extension of the 'UML Profile for MOF'. For a detailed description of the reference mapping also see [17].


Figure 8: Mapping of a model to reference features of an instance-level object.




4 Getting started with a JMI client

The Java Metadata Interface (JMI) provides a natural and easy-to-use mapping from a MOF-compliant data abstraction defined in UML to the Java programming language. Starting from the UML model the Java interfaces to the model can be automatically generated according to the JMI specification.

In this chapter you will see how to write an openMDX client based on the model that has been introduced in Figure 1. Before you start with this chapter please make sure that you have generated the JMI accessors based on the lab model beforehand.

At the end of this chapter you will have the opportunity to practice what you have learnt in this chapter in a lab exercise.

4.1 How to retrieve a package?

In JMI packages play an important part. Each package object provides access to the class proxy objects wihtin its package scope. These class proxy objects are used to create instances (more on class proxy objects in the next section). The root package represents the outermost package extent. All other packages are directly or indirectly nested within the root package. The root package acts as starting point for all top level packages like e.g. org which itself acts as starting point for its nested packages, e.g. openmdx. By means of the refPackage operation a package can retrieve its nested packages. Listing 2 shows how the example package org:openmdx:example:lab1 is retrieved. This example package will be used in the subsequent sections of this chapter.

The JMI specification does not say how an instance of the root package must be obtained and created. In openMDX the root package is created by instantiating the class org.openmdx.base.accessor.jmi.RefRootPackage_1.

Listing 2: Setup initial root package and retrieve desired package.

// setup initial root package based on a given manager instance
RefPackage_1_0 rootPkg = new RefRootPackage_1(
this.manager
);

// get desired package by means of refPackage call on root package
lab1Package lab1Pkg = (lab1Package)rootPkg.refPackage(
"org:openmdx:example:lab1"
);



According to JMI specification the refPackage operation can only retrieve packages that are nested within the current package. This means that to get to a specific package you have to traverse the package hierarchy starting from the root package. E.g. first retrieve the root package, then starting from the root package retrieve the org package, then taking the org package retrieve the openmdx package, …. openMDX simplifies the package lookup. refPackage allows to pass a fully qualified package name. This works for all packages (not only for root packages).

Instead of passing the model name, e.g. org:openmdx:example:lab1, as string parameter it is recommended to use the package class name lab1Package.class.getName(). This has the advantage that if the package lab1 is moved, you will have to adapt the string literal whereas the recommended expression remains the same.



4.2 How to create instance-level objects?

According to JMI, class proxy objects are used to create instance-level objects. Every modeled class is mapped to a class proxy by the JMI mapping. E.g. for the modeled class Person the class proxy object PersonClass is generated. These class proxy objects provide factory operations which allow to create instance-level objects.

In order to create an instance-level object first retrieve the package which contains the desired class proxy. The class proxy can then be used to create instance-level objects. Listing 3 illustrates how to retrieve a class proxy and how to create an instance-level object.

Listing 3: Create an instance of class Person.

// retrieve class proxy for class Person from package
PersonClass personClass = lab1Pkg.getPersonClass();

// use class proxy to create a new Person instance
Person person = personClass.createPerson();



4.3 How to retrieve the openMDX XRI for a given instance?

In openMDX (and MOF) every object (package-, class- and instance-level) has a unique identity. In openMDX this identity is an XRI which can be queried with the JMI operation refMofId.

Listing 4: Retrieve the openMDX URI of a given object.

// retrieve the openMDX URI of a given object
String personUri = person.refMofId()



To retrieve an object for a given openMDX XRI, you use the operation refObject on the root package (or any other package) for this purpose.

Listing 5: Retrieve the object for a given openMDX URI.

// retrieve the object (e.g. a Person instance) for a given openMDX URI
Person person = (Person)rootPkg.refObject(personUri);



The refObject operation is an openMDX extension and therefore not JMI compliant.



4.4 How to set/retrieve attribute values?

According to the JMI specification, each modeled attribute is mapped to corresponding setter/getter accessors in the Java interface. By using these accessors you have a simple, type-safe way to set or retrieve attribute values. Listing 6 shows how to set attribute values.

Listing 6: Set attribute values.

// set single-valued attribute values
// (attribute type org::w3c::string)
person.setGivenName("Hans");
person.setLastName("Muster");
person.setMiddleName("Fritz");
// (attribute type org::w3c::boolean)
person.setMarried(true);
// (attribute type org::w3c::short)
person.setNumberOfChildren((short)2);

// set multi-valued attribute values
// (attribute type org::w3c::string and attribute multiplicity list)
postalAddress.setAddressLine(
new String[] {
"Postfach 99",
"Musterstrasse 999"
}
);



Listing 7 shows how to retrieve the same attribute values.

Listing 7: Get attribute values.

// get single-valued attribute values
// (attribute type org::w3c::string)
String givenName = person.getGivenName();
String lastName = person.getLastName();
String middleName = person.getMiddleName();
// (attribute type org::w3c::boolean)
boolean isMarried = person.isMarried();
// (attribute type org::w3c::short)
short numberOfChildren = person.getNumberOfChildren();

// get multi-valued attribute values
// (attribute type org::w3c::string and attribute multiplicity list)
List addressLines = postalAddress.getAddressLine();



4.5 How to set/retrieve referenced objects?

In compliance with the JMI specification, references are mapped to corresponding setter/getter accessors. The accessors allow a type-safe way

  • to retrieve referenced objects

  • to add new objects to a reference

  • to remove existing objects from a reference

The add and remove operations are only provided if the modeling constraint isFrozen is not set for the corresponding reference.

Listing 8: Retrieve group instances of the reference group of a Person instance.

// retrieve all group instances
Collection groups = person.getGroup();

// retrieve the group instance at the specified index
// only for references stored as attribute
Group group = person.getGroup(index);

// retrieve the address instance for a specified qualifier (priority)
// only for references not stored as attribute
Address address = person.getAddress(priority);

Listing 9: Add a group instance to the reference group of a Person instance.

// add a specified group instance
person.addGroup(group);

// add a specified group instance at the specified index
// only for references stored as attribute
person.addGroup(index, group);

// add a specified address instance for a specified qualifier (priority)
// only for references not stored as attribute
person.addAddress(priority, address);



Listing 10: Remove a group instance from the reference group of a Person instance.

// remove a specified group instance
person.removeGroup(group);

// remove the group instance at the specified index
// only for references stored as attribute
person.removeGroup(index);

// remove the address instance for a specified qualifier (priority)
// only for references not stored as attribute
person.removeAddress(priority);



A remove does not necessarily remove the object itself. The semantics of the remove operation depends on the aggregation (composite, shared, none) of the modeled reference. openMDX defines the following semantic: none removes only the object link but not the object itself; composite removes the object; shared removes the access to the object, the implementation is application-specific.


4.6 How to invoke an operation?

According to the JMI specification, a modeled operation is mapped to an operation in the Java interface. Listing 11 shows an operation invocation. Note that openMDX requires that operation parameters and return types are modeled as structure types. This restriction may be abandoned in future versions of openMDX.

Listing 11: Invoke an operation.

// The operation serialize() on a given person instance,
// this operation takes a single string value as input parameter
// and returns an instance of the modeled parameter structure
// PersonSerializeResult
PersonSerializeResult result = person.serialize("asString");
String serializedForm = result.getResult();

4.7 How do clients interact with application plugins?

In openMDX all application plugin code (business logic code) runs on the server. openMDX implements a service-oriented client / server model as shown in Figure 9.

Frame10

Objects retrieved by the client from the server are cached on the client side. All behaviour (implementation of derived features, operations, etc.) is run on the server side. A round trip to access a feature is made if

  1. the feature is not in the object's (default) fetch set

  2. the feature has not been fetched since commitment of the last unit of work.

Or in more detail: the default roundtrip is that each requested object is returned with its non-derived single valued attributes (including the identities of referenced objects). Plugins can modify this default fetch set by

  • widen or narrow this set of attributes

  • include additional object (e.g. referenced ones) in the reply

In detail, units of work are handled on the client as follows:

  1. Start a unit of work before modifying objects

  2. Perform object modifications such as

  • modify attribute values

  • add and remove objects to references

    The following rules apply when performing operations within a unit of work:

  • All the changes are visible to both query and non-query operations as a corresponding transaction context is associated with a non-optimistic unit of work during its whole duration.

  • The visibility of the changes are as following in case of optimistic units of work:

  • Operations with the modeled query attribute set to true are executed synchronously, i.e. they do not see any changes made during the current unit of work.

  • The excution of operations with the query attribute set to false are postponed to the unit of work's prepare phase, i.e. they see all changes made during the current unit of work and the members of their reply objects must not be accessed until the unit of work has commited.

  • getting and setting attributes, creating instances and creating references are performed on the client side cache (unit of work)

  1. commit or rollback the unit of work

  • In case of an optimistic unit of work the commit starts a new transaction (on the server side) and makes the object modifications persistent. The transaction is then completed. This way all object modifications are either persistet or none in case of a rollback.

  • In case of a transactional unit of work the unit of work is performed within an existing transaction. The commit of the unit of work makes the the object modifications visible to the transaction. The modifications are persistet if the transaction completes successfully.

Listing 12 gives an example.

Listing 12: Create a Person instance, set some attributes and make it permanent in a UnitOfWork.

// In our lab model Person instances are referenced by containment by
// reference "person" originating from a given segment instance.
// Therefore the given segment instance has to be retrieved.
Path segmentPath = new Path(PROVIDER_URI).add("segment").add(SEGMENT_QUALIFIER);
Segment segment = (Segment)rootPkg.refObject(segmentPath.toUri());

// retrieve class proxy for class Person from package
PersonClass personClass = lab1Pkg.getPersonClass();

// begin unit of work
lab1Pkg.refBegin();

// use class proxy to create a new Person instance
Person person = personClass.createPerson();

// set some attribute values
person.setGivenName("Hans");
person.setLastName("Muster");
person.setMiddleName("Fritz");

// adding person instance to segment
segment.addPerson(qualifier, person);

// commit unit of work
lab1Pkg.refCommit();

4.8 Lab Example

Congratulations! You now know everything you need to write a JMI client based on openMDX. Please remember that this is the most recommended way of writing clients.

It is now time to practice. In the package org.openmdx.example.lab1.program.jmi.plugin.work you will find the Java class LabClient that you can complete. E.g. Create new objects, set attribute values, add objects to references, call operations, ...

You can run your client by means of executing ant -f run.xml clientTypedWork.

5 Getting started with Queries

Referenced objects can be retrieved with the corresponding JMI-compliant get<reference name>() : Collection accessor of the instance-level object. In cases where the result set is large (number of objects in the collection) there must be a way to filter the result set. To solve this problem openMDX allows too filter collections by queries. openMDX queries allow to

  • filter a result set

  • sort a result set

Let us have a look at the following example: In the lab model introduced in Figure 2-1 a Segment instance can reference any number of Person objects. The generated JMI reference accessor operation getPerson() returns a collection containing all Person objects. The returned collection is an instance of the openMDX-specific class Container which supports object filtering.

openMDX queries are an extension to the Java Metadata Interface (JMI) specification.

Queries are executed 'server-side', i.e. by providers. A typical provider delegates a query to the persistence plugin which maps the query to an SQL WHERE clause.



5.1 How to filter the result set?

The following steps are required to filter a result set:

  1. create a filter object

  2. specify filter constraints that must be met by the objects of the result set

  3. apply the filter to the result set

The filter classes are generated by the JMI mapping. For every instance-level class a corresponding filter class is generated. These filter classes offer operations to set filter constraints. The following example shows how to specify and apply a filter for the reference address of class Person.

Listing 13: Simple address filter example.

// create an Address filter object
AddressFilter addressFilter = labPkg.createAddressFilter();

// specify filter constraint for attribute usage
addressFilter.thereExistsUsage(FilterOperators.IS_IN, new String[] {"work"});

// apply filter
List result = ((Container)person.getAddress()).toList(addressFilter);

5.2 How to specify filter constraints?

A filter constraint has the following properties:

  • a filter operator

  • a quantor

  • attribute values which the filtered object must match

5.2.1 How to choose filter operators?

Filter operators are used to specify whether an attribute value must be equal, greater, less, … to a given value. openMDX supports the filter operators listed in the table below:

Filter Operator

Description

IS_IN

the attribute value is equal to the given value(s)

IS_NOT_IN

the attribute value is not equal to the given value(s)

IS_GREATER_OR_EQUAL

the attribute value is greater or equal than the given value

IS_GREATER

the attribute value is greater than the given value

IS_LESS_OR_EQUAL

the attribute value is less or equal than the given value

IS_LESS

the attribute value is less than the given value

IS_BETWEEN

the attribute value is between two given values (range)

IS_OUTSIDE

the attribute value is outside of the range of two given values

IS_LIKE

the attribute is like the given value

IS_UNLIKE

the attribute is unlike the given value

SOUNDS_LIKE

the attribute sounds like the given value

SOUNDS_UNLIKE

the attribute sounds unlike the given value

5.2.2 How to choose the appropriate quantor for a filter constraint?

Quantors specify whether a given value must match all attribute values or at least one attribute value. There are two quantors: forAll and thereExists. forAll has the meaning 'match all values', thereExists has the meaning 'match at least one value'.

  • For a non-optional single-valued attribute (i.e. an attribute with multiplicity 1..1) forAll and thereExists have the same meaning. You can either use a forAll quantor or a thereExists quantor.

  • For an optional single-valued attribute (i.e. an attribute with multiplicity 0..1) or a multi-valued attribute (i.e. an attribute with multiplicity 0..n) forAll specifies whether the constraint must be met by all attribute values and thereExists by at least one value.

These attributes can have no value in which case the filter condition is not met at all!

The filter classes contain for every attribute X the operations forAllX and thereExistsX.

5.2.3 Example filter constraints

The following code fragments gives an idea of how to specify filter constraints using different quantors and filter operators.

Listing 14: Example constraints for non-optional single-valued attributes with filter operator IS_IN.

// this constraint is met if the value for the short attribute numberOfChildren equals 2
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNumberOfChildren (FilterOperators.IS_IN, new short[] { 2 });

// this constraint is met if the value for the short attribute numberOfChildren equals 2 or 4
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNumberOfChildren (FilterOperators.IS_IN, new short[] { 2, 4 });

// this constraint is met if the value for the String attribute lastName equals "Muster"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsLastName (FilterOperators.IS_IN, new String[] { "Muster" });

// this constraint is met if the value for the String attribute lastName equals "Muster" or "Meier"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsLastName (FilterOperators.IS_IN, new String[] { "Muster", "Meier" });

// this constraint is met if the value for the boolean attribute isMarried equals true
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllIsMarried (FilterOperators.IS_IN, new boolean[] { true });

Listing 15: Example constraints for optional single-valued attributes with filter operator IS_IN.

// this constraint is met if the value for the String attribute middleName
// is not null and equals "Henry"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsMiddleName(FilterOperators.IS_IN, new String[] { "Henry" });

// this constraint is met if the value for the String attribute middleName
// is not null and equals "Henry" or "James"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsMiddleName(FilterOperators.IS_IN, new String[] { "Henry", "James" });

// this constraint is met if the value for the String attribute middleName
// is either null or equals "Henry"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllMiddleName (FilterOperators.IS_IN, new String[] { "Henry" });

// this constraint is met if the value for the String attribute middleName
// is either null or equals "Henry" or "James"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllMiddleName (FilterOperators.IS_IN, new String[] { "Henry", "James" });

Listing 16: Example constraints for multi-valued attributes with filter operator IS_IN.

// this constraint is met if there are values for the String attribute nickName
// and at least one of the values equals "Bill"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsNickName(FilterOperators.IS_IN, new String[] { "Bill" });

// this constraint is met if there are values for the String attribute nickName
// and at least one of the values equals "Bill" or "Sam"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsNickName(FilterOperators.IS_IN, new String[] { "Bill", "Sam" });

// this constraint is met if either there are no values for the String attribute nickName
// or all the values equal "Bill"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNickName(FilterOperators.IS_IN, new String[] { "Bill" });

// this constraint is met if either there are no values for the String attribute nickName
// or all the values equal "Bill" or "Sam"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNickName(FilterOperators.IS_IN, new String[] { "Bill", "Sam" });

Listing 17: Example constraints for non-optional single-valued attributes with filter operator IS_GREATER or IS_LESS_OR_EQUAL.

// this constraint is met if the value for the short attribute numberOfChildren is greater than 2
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNumberOfChildren (FilterOperators.IS_GREATER, new short[] { 2 });

// this constraint is met if the value for the short attribute numberOfChildren is less or equal than 2
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsNumberOfChildren (FilterOperators.IS_LESS_OR_EQUAL, new short[] { 2 });

Listing 18: Example constraint for non-optional single-valued attributes with filter operator IS_BETWEEN.

// this constraint is met if the value for the short attribute numberOfChildren is between 2 and 4
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNumberOfChildren (FilterOperators.IS_BETWEEN, new short[] { 2, 4 });

5.2.4 How to specify filter constraints for null values?

Null values are only possible for optional single-valued attributes (i.e. attributes with multiplicity 0..1) and multi-valued attributes (i.e. attributes with multiplicity 0..n). The following examples illustrate how to retrieve objects for which no attribute value has been set.

Listing 19: Retrieve objects for which no attribute value has been set.

// this constraint is met if there is no value for the optional
// single-valued String attribute middleName
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllMiddleName (FilterOperators.IS_IN, new String[] { })

// this constraint is met if there are no values for the
// multi-valued String attribute nickName
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.forAllNickName(FilterOperators.IS_IN, new String[] { })

Sometimes you are probabely not interested in the attribute value itself but you want to retrieve all objects for which an attribute value is set at all. The following examples illustrate how to retrieve objects for which an attribute value has been set regardless of its value.

Listing 20: Retrieve objects for which an attribute value has been set.

// this constraint is met if there is a value for the optional
// single-valued String attribute middleName
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsMiddleName(FilterOperators.IS_NOT_IN, new String[] { })

// this constraint is met if there is at least one value for the
// multi-valued String attribute nickName
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsNickName(FilterOperators.IS_NOT_IN, new String[] { });

5.2.5 How to combine multiple query constraints?

You can combine multiple constraints in a query.

It is important to note that the query constraints are ANDed and the query values or ORed. This means that an object must meet all constraints specified by the filter to be a member of the filtered result set.

Listing 21: Combining multiple filter criterias.

PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsGroup(
FilterOperators.IS_IN,
new Group[]{ group }
);
personFilter.thereExistsIsMarried(
FilterOperators.IS_IN,
new boolean[]{ true }
);
List result = ((Container)segment.getPerson()).toList(personFilter);



Filter constraints can not be ORed. The only way to have OR relations is by means of using the filter operators IS_IN, IS_LIKE, IS_UNLIKE, etc. which accept more than one value (which is illustrated in the following example).

Listing 22: OR relation with filter operator IS_IN.

// this constraint is met if the value for the String attribute lastName
// equals "Muster" or "Meier"
PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsLastName(
FilterOperators.IS_IN,
new String[] { "Muster", "Meier" }
);
List result = ((Container)segment.getPerson()).toList(personFilter);

5.2.6 Ordering the result

Filters can also be used to specify the sort order. For every attribute the filter class contains an operation orderByX. This operation takes a single input argument which indicates the sort order (ascending or descending). These operations allow to specify the sort order of all the instances that match the filter constraints.

Listing 23: Specify sort order.

PersonFilter personFilter = labPkg.createPersonFilter();
personFilter.thereExistsIsMarried(
FilterOperators.IS_IN,
new boolean[]{ true }
);
personFilter.orderByLastName(Orders.ASCENDING);
List result = ((Container)segment.getPerson()).toList(personFilter);

5.3 Lab Example

In the package org.openmdx.example.lab1.program.jmi.typed.filter.work you find the class LabClient which allows you to practice the use of filters. To test your code, you can run it by executing ant -f run.xml clientFilterWork. For more information on how to run your code, please refer to .

The solution in package org.openmdx.example.lab1.program.jmi.typed.filter.solution might give you some hints in case you run into problems (execute ant -f run.xml clientFilterSolution to run it).

6 Getting started with Application Plugins

In the previous chapter we explained how to write an openMDX client. This client uses the Java interfaces which have been generated from the model shown in Figure 1. In this chapter you will see how to write implementation part of these interfaces.

A provider represents the implementation part of an application and implements the interface specified by a model. A provider typically consists of several plugins defined by the provider configuration.

Frame9

A plugin is a platform independent component composed of one or more classes implementing the behaviour of the provider. In other words a plugin implements the business logic of an application.

This chapter shows how to implement a plugin using the JMI binding. The first section of this chapter explains the JMI binding architecture. The second section shows a sample implementation. While working through the case study you will find several opportunities to practice.

6.1 Overview

A JMI plugin is composed of two parts:

  • a plugin class which acts as object factory for the implementation objects

  • the implementations classes that implement the application behaviour

The plugin must extend the class org.openmdx.compatibility.base.dataprovider.transport.dispatching.Plugin_1.

6.2 How to create an application plugin?

In this section you see how to implement a JMI plugin. As an example this case study shows how to implement an application plugin. The application plugin implements all behavioral and derived features of the model. In other words: it implements the application business logic. Features which are not implemented (typically persistent features) by the application plugin are delegated to the next configured plugin which is typically the persistence plugin.

The application plugin can be viewed as implementing the 'application logic aspect' of the model, in our case the application logic specified by model introduced shown in Figure 1.

6.2.1 How to write a plugin class?

The implementation of the plugin class is straight-forward. You have to extend org.openmdx.compatibility.base.dataprovider.transport.dispatching.Plugin_1 and implement the protected operation getObjectFactory. As long as you do not need an application context object (more on that later), the implementation of the application plugin looks as shown in Listing 24.

Listing 24: JMI Plugin implementation.

public class LabPlugin
extends Plugin_1
{
protected ObjectFactory_1_0 getObjectFactory() throws ServiceException
{
return new RefObjectFactory_1(
new RefRootPackage_1(
this.getManager(),
this.getPackageImpls(),
null // application context object
)
);
}
}

6.2.2 How to configure a plugin?

A plugin configuration consists of

  • the package names of the implementation classes

  • the model package names of the implemented classes

The sample below shows the configuration for an example application plugin. Each provider and its plugins and their configuration options must be configured in the file ejb-jar.xml. The format of this file is defined by the J2EE specification. In order to create the configuration for the labs, the entries explained in the following sections must be added to the file. For more information on openMDX deployment configurations please refer to the openMDX deployment tutorial.

The variable KERNEL/APPLICATION configures the application plugin class.

Listing 25: KERNEL/APPLICATION Example.

<env-entry>
<env-entry-name>KERNEL/APPLICATION</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org.openmdx.example.lab1.plugin.jmi.overriding.solution.LabPlugin</env-entry-value>
</env-entry>

The variables KERNEL/modelPackage[x] tells the provider which UML model packages are used in this provider.

Listing 26: KERNEL/modelPackage Example.

<env-entry>
<env-entry-name>KERNEL/modelPackage[0]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:w3c</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/modelPackage[1]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:openmdx:base</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/modelPackage[2]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:openmdx:example:lab1</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/modelPackage[3]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:oasis_open</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/modelPackage[4]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:openmdx:compatibility:view1</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/modelPackage[5]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:openmdx:generic1</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/modelPackage[6]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org:openmdx:compatibility:state1</env-entry-value>
</env-entry>

The variables KERNEL/packageImpl[x] tells the provider in which Java packages to look for the JMI object implementations. This is in fact a mapping of model package names (seen above) to Java package names.

Listing 27: KERNEL/packageImpl Example.

<env-entry>
<env-entry-name>KERNEL/packageImpl[0]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/packageImpl[1]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/packageImpl[2]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>org.openmdx.example.lab1.plugin.jmi.overriding.solution</env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/packageImpl[3]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/packageImpl[4]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>
<env-entry>
<env-entry-name>KERNEL/packageImpl[5]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>

<env-entry>
<env-entry-name>KERNEL/packageImpl[6]</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value></env-entry-value>
</env-entry>

Because there are no Java packages configured for the UML model packages org:w3c, org:openmdx:base, org:oasis_open, org:openmdx:compatibility:view1, org:openmdx:generic1 and org:openmdx:compatibility:state1 the request dispatcher will take the generated default JMI object implementations.

Usually the plugin class and the associated JMI object implementation classes are located in the same package. However, this is not a requirement.



NOTE

openMDX offers a LightweightContainer which allows to run openMDX providers in a VM with very small footprint. EARs can be run without any modification. This allows to run and debug openMDX applications easily and then deploy them to a J2EE application server. Therefore we strongly recommend that you first get the samples up and running with the LightweightContainer before deploying them on an application server.

6.2.3 How to add business logic?

As already mentioned the application plugin implements the 'application or business logic aspect' of a model. You have to provide object implementations and methods for all the features which must implement business logic. A typical provider configuration consists of an application and a persistence plugin.

The plugin approach allows fast prototyping. In a first step a provider can be configured with the standard openMDX persistence plugin (either Database_1 or InMemory_1) and with an 'empty' application plugin, i.e. an application plugin which does not implement behaviour. Clients can invoke the provider as long as they do not access behavioural features. In subsequent steps the application plugin can be enriched step-by-step with business logic.

It is important that the implementations have constructors with the described signatures. Otherwise the generic request dispatcher can not instantiate the object implementations.

It is important that the JMI object implementations have the same class name as the generated default JMI object implementations. Otherwise the implementation classes cannot be found by the class loader.

Let us have a look at the example class Person from the lab model. This class has one derived attribute age and two operations serialize and assignGroups. As mentioned before you do not have to care about the other features because the invocations are delegated automatically to the configured datastore plugin.

6.2.3.1 Signature of implementation class

An implementation class

  • must extend org.openmdx.base.accessor.jmi.spi.RefObject_1 and

  • must implement a constructor which takes as parameter the default implementation as delegate object.

This is shown in Listing 28.

Listing 28: Signature of implementation class.

public class <Class>Impl
extends RefObject_1
{
//---------------------------------------------------------------------------
public <Class>Impl(
<Class> delegate
) {
this.delegate = d;
}
...
}

For example the class PersonImpl looks as shown in Listing 29.

Listing 29: Signature of class PersonImpl.

public class PersonImpl
extends RefObject_1
{
//---------------------------------------------------------------------------
public PersonImpl(
Person delegate
) {
this.delegate = d;

}

The openMDX JMI request dispatcher works as follows:

  1. It assumes that the implementation extends RefObject_1.

  2. Before accessing a feature (e.g. age) or invoking an operation (e.g. assignGroups) it checks by reflection whether the implementation class exposes a method matching the feature request (e.g. getAge(), setAge(), assignGroups()).

  3. If yes, the method is invoked and so the user-defined implementation is invoked.

  4. If no, the request is forwarded to the delegate.

6.2.3.2 Sample implementation

The code below shows the implementation for the mentioned class Person.

Listing 30: Implementation of Person.

public class PersonImpl
extends RefObject_1
{
//---------------------------------------------------------------------------
public PersonImpl(
Person delegate
) {
this.delegate = delegate;
}

//---------------------------------------------------------------------------
public Person getDelegate()
{
return this.delegate;
}

//---------------------------------------------------------------------------
public short getAge(
) {
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
int birthdateYear = new Integer(
(this.delegate.getBirthdate()).substring(0,4)
).intValue();
short age = (short)(currentYear – birthdateYear);
AppLog.info("derived attribute 'age'=" + age);
return age;
}

//---------------------------------------------------------------------------
public PersonSerializeResult serialize(
PersonSerializeParams params
) throws RefException {
AppLog.info("format", params.getFormat());
if ("asString".equals(params.getFormat()))
{
String result = this.delegate.getGivenName() + " " +
this.delegate.getMiddleName() + " " + this.delegate.getLastName();
return (
(org.openmdx.example.lab1.cci.lab1Package)this.delegate.refImmediatePackage()
).createPersonSerializeResult(result);
}
// format not supported
else {
AppLog.warning(
"cannot serialize unsupported format => throwing exception CanNotSerialize",
"format=<" + params.getFormat() + ">"
);
throw new CanNotSerialize(
StackedException.DEFAULT_DOMAIN,
StackedException.ASSERTION_FAILURE,
"Desired format is not supported. Supported formats are [asString]",
params.getFormat()
);
}
}

//---------------------------------------------------------------------------
public org.openmdx.base.cci.Void assignGroups(
PersonAssignGroupsParams params
) throws RefException {
for(
Iterator it = params.getGroups().iterator();
it.hasNext();
) {
// Note:
// The params are not of type Group but of GroupImpl. Because the
// implementation is intercepted, to access Group features the
// corresponding delegation object must be dereferenced.
this.delegate.addGroup(((GroupImpl)it.next()).getDelegate());
}
return (
(org.openmdx.base.cci.basePackage)this.delegate.refOutermostPackage().refPackage("org:openmdx:base")
).createVoid();
}

//-------------------------------------------------------------------------
// Variables
//-------------------------------------------------------------------------
private final Person delegate;
}

Please note that if the implementation of an operation needs to access another feature or operation of this class, you have to delegate to the delegation object (unless the delegating object implements the operation itself). The following example shows how the implementation of the derived attribute age in class Person delegates to the delegation object to retrieve the birthdate of the person.



6.2.4 Lab Example

In the package org.openmdx.example.lab1.plugin.jmi.multipleinheritance.work you find the class LabPlugin. To complete the lab you have to provide implementations for all modeled objects that have operations or derived features. To test your code, you can run it with ant -f run.xml pluginJmiWork. The package package org.openmdx.example.lab1.plugin.jmi.solution contains the solution in case you run into problems (execute ant -f run.xml pluginJmiSolution to run it).



7 Plugin context

There is often the requirement that JMI instance-level objects must have access to a plugin-wide, application-specific context object. Examples are configuration values or any other kind of user-defined objects. openMDX provides a way to pass user-defined context objects to JMI instance-level objects. This chapter explains how to pass user-defined context objects to JMI instance-level objects.

7.1 How to pass a context object?

The constructor of the object factory allows you to supply a user-defined context object (the constructor is typically called in the method getObjectFactory() of the application plugin). The object factory will then pass this context object to every created instance. The following code fragment shows how this is done:

Listing 31: Pass the context object.

protected ObjectFactory_1_0 getObjectFactory() throws ServiceException
{
return new RefObjectFactory_1(
new RefRootPackage_1(
this.getManager(),
this.getPackageImpls(),
// Set application defined context object
// which will be passed to all objects that are created by this
// ObjectFactory_1_0.
this.myContextObject )
);
}

Please refer to How to implement the plugin object? for more details about plugins and the getObjectFactory() operation.

openMDX does not touch (read or modify) the context object. It is passed 1:1 to all object instances.



7.2 How to access the context object?

Each object created by the object factory holds a reference to its object factory. This allows the method RefObject_1_0.refObject() to return the context object (the implementation merely delegates to the object factory and returns the context object). This is illustrated by the following code fragment of class PersonImpl:

Listing 32: Access context object from within JMI object implementation.

public short getAge(
) {
int currentYear = Calendar.getInstance().get(Calendar.YEAR);

// query application defined context object
int ageFactor = ((MyContextObject)this.refContext()).getAgeFactor();
int birthdateYear = new Integer((this.getBirthdate()).substring(0,4)).intValue();
return (short)((currentYear - birthdateYear) * ageFactor);
}

8 Getting started with a Reflective JMI client

Getting started with a Typed JMI client explained typed JMI interfaces. These interfaces are generated according to the JMI mapping specification. As you may have observed all these interfaces extend the interfaces from javax.jmi.reflect packages, namely RefObject, RefClass, RefPackage. etc. These reflective interfaces provide the same functionality as the generated typed interfaces, but in a reflective manner. Values can be get and set with the methods refGetValue(), refSetValue(), operations can be invoked with refInvoke(), etc.

This chapter explains how to write an openMDX client using the Reflective JMI accessors. The examples used throughout this chapter are based on the model introduced in Figure 1.

With the generic accessor, openMDX offers an alternate way of generic programming. In fact, the JMI implementation is a wrapper on top of the generic accessor as shown in JMI Mapping Overview. The main difference between JMI reflective and the generic accessor is, that JMI is type-safe whereas the generic accessor is not. The generic accessor does not have any model information. This may improve performance in some situations. The other difference is, that JMI reflective is specified by the JMI specification, whereas the interfaces of the generic accessor, namely Object_1_0 and ObjectFactory_1_0 are not.

As always at the end of this chapter you will have the opportunity to practice what you have learnt in a lab exercise.

8.1 How to create an instance?

As with typed programming, JMI class proxy objects are used to create instance level objects. The class proxy objects provide factory operations that allow the clients to create instance level objects.

In order to create an instance, the corresponding class proxy object must be retrieved. For this purpose the package object that contains the desired class proxy object must retrieved first. Use the refClass operation provided by the package (in the lab example: lab1Pkg) to access the desired class proxy object. This class proxy object can then be used to create instance level objects with the refCreateInstance() operation. Listing 33 illustrates how to retrieve a class proxy object and how to create an instance level object.

Listing 33: Create a new instance of class Person.

// retrieve class proxy for class Person from package
RefClass personClass = lab1Pkg.refClass(
"org:openmdx:example:lab1:Person"
);

// use