Chapter 12

SA Forum Java Mappings: Specifications, Usage, and Experience

Robert Hyerley1 and Jens Jensen2

1Hewlett-Packard, Grenoble, France

2Ericsson, Stockholm, Sweden

12.1 Introduction

This chapter first introduces the reader to the history, rationale, and the architecture driving the Java mapping specifications: in particular, their relation to the existing C language specifications.

Subsequently, the chapter details the conventions and patterns/idioms used when developing the Java mapping specifications. With this knowledge, the application programmer will be able to more easily understand and use the Java mappings. The Java mappings are based on being able to understand their semantics by referring to the original C specification. Understanding the underlying mapping logic helps immensely in ‘connecting’ the Java mappings with the original specifications (see [61, 93] and [63]).

The examples of usage that follow increase understanding as well as shed light on various practical concerns.

12.2 Background

12.2.1 Early Exploration of Java Mappings in Hewlett–Packard

Hewlett-Packard (HP) and the Telecom Infrastructure Division (TID) in particular, has long been involved with the Service Availability (SA) Forum. In 2005 we turned to the service specifications for a particular use quite different from what was originally envisioned. The HP server divisions were investing in, and supporting, Web-Based Enterprise Management (WBEM) [92] as an alternative to Simple Network Management Protocol (SNMP) [53] for both hardware and software management. We in the TID investigated how to interface our software components with the WBEM agent: the Common Information Model Object Manager (CIMOM).

In the HP implementation, a CIMOM management agent resided on each operating system host. That implementation was written in C/C++. The standard method of attaching a component with the CIMOM was with a plug-in called a provider as shown in Figure 12.1. The component specific provider, running as part of the CIMOM, communicates with the component. Hence, the communication between the component and the provider is private. If the component is provided with a library that both implements a programmatic interface and the communication scheme with the associated provider, then the component (in this case, our telecom software) would be isolated from the library/provider communication.

Figure 12.1 An early Java mapping.

12.1

Our primary interest at this point was implementing WBEM indications, roughly equivalent to SNMP traps. The component would signal an event of interest via the library Application Programming Interface (API). The provider would receive the event and invoke the appropriate CIMOM function such that an indication would be emitted.

The communication scheme was relatively simple: it was local to the host and it had one producer and one consumer. We used Unix named pipes for the channel.

We could have invented an API for our components to use, but we realized that the Application Interface Specification (AIS) Event Service (EVT) [43] defined an interface that suited our purpose: the event semantics and the SA Forum pattern of ‘client library API with encapsulated/hidden communication to a server’ fit. We would implement the interface but not the EVT itself.

The missing piece was Java. Some of components were written in Java and the EVT, at the time, had only a C language binding. Over the course of a few weeks, we investigated and developed a Java Language mapping for the EVT. We resisted defining a subset or usage specific mapping. The Java mapping might be useful in the future as a real EVT. As well, defining the task as simply mapping the existing standard provided focus and eliminated the need for any problem specific analysis.

The pleasant surprise during this mapping exercise was that the EVT API, specified for C, was object oriented. Using a few mapping conventions, a Java API emerges almost directly.

Few of the mapping conventions have changed since this first mapping of the EVT. HP went on to define—for internal use—mappings for other SA Forum Services using the same conventions. In 2008, this work was contributed to the SA Forum and provided the greatest part of the basis for the official Java language mappings. These conventions have been used by others to contribute Java mappings.

This chapter details the mapping conventions. They are the key to understanding the Java API.

12.2.2 Java in Ericsson

More than 10 years ago Ericsson started to develop products implemented in Java. During the last six years the main focus has been on Java Enterprise Edition (Java EE) [97] technology, that is, the implemented applications are designed to be deployed on application servers.

We needed to support several deployment scenarios. In some scenarios our Java applications should execute in a heterogeneous environment together with legacy native code but yet be managed in a unified way. For these scenarios the SA Forum specified services provided an appropriate way to achieve the unified management. In other scenarios our Java applications should execute in a homogeneous environment where the management support provided by the application server was adequate.

We therefore wanted the applications to be agnostic about the frameworks used to manage and to control them. We also thought that it was essential to use existing Java standards as far as possible in order to gain wide acceptance in the Java community and to be able to incorporate third party applications in a seamless way. At the same time it should be possible to plug in different back-ends in order to support the wanted deployment scenarios.

The resulting approach was applications using standard Java APIs and plug-ins mapping these APIs to the SA Forum services. The mapping was quite straightforward for the Log service (LOG) and the Notification service (NTF). We published the SA Forum whitepapers [95, 96] describing how the Java Logging API should be mapped to the LOG [40] and how the Java Management Extensions (JMX) [97, 98] notifications should be mapped to the NTF [39].

Mapping the Information Model Management service (IMM) [38] was more complicated. JMX is the Java standard for management. It is mainly used as the interface for management systems and for applications to expose configuration and runtime information to the management systems. However, it is to a lesser extent used for applications to access their configuration. For this purpose there exists a number of different approaches in the Java community—all of them with serious limitations. We have used JMX as the API for applications to access their configuration, but we have also tried JavaBeans [99] for this purpose, as this approach results in concise, straightforward and easy-to-read code. There is obviously a need for further Java standardization in this area.

No Java standard for availability management existed. We therefore implemented enhancements in the application server in order to be able to plug in different availability managers such as the SA Forum Availability Management Framework (AMF) [48]. The applications did not need any modifications. Based on this experience we started the standardization of Availability Management for Java, JSR 319 [100], which now has finished.

Mapping Java standard APIs to SA Forum services implies compromises and some features of the SA Forum services are not possible to use. However, our conclusions were that these compromises are quite acceptable for most Java applications.

12.2.3 The SA Forum Java Mapping Initiative

The pace of introduction of new services continues to increase. Being able to leverage a large community of developers and tools as well as third party products and frameworks is critical to rapid development. The Java community is large as is the rich Java software collection.

In the telecom as well as other domains, carrier grade high-availability (HA) and fault tolerance is the norm: it needs to be present when Java is employed. Important problems to solve are how to provide HA and Operations, Administration, and Maintenance (OAM) integration to Java applications in a mixed (Java plus other programming languages/frameworks) application environment without imposing an intrusive technology.

The availability support that exists for Java EE today covers only the Java parts and does not include availability support for hardware and non-Java applications. It is desirable to have a framework that can provide availability for mixed applications in the same clustered system. The same problem exists for configuration and fault management in mixed technology clusters.

One way to solve these problems is to use a standardized, global framework for availability, and configuration/fault management as the base for both Java and non-Java applications. However, this runs counter to nonintrusiveness. For wholly new components, this framework might be fine. But again, the assumption is that applications and services today are largely built from pre-existing pieces.

A central idea when starting the work on the Java mappings for SA Forum services was to define one set of Java interfaces that is a rather straightforward mapping of the SA Forum interfaces. These interfaces can then be used directly, or, be used for integration with existing solutions within the Java community.

The SA Forum would specify the primary mapping, and then the integration with existing Java standards could then be described in white papers. For the parts where Java lacks a standard, these could be defined within the Java Community Process (JCP).

Not all of the services defined in the SA Forum standard are of interest for Java and Java EE. Some of the services already exist with the same or similar functionality standardized for Java, and there is no need for an interaction between Java and non-Java applications.

Other SA Forum services, for reasons of integration, benefit in having a Java mapping:

  • OAM alignment. Java applications and native applications will be hosted in the same environment. One example of native applications that are already colocated with Java is databases. It is highly desirable that both types of applications be managed the same way.
  • SA Forum enabled third party products. When the ecosystem around SA Forum takes off, there will be a more and more SA Forum enabled components available. It is desirable that these products can be used together when mixed in with Java and Java EE.
  • High availability. When Java EE is to be used in a mixed environment with native applications it is important that one framework controls both the Java and non-Java application redundancy and distribution. A new Java API has been standardized for this: Availability Management for Java [100].

12.3 Understanding the Java Mappings

12.3.1 Java Application Integration Architecture

A programmer or analyst, seeking to use the SA Forum AIS services with Java applications or components, would naturally turn to the Java mapping specifications. These specifications are published as JavaDoc [101]; a format familiar to all Java programmers.

However, that programmer would quickly discover that the JavaDoc alone is insufficient for understanding the API. It is so sparse, that one might conclude that it was written by a junior programmer unschooled in producing meaningful documentation comments.

Why is this? The fundamental reason rests in two design choices adopted by the SA Forum when producing these specifications:

First, the Java API should be on the same level and have the same semantics as the C API while being expressed with appropriate Java constructs and conventions. One could imagine Java interfaces (APIs) to SA Forum services in many forms: for example, encapsulating frameworks, virtualized resources, mapping to existing Java objects such as Streams or MBeans, and so on. A ‘higher level’ interface might fit better with current Java style, might hide more implementation details, might be more flexible, or might be easier to approach for Java programmers. A ‘lower level’ interface—some sort of direct mapping via the Java Native Interface—might simplify the specification process and enable a straightforward way to use existing SA Forum service implementations in a Java environment. Any choice of a mapping approach has drawbacks of course. The higher level interfaces would have required significant specification and implementation development plus a period of experimentation and refinement. The lower level interfaces would have exposed much of the irrelevant C implementation details. The Java Native Interface [102] adds risk to the integrity of the Java runtime and is not appropriate for general application programming. We felt that a low level interface would often be wrapped in more appropriate Java by users—creating a multitude of unique and nonstandard interfaces.

The ‘middle ground’ approach was a choice of practicality. It seeks to remove as many C artifacts as possible without losing a correspondence to the underlying C specifications. This is done by using regular rules or patterns to transform the C specification into Java as opposed to ad hoc interface redesign. These rules are described below.

It is hoped that this mapping approach will result in Java interfaces that have no need to be wrapped unless semantics different from the C specification are to be introduced. At the same time, if necessary, the standardized Java mappings can be used as a foundation and be completely hidden when building a higher level interface. Such higher-level interfaces have, in fact, been developed including some that have been standardized [100].

Secondly, there should be a single specification of SA Forum services. With a choice of producing a Java mapping close to the C language specification meant that there was little to gain by producing a separate, stand-alone specification. As well, with limited resources, this work would have significantly delayed the appearance of such a specification. Finally, with a single specification, there was less risk of errors introduced by duplication of information, and much less risk of inconsistencies between specifications.

Hence, to use the Java mapping specification, one must start by reading and understanding the C language specification. Only then will the Java mapping be understandable.

The Java mapping in JavaDoc is suitable as a reference but rather unsuitable for front to back reading. As a reference, it of course provides class and method signatures as well as package structure. It also provides references into the C language specifications. So, having the C specification immediately available as an additional reference is generally necessary.

While one can follow the references into the C language specifications to understand the Java mapping, it is often simpler to understand the conventions used to produce the mapping. The remainder of this section explains those conventions. Armed with an understanding of the original specification and these conventions, the programmer can effectively use the terse JavaDoc.

12.3.2 Naming

The Java API uses commonly accepted naming conventions [103]. The C language APIs fortunately use constant, variable, typedefs, parameter, and function naming conventions that are roughly compatible in terms of capitalization and noun/verb usage.

On the other hand, the C language APIs rely on identifier (ID) naming to simulate namespaces. The common pattern is to use prefixes to reflect a (missing) package mechanism in C. Since Java has packages, these are used directly. This removes the need for prefixes. The package structure used in the Java mapping is described below.

In addition, the C language API convention of terminating all type definition names with a capital ‘T’ is not employed in the Java mapping.

With these conventions, it is generally possible to recognize the correspondence between the C and Java names as in this example from the NTF: the typedef definition SaNtfSpecificProblemT in C corresponds to the class definition SpecificProblem in Java within the org.saforum.ais.ntf package.

There are some exceptions to increase readability and decrease confusion:

1. Exception classes corresponding to SaAisErrorT values are all defined in the org.saforum.ais package. The ‘Ais’ prefix is retained. For example, SA_AIS_ERR_TIMEOUT in C becomes AisTimeoutException in Java.
2. The commonly used ‘handle’ idiom is embellished with the handle type in its name; for example, AmfHandle instead of Handle.
3. Certain SA Forum specific constants retain this reference in their names: for example, SA_TIME_END appears in both the C and Java mappings as the ‘SA’ is used as an adjective and not a namespace designation.

12.3.3 Package Structure

The Java mapping uses packages to organize the different service APIs. There are three levels in the package name hierarchy.

At the top level of naming there is a package meant to be included—in whole or in part—by all packages using SA Forum services in Java: org.saforum.ais. This package roughly corresponds to definitions found in the SA Forum AIS Overview and C Programming Model specifications (see also: the ais.h header file) [93]. This package factors out definitions common to all AIS services. Additionally, it contains library handle interfaces providing lifecycle control and the infrastructure for asynchronous communication between the AIS services and the client code. Finally, it adds a generic factory class to provide the ‘bridge’ from the client to the SA Forum service implementation.

Below the global level are the various service packages corresponding to the C language specifications. These names are of the form org.saforum.ais.<service>. Each service package is named by its abbreviation: for example, ‘amf,’ ‘clm,’ and so on. Some services are defined using only a single package.

Other services (e.g., ‘ntf’) have subpackages at a third naming level based on the ‘roles’ supported by the service. These names are in the form of org.saforum.ais.<service>.<role>. These subpackages contain definitions specific to roles: here, for example, the NTF [39] has ‘producer’ and ‘consumer’ packages; the IMM [38] has ‘om’ (object manager) and ‘oi’ (object implementer) packages. When subpackages are present, the second level package contains definitions common to the roles.

12.3.4 The Underlying Objects

The naming conventions and package organization provide a framework for organizing the various service specifications and translating IDs. They do not address the correspondence between the C language definitions and Java objects. In particular, they do not guarantee that the C definitions can be cast into an object-oriented, or any other, style.

Fortunately, the C language service specifications are designed using an object-oriented style. This style is not well explained in the specifications, but is easily discovered with careful reading. Once understood, the translation to Java objects is straightforward.

The C language specifications use a pattern of obtaining an ‘object reference’ via a ‘factory,’ followed by repeated ‘method invocations,’ followed eventually by ‘object destruction.’ The object references are called handles in the C specifications. The factories are initialize functions (e.g., saNtfInitialize). The methods are functions associated with an object type and which always take a handle as a first parameter. Object destruction is accomplished with finalize functions (e.g., saNtfFinalize). In C there is not, of course, automatic garbage collection. Details of how factories and life-cycles are mapped in Java are covered in the section below.

In Java, the object reference parameter is not needed.

These service objects tend to be large and often contain (internal) references to system resources and cooperating processes (e.g., area servers). In typical usage, only a few such objects are allocated per client process: often one per service. Because of the potential internal references, these service objects retain the explicit destruction methods.

One will not find the service objects defined using Java classes. Instead, they are defined using interfaces. These results in a Java mapping—expressed in Java source code—that is largely devoid of implementation. The notable exception to this rule is the FactoryImpl and related factories.

There are, however, many other class definitions in the Java mapping. The use of class definitions is generally reserved for defining data structures. That is, these classes have no methods; not even ‘getters’ and ‘setters.’ Objects of these classes are used only as parameters.

12.3.5 Types

The C language specifications define a fairly high number of special data types that are used primarily to pass information between the underlying middleware and the client code. These data types are mapped using the following conventions:

Primitive data types are mapped to their Java counterparts of the same bit-size. For example, a type defined in the C language specification as SaInt16T is mapped to a short type in the Java mapping. Unsigned integer types are mapped to the signed Java primitive type of the same bit size. Although this could in theory cause interpretation problems if, for example, arithmetic or relational operations were used carelessly. In practice, most unsigned integer types of the C language specification are used for special purposes, so this is not a problem: unsigned values used as IDs only need an equality check and never computation; flags are defined by their bit positions and the Java bitwise operations operate as expected on signed values. Still, the client programmer needs to aware of this use of signed primitive types. The alternative of defining or using more complex types for unsigned values was not adopted as experience showed that problems did not appear in practice.

When a particular parameter or return value can be one of many different numeric types, java.lang.Number is used in the mapping.

Strings are represented in the C language specifications in several ways including null-terminated strings, character arrays, and structures containing array and length fields. All of these types are mapped using the java.lang.String class. This means that all length information is unneeded in the Java mapping: it is provided by the length() method. As well, all the other String methods are available ‘for free.’

In a similar manner, whenever the C specification defines a struct that encapsulates a buffer pointer and its length, a Java array is used instead as these always have a length property.

Enumerated types (enum) are used to define most constant values. Java enums define a set of IDs without the need to explicitly assign implementation values to the IDs. However, in the Java mapping enums are mapped to the numeric value defined by the corresponding C language definition. Java clients never need this mapping for internal use. The reason for the mapping is that the Java client may have to forward the numeric value to a non-Java, but SA Forum aware entity, through some other channel than the Java API. For example, the value may be sent in a message or written in a log record.

The mapping is achieved using the standard techniques associated with Java enums (i.e., defining a private constructor and value field) plus implementing getValue() defined in org.saforum.ais.EnumValue.

The Java mapping uses public static final fields of the appropriate primitive type for constants that cannot be defined reasonably using enum's. This is the case when the constant is a numeric value used in calculations and not simply an instance name. As well, some constants are not defined as enum's simply due to historical accident. Constants are used for bitmaps, strings, and (meaningful) numeric values.

C structs are mapped to public classes with public fields. For the sake of simplicity, there is no data hiding or encapsulation (e.g., no ‘setters’ or ‘getters’). These classes are completely free of code. The reasons for this decision are: First, these classes are used only as data transfer objects, that is, they are only meant to transmit data between the client code and the service. Secondly, the likelihood of changing the existing fields of these structures is marginal, since they are likewise exposed in the C API specifications and changing them in newer AIS versions would break the compatibility with older AIS versions and client code. Finally, mapped in this way, the correspondence between the Java and C is evident.

One potential function of such setters and getters would be to provide validity checks for the fields of these objects. However, the AIS servers do the same validity checks when the objects are actually passed to them. It is not clear that the client library code would even be able to perform such checking.

C unions are mapped using an enum discriminator in the ‘root’ type that indicates the actual underlying type or sub-type. The use of this technique is not always consistent. For example, in org.saforum.ais.imm.AttrValues, one uses the attrValueType field to select which cast to apply to the java.lang.Objects in the attrValues array. In org.saforum.ais.ntf.Notification, the getEventType method returns an EventType used to select a cast of the entire Notification object to a subtype. Programmers need to pay particular attention when using these union types!

C arrays are mapped to Java arrays in a straightforward manner. Again, an associated length variable is not needed in Java. In the C language specification, arrays often have a maximum or allocated size plus the ‘used’ size. This complication is largely eliminated in the Java mapping: arrays are allocated to the size needed. This implies that reuse of allocated variable size data structures is not well supported or encouraged in the Java mappings.

Even more significantly, the responsibility for allocating memory is often different between the C specification and the Java mapping. The C specification style is for the client to allocate (and often deallocate) memory for shared use by the client and the client library. In the Java mapping, the style is that the producer of the data allocates memory and the memory is eventually garbage collected.

C function pointers—used only for callbacks in the C specifications—are mapped to interfaces with a single method defining the signature of the callback. The client implements the callback function by defining a class that implements the callback interface. The callback is provided to the library by providing an object of this class. This implies that callback methods may not be static.

12.3.6 Parameters, Exceptions, and Method Signatures

Functions of the C language specifications are mapped to methods in Java. These methods are defined in interfaces. For the most part, there is one-to-one correspondence in functionality between the C function and the Java method. As explained above, typically the naming has been simplified in the Java mapping in such a manner that the equivalent C function name is obvious. Noteworthy exceptions to the naming of such Java methods is the initialization and finalization of handles:

1. In the C specifications, the library handle lifecycle is controlled by sa<Area>Initialize() and sa<Area>Finalize() functions. Applying the default naming conventions would lead to a finalize() method. To avoid confusion with the well-known method with the same name inherited from java.lang.Object, the method name finalizeHandle() is used instead. For consistency, initializeHandle() is used instead of simply initialize().
2. For handles other than those referring to a service library instance, the pair of create<Entity>()/destroy() method names is used for life-cycle control.

There are cases where a single C function is re-factored to several Java methods with different parameter lists. These C functions use certain parameters to govern what the function actually should do while in the Java mapping this information is encapsulated in the method name. This is an exception to the normal naming conventions. The documentation of the Java methods contains a reference to the equivalent C functions, including information on the specific parameter values that belong to a ‘refactored’ Java method.

In Java, memory is never freed explicitly. Therefore, the C API functions used to free memory do not have their counterparts in Java. As noted above, the service objects do retain their ‘destruction’ methods so that system resources can be freed.

As already described, the C ‘handles’ are never passed as parameters in the Java methods.

The C language specifications employ a typical C error handling convention: the return value of each function indicates success or failure along with an encoding of the error cause. The enum type SaAisErrorT defines these errors in the C specifications. SA_AIS_OK indicates successful execution, whereas SA_AIS_ERR_<SOME_ERROR> values represent possible errors.

In the Java mapping, errors are handled using exceptions. The org.saforum.ais package defines corresponding exceptions for each SaAisErrorT value. So, Java methods may throw one of these exceptions and do not return a status value. The return value can then be used to return other data that in the C specifications is returned with an ‘out’ parameter.

There is a superclass, AisException, from which all the other AIS exceptions are subtyped. The API never throws this exception: explicit exception subtypes are always defined for each method. The superclass only serves to factor out common functionality.

The enumerated type AisStatus corresponds to the C SaAisErrorT values (including SA_AIS_OK—but there is no ‘AisOk’ exception). These C language enum values are available for each exception in the cause field. The status value is used to indicate the status of operations executed asynchronously in the Java mapping; that is, the status of an operation other than the one initiated by the actual method call. See, for example, org.saforum.ais.imm.om.AdminOwner.adminOperation.Invoke.

The replacement of handles with object references, the use of exceptions instead of a status return, the use of the (now available) return value for other data, and the elimination of descriptive parameters such as allocated and used array sizes results in the Java methods having far fewer parameters: approximately one half, on average compared to the C functions. Also, the elimination of the status return and producer allocated memory result in the Java signatures having very few ‘out’ parameters. The naming conventions shorten and simplify parameter, type, and method names. In total the Java signatures are quite different while their C counterparts are usually readily recognizable.

12.3.7 Factories, Callbacks, and Life-cycles

The prototypical C language AIS client library instance life-cycle is based upon the following model:

1. The client initializes a dynamic entity—an instance of the service client library—representing and implementing an association between the client and the service area server.
2. The client code communicating with the area server via the entity.
3. The client code shutting down the association when it is no longer needed.

In the Java mapping this generic model is translated to the following:

1. The client code obtaining an <Area>Handle object (an instance of the service client library).
2. The client code communicating with the local library and area server by invoking methods on the library handle or on other closely associated objects always obtained from this ‘root’ or ‘core’ object. As well, the area server initiates communication to the client via the callback mechanism.
3. The client code shutting down the library instance by invoking its finalizeHandle() method. The client should not retain any references to this now defunct object so that it can be garbage collected.

The client is free to create and use more than one <Area>Handle object. The only exception applies to the AMF service, which allows only a single library handle to carry out operations that require registration.

The two life-cycle operations of initialization and the finalization are done rather differently: finalization is a method invoked on the existing object; initialization via the generic factory framework is significantly more complicated and is explained here.

The factory framework consists of:

  • The factory method initializeHandle defined by the org.saforum.ais.Factory interface. All the specific service factory classes implement this interface. This is a generic interface parameterized by the service interface ‘S’ representing the service library handle type; the callback class ‘C’ representing the set of callback objects used by the libraries to invoke client code.
  • Service handle types (interfaces). These define the core objects for each service. The initialize methods return objects of this type.
  • Callback classes defined for each service. These classes are nested within the handle interfaces. These classes contain a field for each callback function.
  • A <Area>HandleFactory class for each service. These classes are derived from, and share the implementation from, the org.saforum.ais.FactoryImpl class. This class is the only one in the Java mapping that contains any significant code: everything else is either data or interface definitions.

Because of the use of interfaces, the factory methods cannot be static. Hence, it is required to create a factory object using the default constructor prior to calling the factory method. Details of the steps client code must take are covered below in the example.

Once the root object is obtained from the factory, the client may invoke methods on it, including methods that are used to obtain/create related objects representing functionality and data subsets. The life-cycles for these related objects follow one of the following patterns:

1. ‘Getter’ methods return references to objects that have a one-to-one association with the library handle: that is, all invocations of the getter method will return the same object. These objects simply bundle the methods providing part of the service. For example, AmfHandle defines many getter functions: getComponentRegistry(), getCsiManager(), and so on. These objects do not require any special method for clean-up after use as their life-cycles are associated with that of the core object.
2. ‘Creator’ methods—following the naming convention of create<Entity>—return references to <Entity> objects that are newly created on each method invocation and may also allocate system resources. For example, the Consumer interface defines a createReader() method that creates and returns a new Reader object upon each invocation. These created objects will usually have a method named destroy() that must be invoked on the object when it is no longer needed so that system resources can be cleanly deallocated. As in the case for core objects, the client should drop all references to such objects after invoking destroy() so that the garbage collector can recover their memory.

12.3.8 Callbacks and the Selection Object in Java

The library handle provides a mechanism for asynchronous communication between the client and the associated area server. This mechanism is shared by all the services, and hence, is factored into the ais package in the Java mapping.

There are three elements:

1. The set of callback objects that are invoked when a requested asynchronous operation is ready. The callback objects are provided when the library handle is initialized (see above). They are permanently associated with the library handle until the library handle is finalized. The client code is free to provide null for any callback object if the client does not use the operation associated with that particular callback. However, if an operation would require a callback and it is requested without a callback defined, the AisInitException will be thrown.
2. Methods to control the dispatching of callbacks. Following the model defined in the C language specifications, the client library never invokes a callback using a thread not provided by the client itself. This allows the client to be truly single threaded if necessary. Pending callbacks are always invoked in the context of one of the dispatch() methods defined in org.saforum.ais.Handle.
3. Methods to detect pending callbacks The library handle provides several forms of the dispatch() method. Some will return immediately if there is no pending callback, some will block until a callback becomes pending, and some will specify a time-out before returning. Furthermore, the library handle provides methods for checking the availability of callbacks without actually dispatching them (see the hasPendingCallback() methods). Last but not least, the library handle supports multiplexed callback selection by integrating this API with the New I/O (NIO) [104] framework: the getSelectableChannel() method will return a SelectableChannel object that can be used by NIO selectors. This is the closest to the semantics of the C language selection object.

12.4 Using the Java Mappings

12.4.1 Integrating AIS Services with Java Applications

In this section, we will present a simple client code example that both illustrates the Java API use as well as highlights some fine points and practical concerns when the mapping specification is put to use.

The inspiration for this example is Appendix C of the NTF specification [39]. The appendix contains C code that sends and receives security alarm notifications. We don't attempt to translate the example in all the details but the basic functionality is the same.

When one compares the C and Java code for this example, a key difference is immediately apparent: most of the C code is occupied with obtaining and using storage; most of the Java code is the instantiation of objects—with the Java code being substantially shorter. Both versions of the example devote (or should devote) significant effort to error/exception recovery.

The Java example is cast in the light of two components: one that checks access attempts to various ‘objects.’ If the access is denied (always in this example), a security alarm notification is emitted. This component is called the ‘GateKeeper’ and we have defined a class with this name.

The second component, named the ‘GateWatcher’ waits for security alarm notifications of a certain (elevated) severity level emitted by components from the GateWatcher's domain (i.e., the same vendor). The GateWatcher does nothing more than print out a line of information for this example.

This example, as well as the C language version in the specification appendix, is not meant to—and does not—illustrate best practices of modularity, robustness, or other elements of politically correct style.

12.4.1.1 Finding the SA Forum Service

For a client to use an SA Forum service, several prerequisites must be met: the service implementation must be installed according to the service vendor's guidelines, the Java mapping classes must be available to the client Java code, and via some appropriate configuration the mapping library must be able to locate and load the Java service library implementation classes.

Different vendors and different services will have varying approaches to installation. We won't cover these here. In any event, the client software design is independent of the particular approach.

The Java mapping classes must be available. In this example of the NTF, we will need two jar files containing four packages: ais.jar and ntf.jar. They need to be available to the client's classloader(s), which often means they can be found on the classpath.

The third prerequisite is the trickiest: how to locate—in fact even how to name—the class supplied by the service vendor that implements (for the NTF in our example) public static NtfHandle initializeHandle( NtfHandle.Callbacks, Version ). The SA Forum Java mapping does not specify vendor class or package names for the implementation. The only requirement is that the vendor must implement this initialization method. Without the classname, we can't even use the classpath to search for this method.

What the Java mapping does specify are two property keys (or, ‘property names’) for each service: one that is used to find the name of the class implementing initializeHandle, a second that is used to find the URL that a URLClassloader can use to find the named class. When the client allocates a factory for the service, the System properties are searched using these keys. One finds the definition of the keys for the NTF in the NtfHandleFactory class.

These properties might be set on behalf of the client when the client is launched, perhaps on the command line. That is, the client inherits these properties. The client might also be able to set these properties programmatically. We say ‘might’ because the client might not have permission to set these properties. The means for the client to set these properties was included in the specification for testing purposes: it is not a mechanism suggested for production use.

For our example, we will actually set the properties using client code:

 /**
  * Change this as needed to adopt to vendors
  * and local install environment.
  */
static final String SAF_IMPL_PROVIDER_URL =
“http://localhost:8080/saforum/providers/com.provider/impl/classes/”;

/**
 * Class name of implementation of factory
 */
static final String SAF_IMPL_NTF_CLASSNAME =
“com.provider.ais.ntf.ntfImpl.NtfHandleFactoryImpl”;
/**
 * Set up implementation configuration information for
 * SA Forum services. This needs to be done before
 * instantiating any services. Note that the execution
 * environment, including security
 * settings applicable to system properties, may override
 * this configuration.
 */
static protected void safSetup() {

System.setProperty(NtfHandleFactory.IMPL_CLASSNAME_KEY,
       SAF_IMPL_NTF_CLASSNAME);
  System.setProperty(NtfHandleFactory.IMPL_URL_KEY,
       SAF_IMPL_PROVIDER_URL);
}

12.4.1.2 Instantiating a Service Instance

With the configuration set, we can now obtain a NTF instance.

In our example, each GateKeeper object has its own NTF instance. An alternative approach would be that different functional areas of the client code would share a single service. It is mostly a matter of client architecture choice: ‘mostly’ because creating a service instance can be rather heavyweight in terms of communication between servers and in the maintenance of shared state between servers. This might, in practice, constrain architectural choice.

Both the GateKeeper and the GateWatcher extend an abstract class called NotificationWrapper that holds shared data and logic. This class also suggests a common lifecycle of initialize, use, and then shut down. The handle field is inherited from NotificationWrapper.

In the GateKeeper example, the initialize method instantiates the NTF as well as the associated producer. Data that will be sent with each notification is also initialized:

public void initialize() {
  
Version version = new Version('A', (short) 2, (short) 1);
  
NtfHandle.Callbacks callbacks = new NtfHandle.Callbacks();
  
NtfHandleFactory factory = new NtfHandleFactory();
try {
 handle = factory.initializeHandle(callbacks, version);
 producer = handle.getProducer();
} catch (AisException e) {
      e.printStackTrace(); // acceptable for an example
}
  
       classId = new ClassId();
       classId.vendorId = 33333;
       classId.majorId = 995;
       classId.minorId = 1;
  
     serviceProvider =
         new ServiceUser();
     serviceProvider.value =
  new ValueStringImpl(“switch configurator”);
     securityAlarmDetector =
  new SecurityAlarmDetector();
  securityAlarmDetector.value =
 new ValueStringImpl(
          “com.example.notifications.GateKeeper.checkAccess”
          );  
}

Of particular note:

1. The GateKeeper is requesting version A.02.02 even though later versions are available.
2. A Callbacks structure is allocated, but no callbacks are supplied here since the GateKeeper is never going to request an operation that would require them.
3. Once the NTF instance is obtained (i.e., handle is initialized), we immediately request the associated Producer object. The handle will be used again only to shut down the service.
4. Marshaling data and requesting service.

The GateKeeper's main task is to check access and return ‘ok’ or not. If access is denied, a security alarm is raised and a SecurityAlarmNotification sent:

public boolean checkAccess(String user, String role, String object,
      String operation) {
 boolean ok = false;// for this example, we're not
     // letting anyone do anything

 if (! ok) {
         ServiceUser serviceUser = new ServiceUser();
         serviceUser.value = new ValueStringImpl(user);
         Date now = new Date();

         try {
          SecurityAlarmNotification alarm =
 producer.createSecurityAlarmNotification(
          EventType.OPERATION_VIOLATION,
          object,
          classId,
          now.getTime() * 1000 * 1000,  // SAF uses nanoseconds,
          // not milliseconds
          ProbableCause.AUTHENTICATION_FAILURE,
          Severity.SEVERITY_MAJOR,
          securityAlarmDetector,
          serviceUser,
          serviceProvider);
 alarm.setAdditionalText(“Access Denied!”);
 alarm.send();
 } catch (AisException e) {
     e.printStackTrace();  // acceptable for an example,
            // but not for production
 }
}

Some of the data sent is specific to each access check and some is common and reused in every notification. The SA Forum has defined time stamps in terms of nanoseconds while Java only provides milliseconds. This requires us to make a conversion. The mapping specification could have defined an automatic conversion, but this would have violated the principle of following the semantics of the original C language specification.

12.4.1.3 Modularity and Exceptions

Up to this point, we have not handled exceptions very reasonably. We will see better examples below.

One design choice that we have made is to insulate code using the GateKeeper and GateWatcher classes from the NTF: no SA Forum types or exceptions are visible from the outside. Presumably, a completely different type of alarm notification scheme could replace the SA Forum defined service. But, how do we report problems when they inevitably occur? We could define our own exception classes and map the AisExceptions to these. Or, we could use the AisExceptions even if we do not use the SA Forum service. Still another approach would be to map the appropriate AisExceptions to Java unchecked exceptions. Similar design choices pertain to SA Forum type definitions in general.

The design space is large and the choice is largely driven by the client application, not the mapping specification. In the following section covering containerized applications, we will see yet another approach: mapping between the NTF and JMX.

12.4.1.4 Being Called Back

While the GateKeeper sends notifications in response to access violations, the GateWatcher receives notifications when the NTF delivers them. The GateWatcher's initialization involves more than obtaining the consumer object: a filter defining notifications of interest must be constructed and then used to subscribe to notifications. As well, a callback method must be provided to NTF so that it can be invoked when a message matching the filter arrives. Here is the initialization code:

public void initialize() {
  Version version = new Version(‘A’, (short) 4, (short) 1);

  NtfHandle.Callbacks callbacks = new NtfHandle.Callbacks();
  callbacks.notificationCallback = new OnNotificationCallback();

  ClassId classIds[] = new ClassId[] { new ClassId() };
  classIds[0].vendorId = 33333;
  classIds[0].majorId = 995;
  classIds[0].minorId = 1;

  Severity severities[] = new Severity[] { Severity.SEVERITY_MAJOR,
  Severity.SEVERITY_CRITICAL };

  NtfHandleFactory factory = new NtfHandleFactory();
  try {
   handle = factory.initializeHandle(callbacks, version);
   Consumer consumer = handle.getConsumer();
   NotificationFilters filters = new NotificationFilters();
   filters.securityAlarmFilter =
   consumer.createSecurityAlarmNotificationFilter(
       null,
      (FilterName []) null,
       null,
       classIds,
       null,
       severities,
       null,
       null,
       null);
   consumer.createSubscription(filters); // we drop the return value
  } catch (AisException e) {
        e.printStackTrace();
              // acceptable for an example, but not for production
}  
}

Of note:

1. The GateWatcher requests version A.04.01 that includes changes to how filters are specified (Java mapping in progress).
2. We filter on a single ClassId: that of our partner, the GateKeeper. We don't want to receive (here) other security alarms.
3. We provide a callback for security alarms only. We won't get notifications for other alarms, state changes, and so on.
4. The cast of null to a FilterName[] is required to disambiguate between a deprecated version of createSecurityAlarmNotificationFilter. In the C language API, the name of the function changes with each change made in its specification. In Java, we use features in the language to reduce such renaming.
5. We don't plan to ever unsubscribe, so we don't save the return value from createSecurityAlarmNotificationFilter.

Here is what the callback looks like:

class OnNotificationCallback implements NotificationCallback {

  @Override
  public void notificationCallback(
    Subscription subscription,
    ConsumedNotification notification) {
  try {
   SecurityAlarmNotification alarm =
        (SecurityAlarmNotification) notification; 
   // and if the cast fails?
   Date eventTime = new Date(alarm.getEventTime() / (1000*1000)); 
   // convert to milliseconds
   System.out.println(
               “Security Alarm at ” +
               eventTime.toString() + 
               “, with severity ” +
               alarm.getSeverity() +
               “, type: ” +
               alarm.getEventType() +
               “, probable cause: ” +
               alarm.getProbableCause() +
               “ (“ + alarm.getAdditionalText() + ” “
               + alarm.getLocalizedMessage() + ”)”);
    } catch (AisException e) {
     e.printStackTrace();
      // acceptable for an example, but not for production
    }
  }
}

When called, a few fields of the notification are printed out for example purposes. What would it mean if the cast to SecurityAlarmNotification failed? It would mean an error in the service implementation since it should only provide security alarm notifications to security alarm notification callback methods. This looks like a place to improve the mapping specification: the typing could be tighter so that no cast would be needed.

In SA Forum services, the client is responsible for providing the threads used to invoke callbacks. They are supplied with the dispatch method (and its variants). Everything above was initialization prior to ‘use.’ The use phase of the GateWatcher's lifecycle is the listen() method:

public void listen() { 
 try {
         handle.dispatchBlocking();
 } catch (AisException e) {
        e.printStackTrace();
              // acceptable for an example, but not for production
 }
 return;
}

What is not shown here is the creation of the thread that (eventually) calls listen. The way this example is written, listen is going to repeatedly block waiting for notifications to arrive, invoke the callback for each, and then block again. Only when the service is shut down does this thread return from dispatchBlocking, and hence, from listen.

12.4.1.5 Cleaning Up

When it is time to stop checking access or stop receiving notifications, we can simply shut down, or ‘finalize,’ the NTF instantiations. The code to do this is identical for both consumers and producers and is contained in the NotificationWrapper class:

public void shutdown() {
  if (handle == null) return;   // already shutdown, or tried to

  long timeout = 500;  // sleep timeout milli's if library
       is not responding
  int attempts = 3;// max tries to shutdown, double timeout
       each attempt
  do {
   try {
    handle.finalizeHandle();
   } catch (AisLibraryException e) {
    break;// library dead, can we report this?, give up here
   } catch (AisTimeoutException e) {
    try {
     Thread.sleep(timeout);
    } catch (InterruptedException e1) { // ignore this, just try again
    }
    timeout >>= 2;   // wait long next time if we try again
    continue;
  } catch (AisTryAgainException e) {
    continue;    // might work next time?
  } catch (AisBadHandleException e) {
    // don't bury this, it's a bug! re-throw!  
    throw new IllegalStateException(“
     Expected the notification handle to always be valid”,e);
  }
 } while (--attempts > 0);

 handle = null; 
  // indicates we're done, and let's the garbage collector go to work
}

The basic code is pretty simple: if we haven't already shut down (i.e., the handle is null), then call finalizeHandle(). What we will illustrate here is handling and attempting to recover from exceptions. This is the reason for the complexity.

Two of the exceptions deal with (hopefully) transient situations: ‘Timeout’ and ‘TryAgain.’ In either case, attempting the operation again might result is success. We keep track of these retries with the attempts counter which counts down from the initial value. With timeouts, we sleep between tries (and increase the time we sleep on each attempt).

The AisLibraryException indicates an internal error in the service (it is not our fault …well, maybe, if we're coding in C with run-away pointers!). We can't recover from this and might not even be able to create a new service instance. In many situations, we should report this and perhaps initiate an application restart. Here, since we are shutting down anyway, we just ‘swallow’ it.

The AisBadHandleException indicates that, most likely, there is an error in our code. For instance, this exception is thrown when the handle has already been finalized. But we checked for the handle being null! Maybe somebody might change the code in the future and reporting this exception—we can't actually recover here—will help find a bug. Maybe there is a bug in the code already? What if two threads were to call shutdown at the same time? Might there not be a race to shut down the service first? The reader is invited to design a thread-safe version of shutdown without making assumptions about finalizeHandle's implementation.

Even if we never successfully execute finalizeHandle, we set the handle to null (except for bad handles, where it might be useful to not clobber the handle reference). Alternatively, we might have decided to throw an exception if we couldn't shut down in the given number of attempts, but for an example it is probably OK since our entire application is going to terminate. A SA Forum service must be able to handle service instances terminating ungracefully in any event.

12.4.1.6 The Big Picture

Normally, the producers and consumers of notifications would be in separate processes. For this example, we run them together. Here is the code that runs each through their respective life-cycles:

public static void main(String[] args) {
    NtfImpl.safSetup();

    GateWatcher watcher = new GateWatcher();
    watcher.initialize();
    GateKeeper gate = new GateKeeper();
    gate.initialize();

    new Thread(watcher).start();
    gate.checkAccess(“Robert”,“Administrator”, “Switch042”, “disable”);
  
    watcher.shutdown();
    gate.shutdown();
}

12.4.2 Integrating AIS Services with Containerized Java Applications

When an existing—and commonly used/supported—interface for Java already exists, and when this overlaps with the functionality provided by an SA Forum service; a key idea is to continue to expose the already available Java standards to the client applications. These interfaces will then be integrated and implemented with SA Forum services using the Java mapping APIs.

These existing standards are generally part of Java EE. Java EE provides an execution container to Java code—a container composed of various services, some of which address cluster-wide and distributed execution.

The integration between the JMX and the SA Forum NTF is one example. The diagram of Figure 12.2 illustrates how a Java application can use JMX to send notifications. The Java application is only using the JMX framework and design patterns. Integration logic is plugged-in between the JMX framework and the notification service. When producing notifications, this logic transforms the JMX notifications to NTF notifications and then forwards them to the SA Forum service. Similar transformation happens in the reverse direction. In this way all notifications in a mixed environment will be connected using the same service for sending and receiving notifications.

Figure 12.2 Integration of JMX and NTF.

12.2

12.4.3 AIS Services in Mixed Language and Mixed Implementation Environments

It is unlikely that a single process is going to mix the usage of Java and C library API's (e.g., through the Java Native Interface). On the other hand and as described above, a single application composed of C as well as Java components (processes) is quite likely to use both interfaces. The likelihood is increased further if one considers a set of applications in a cluster.

When using the Java mapping directly or when implementing integration logic, what needs to be considered in this mixed environment? We have already discussed the provisions made to obtain the underlying C encoding for Java enum values for use when processes need to communicate outside of the SA Forum service channels. Other Java primitive values are clearly identified with the corresponding C types and could be likewise communicated. The communication of complex types has not been addressed in the mapping specifications although some tentative work is underway to define a SA Forum ‘external representation.’

Another data consideration is endian-ness. However, this is a concern even in the C-only world with different machine architectures. Within the SA Forum services, this is handled by the service implementations insuring that data is properly converted as needed (or otherwise well defined for opaque client data). This is below, and is not a concern for, the Java mapping level.

Because the C and Java specifications are at the same level; because the mixed environment scenario was considered early in the mapping; and finally because we expected some Java service APIs to be implemented on top of C language implementations (and vice-versa!) the mapping specifications were written to be lossless in both directions: Java to C and C to Java. This avoids most of the problems that one might encounter in a mixed application language environment.

12.5 Going Further

12.5.1 The Java Mapping Roadmap

As of this writing, five SA Forum services have Java mapping specifications (plus the global mapping package). These have been developed by contributors on an ‘as needed’ basis. The advantage of this is that those working on the Java mappings usually have some direct and early feedback from real-world implementation experience. As well, the services that have Java mappings are, in some ways, the most used.

There is not an explicit roadmap for other service mappings. The ‘as needed’ approach will be retained for the time being. Due to the well-defined mapping guidelines and the experience gained so far, producing a new mapping is now a straightforward exercise.

The C specifications continue to evolve with corrections, clarifications, and a few enhancements. The Java specifications will track these changes with a goal of simultaneous releases of C and Java specifications.

12.5.2 Related Java Standards and Other References

As there did not exist any standardized way to handle HA in Java, we started a new JSR (Java Standardization Request) for this [100]. The purpose of the new JSR is to enable external availability managers, such as SA Forum AMF, to supervise and to control Java runtime entities. Thereby HA can be achieved in a standardized way. Availability Management for Java is not limited to SA Forum AMF but could be integrated with other HA frameworks as well.

Availability Management for Java has the following features and requirements:

  • It provides the means by which an availability manager can control and supervise distributed Java processes and runtime entities, called availability units, within these Java processes. The availability manager controls the lifecycle of the availability units and of the Java processes and it can supervise their health. In case any error is detected, the availability manager may be configured to execute recovery actions.
  • It supports different availability managers. There may be different availability manager with different capabilities. They may, for example, provide advanced error recovery actions such as recovery escalation, and they may support switchovers, when an availability unit in one Java process takes over the work of an availability unit in another Java process.
  • It may be used for Java EE application servers and applications executing on Java EE 5.0 or later. The concepts are also possible to use in a Java Standard Edition environment, if complemented with definitions of the availability units to be controlled.
  • It supports both Java EE applications that are not at all aware of the control of the availability manager and those that are availability aware. Most of the specification is implemented by the application server and current Java EE applications can be supported without any change.

Availability Management for Java can be integrated with SA Forum AMF by implementing a specialized agent. Thereby the application server instances will be mapped to AMF container components and the Java EE deployment units (applications or stand-alone modules) executing in an application server instance can be mapped to AMF contained components. It can be decided at deployment time if a deployment unit shall be mapped to a contained component or if it should be considered as an integrated part of the server instance and thereby included in the container component.

Availability Management for Java has a simpler state model than AMF due to the need to fit other availability managers as well. Basically, the server instances and the deployment units can be instantiated, activated, deactivated, and terminated. When setting the HA state of a component for a component service instance, this will map either to activate or deactivate of the server instance or deployment unit. Setting HA state active will map to activate and setting any of the other HA states or removing the HA state will map to deactivate. The attributes and the transition information (mapped to activation reason or deactivation reason) given when setting the HA state of the component will be forwarded to the deployment unit. The simplified state model implies that to each component it is meaningful to assign only one component service instance.

Container components and contained components must not be collocated in the same service unit and service units containing container service units and service units containing contained components must belong to different service groups. The only redundancy model supported by AMF for service groups containing container components, that is, server instances, is the N-way-active redundancy model. A service group containing contained components, that is, Java EE deployment units, can be associated with any of the redundancy models defined by AMF, except the N-way redundancy model which requires a component capability model not supported by Availability Management for Java.

Framework-invoked health checks are supported if optional health check methods are implemented for the individual deployment units.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.131.47