Copyright (c) 1999, 2000 Silicon Graphics, Inc. All Rights Reserved.
Related: Basic Concepts | Architecture | Association | Category Names |
This document describes how Category(s) and Item(s) are supported in the Rhino infrastructure. The first section discusses the underlying concepts behind Category(s) and Item(s). The second section presents the API for implementing Category(s) and Item(s) at the server side. The last section presents the API to obtain dynamic information about a specific Category and Item at the client side. Typical clients of Category(s) and Item(s) include Rhino UIs, such as Tasks and ItemViews.
An Item is a subclass of AttrBundle. The type of an Item instance is the name (selector) of the Category instance that it belongs to. The selector of an Item instance is the unique name of the instance within the Category instance. For example, a user account Item instance can have selector foo within the type UserAccountCategory and the following Attributes:
string, userName, foo long, userId, 3944Arrays of values can be represented in an Item as follows:
For example, if an Item of type group account has an array of Strings representing names of users that use its group id, this would by represented by an Attribute with key, for example, NUM_USERS that specifies the number of values in the array and a key, for example, USER as the prefix to use. USER0, USER1 ... USER<NUM_USERS - 1> would be the Attribute keys of the actual String values. For example, NUM_USERS = 3, USER0 = foo, USER1 = bar, USER2 = baz.
This is the format that the Association mechanism relies on for parameters referring to the selectors of Item(s) to monitor the relationship between Item(s).
A Category class has a collection of Items. Category is also a subclass of AttrBundle. The type of a Category instance is the constant value Category. The selector is the unique name of the Category instance within the system. For example, the user account Category instance would have selector UserAccountCategory. A Category can have a set of Attributes that apply to all Item(s) of that type. For example, UserAccountCategory instance can have Attributes that store information about whether shadow passwords are in use by a system. Category classes provide API to support monitoring of the Item(s) and Attribute(s) of the specific type and notification of current state and state changes to interested clients.
Clients interested in information about a Category instance do so via a CategoryListener interface. A CategoryListener instance can be registered with a Category for notifications. Upon registration, the CategoryListener instance receives information about the current state of the Category instance. If the state of the Category changes the CategoryListener will receive information about the changes as they occur. Information from Category can be obtained at several granularities.
All client-server communication is asynchronous so that the UI can be responsive to user input and not block waiting for completion of a request to the server. Asynchronous nature is achieved by using a callback model.
The concepts of Category, Item and CategoryListener are implemented in Java at the client-side and in C++ at the server-side. An application developer writes the logic to determine the set of Item(s) (of a specific type) and Category Attribute(s) and to monitor the system for any changes to the Item(s) and Category Attribute(s) in C++, using the server-side API. The application-specific clients, such as specific Tasks or ItemViews are written in Java and use the client-side API to obtain information about Category(s) and Item(s).
All application-specific entities are instances of Items. No subclassing is required. A Category class needs to be subclassed. An instance of the subclass performs application-specific operations to obtain the state of the system and to inform the Category base class of any changes to the state. For example, the UserAccountCategory instance (of Category) would read and monitor the passwd files or NIS maps to monitor user account Item(s) to obtain the current state and detect changes.
When the first CategoryListener is added to a Category instance, the Category base class calls Category::startMonitor(). Category subclasses must override this method to do whatever is necessary to discover existing Item(s) and monitor Item(s) of the specific type. Information about all Item(s) that exist at the time Category::startMonitor() is called should be communicated to the Category base class via Category::addItem() calls. Information about Category attributes should be communicated by the subclass via AttrBundle::setAttr(). The end of the Item(s) and Category Attribute(s) that exist when Category::startMonitor() is called should be communicated to the Category base class via an Category::endExists() call. Any future addition, removal of Item(s) as well as changes to the Item(s) should be communicated to the Category base class via Category::addItem(), Category::removeItem() and Category::changeItem() calls. Information about changes to Category attributes should be communicated by the subclass via AttrBundle::setAttr().
Category also supports methods Category::beginBlockChanges() and Category::endBlockChanges() that can be called by subclasses to indicate the start and end of a block of notifications. Category::replaceItemList() can be called by subclasses when it wants to replace the current list of Item(s) by a new list. The Category base class computes any changes between its previous list and the new "list", updates its list and notifies interested listeners of any changes. None of Category::addItem(), Category::changeItem(), Category::removeItem() or Category::replaceItemList() should be called prior to the call to Category::startMonitor().
A subclass typically makes zero or more Category::addItem() and Category::setAttr() calls, followed by a Category::endExists() call followed by zero or more Category::addItem(), Category::changeItem(), Category::removeItem() and Category::setAttr() calls.
A Category subclass can also inform interested listeners of application-specific error notifications using Category::notifyError(). Error notifications are passed to CategoryErrorListener instances that are registered via Category.addErrorListener().
The anticipated use of information in Category is by the client side code. Thus, the Java implementation of the CategoryListener interface is covered in detail in the Obtaining information about Category(s) and Item(s) on the client-side section. The information in that section can be applied to server-side components requiring information from a Category via the C++ CategoryListener API.
CategoryFactory is the factory class for Category objects. CategoryFactory methods are mostly used by the Category Service, described in sysadmd(1M), to fulfill requests from remote clients. They can also be used by any server-side components that require information from a Category. Category subclasses use the macros defined by CategoryFactory.h.
The steps required to make information about a Category of selector catName available to the rest of the system are detailed below. The Category instance will hold information about Item(s) of type catName
The above steps will allow clients to obtain the Category instance for catName. To avoid Category name clashes, apllications should attach a product specific prefix to their categories. For example, FailSafe Manager and Miser Manager can use a category by name ResourceCategory, to refer to different entities. To avoid name clashes, the two categories could be named fsmgrResourceCategory and msmgrResourceCategory.
Consider, for example, the UserAccountCategory Category. In order to plug-in this Category into the Rhino infrastructure, create a library with the naming convention UserAccountCategory.so, with the entry points described above and install it in /usr/sysadm/category. The Category Service responds to client requests for a Category with selector UserAccountCategory by interfacing with UserAccountCategory.so to obtain information about the user account Item(s) and passes this information to the clients.
Consider a Category named rhexampRhinoExampleCategory which is a collection of Item(s), one Item for each file in a particular directory. The Attributes of each Item correspond to the file name, file permissions and contents of the file. The implementation of this Category uses the fam(1M) API for obtaining the existing Item(s) in the system and monitoring of future changes is given below.
#pragma once #include <sys/types.h> #include <sysadm/fam.h> #include <sysadm/Category.h> #include <sysadm/CategoryFactory.h> namespace rhexamp { using namespace sysadm; SaCATEGORY_REF_DECL(rhexampRhinoExampleCategory); // // rhexampRhinoExampleCategory maintains an Item for each known // RhinoExample. // class rhexampRhinoExampleCategory : public Category { protected: rhexampRhinoExampleCategory(); virtual ~rhexampRhinoExampleCategory(); // Start monitoring the system. virtual void startMonitor(); // Allow CategoryFactory to create us. SaCATEGORY_FRIEND_DEF(rhexampRhinoExampleCategory); private: // Intentionally undefined. rhexampRhinoExampleCategory(const rhexampRhinoExampleCategory&); rhexampRhinoExampleCategory& operator=(const rhexampRhinoExampleCategory&); Item createItem(const char* exampleName); void processFamEvent(FAMEvent& event); static void famInput(void* clientData, int id, int fd); int _inputId; FAMConnection _famConn; FAMRequest _configDir; bool _famStarted; }; } // namespace rhexamp
#include <sys/stat.h> #include <assert.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sysadm/format.h> #include <sysadm/AppContext.h> #include <sysadm/Log.h> #include <rhexamp/RhinoExample.h> #include "rhexampRhinoExampleCategory.h" namespace rhexamp { SaCATEGORY_REF_DEF(rhexampRhinoExampleCategory); // // Constructor. // rhexampRhinoExampleCategory::rhexampRhinoExampleCategory() : Category("rhexampRhinoExampleCategory") { } // // Destructor. // rhexampRhinoExampleCategory::~rhexampRhinoExampleCategory() { if (_famStarted) { FAMClose(&_famConn); AppContext::getAppContext().unregisterMonitor(_inputId); } } // // Item rhexampRhinoExampleCategory::createItem(const char *exampleName) // // Description: // Respond to a FAM event that indicated that a new example has // been created. This also gets called at startup for each // example that already exited. // // Make sure example is valid, and if so put together Example // attributes. // // Parameters: // exampleName Name of the example that showed up. // // Returns: // Newly created item corresponding to "exampleName". // Item rhexampRhinoExampleCategory::createItem(const char *exampleName) { char exampleFile[PATH_MAX]; (void)SaStringFormat(exampleFile, sizeof exampleFile, "%s/%s", RHINO_EXAMPLE_DIR, exampleName); struct stat f; if (stat(exampleFile, &f) == -1) { return Item("", ""); } FILE *fp = fopen(exampleFile, "r"); if (fp == NULL) { return Item("", ""); } char buf[100]; char *type = fgets(buf, sizeof buf, fp); (void)fclose(fp); if (type == NULL) { return Item("", ""); } char *pc = strchr(buf, '\n'); if (pc) { *pc = '\0'; } Item item(getSelector(), exampleName); item.setAttr(Attribute(RHINO_EXAMPLE_NAME, exampleName)); item.setAttr(Attribute(RHINO_EXAMPLE_TYPE, type)); item.setAttr(Attribute(RHINO_EXAMPLE_MODE, (long long)f.st_mode)); return item; } // // void rhexampRhinoExampleCategory::processFamEvent(FAMEvent &event) // // Description: // Process a single FAM event. // // Parameters: // event The event to process. // void rhexampRhinoExampleCategory::processFamEvent(FAMEvent &event) { Log::trace(getSelector(), "Got a fam event"); switch (event.code) { case FAMExists: case FAMCreated: { Item item(createItem(event.filename)); if (item.getSelector() != "") { addItem(item); } } break; case FAMChanged: { Item item(createItem(event.filename)); if (item.getSelector() != "") { changeItem(item); } } break; case FAMDeleted: removeItem(event.filename); break; case FAMEndExist: endExists(); break; } } // // void rhexampRhinoExampleCategory::famInput(void* clientData, int, int) // // Description: // Input callback that gets called when we get a FAM event. // // Parameters: // clientData ClusterCategory* (this is a static method). // void rhexampRhinoExampleCategory::famInput(void* clientData, int, int) { rhexampRhinoExampleCategory* self = (rhexampRhinoExampleCategory*)clientData; FAMEvent event; while (FAMPending(&self->_famConn) == 1) { if (FAMNextEvent(&self->_famConn, &event) != -1) { self->processFamEvent(event); } } } // // void rhexampRhinoExampleCategory::startMonitor() // // Description: // Set up our FAM connection. // // Returns: // 0 if successful, -1 if error. // void rhexampRhinoExampleCategory::startMonitor() { if (FAMOpen(&_famConn) == 0) { _famStarted = true; FAMMonitorDirectory(&_famConn, RHINO_EXAMPLE_DIR, &_configDir, NULL); _inputId = AppContext::getAppContext().registerMonitor( FAMCONNECTION_GETFD(&_famConn), famInput, this); } else { endExists(); } } } // namespace rhexamp
rhexampRhinoExampleCategory.c++ is compiled into a library called rhexampRhinoExampleCategory.so and installed in /usr/sysadm/category. This makes information about rhexampRhinoExampleCategory and its Item(s) available to the rest of the Rhino infrastructure.
All application-specific entities are instances of Item. Further, all application-specific categories are instances of Category. No subclassing is required by the developer of specific application. The steps in obtaining information about Category(s) and Item(s) are:
Category instances are obtained via a HostContext object. When writing Task UI interface, a HostContext object will be available for you from the Task infrastructure. The same applies to writing an ItemView, etc.
Internally, the HostContext object is obtained when a user successfully logs in to a server machine.
If the HostContext object is hostContext, then a client can obtain a handle to "UserAccountCategory" by using the following code:
Category cat = hostContext.getCategory("UserAccountCategory");This is an asynchronous call that returns an handle to the Category before it receives a response from the server. The client can use this handle to add CategoryListener instances for obtaining information. If an error is encountered in communication with the server or loading the specific Category instance requested, this is handled as a fatal connection error by the infrastructure and the client will exit after the error message is acknowledged by the user.
Clients interested in information about a Category instance can create a subclass of CategoryListener and register for notifications by passing a CategoryListener instance to Category.addCategoryListener(). The specific Items of interest are indicated by the NotificationFilter parameter. The NotificationFilter also specifies whether the CategoryListener instance is interested in notifications about the Category attributes. Call Category.removeCategoryListener() to unregister interest in notification.
Category base class notifies registered CategoryListener instances about Item(s) discovered (by subclasses) in the system or Item(s) that are later added via CategoryListener.itemAdded() calls, Item changes via CategoryListener.itemChanged() calls, and Item removal via CategoryListener.itemRemoved(). Notifications about Category Attribute(s) discovered (by subclasses) in the system or Attribute(s) that are later added are via AttrListener.attrAdded() calls, Attribute changes via AttrListener.attrChanged() calls, and Attribute removal via AttrListener.attrRemoved(). When Category.addCategoryListener() is called, Category sends the listener its current list of Item(s) and Attribute(s) via CategoryListener.itemAdded() and AttrListener.attrAdded() calls. End of notification of the current state is signaled by a CategoryListener.endExists() call, if the Category itself has received this notification from its subclasses. Else, CategoryListener.endExists() will be called when Category receives this notification from its subclasses.
A CategoryListener can expect to receive zero or more itemAdded() and attrAdded() calls, followed by an endExists() call followed by zero or more addItem(), changeItem(), removeItem(), attrAdded(), attrChanged() and attrRemoved() calls. The endExists() call signals that the Category has communicated the entire set of Item(s) discovered in the system to the CategoryListener.
Category base class passes Category.beginBlockChanges() and Category.endBlockChanges() notifications to identically named methods on registered CategoryListener instances.
The following code illustrates how information can be obtained about Category(s) and Item(s) on the client-side. A CategoryAdapter is a default implementation of CategoryListener. The code below can be used in the Create A User Account Task to verify that the user account name does not already exist on the server.
Category cat = hostContext.getCategory("UserAccountCategory"); cat.addCategoryListener( new CategoryAdapter() { public void itemAdded(Item item) { if (item.getString(NAME).equals(userInputName)) { // UserAccount with name userInputName already exists // Steps to signal error ... } } public void endExists() { // UserAccount with name userInputName does not exist // Steps to signal successful verification ... } }, NotificationFilter.ALL_ITEMS);
The above method can be used when the user account name is not the same as the selector of the Item. If the client has the selector (unique name) of the Item, then there are two other ways of determining if the user account that was specified already exists. The following code uses a CategoryListener with a NotificationFilter that expresses interest in only one Item with a specified selector. If an Item with that selector exists, Category will pass the Item state to the CategoryListener via CategoryListener.itemAdded() followed by an CategoryListener.endExists(). If an Item with that selector does not exist it will send an CategoryListener.endExists() notification.
NotificationFilter filter = new NotificationFilter(); filter.monitorItem(selector); cat.addCategoryListener( new CategoryAdapter() { public void itemAdded(Item item) { // UserAccount with unique name "selector" already exists // Steps to signal error ... } public void endExists() { // UserAccount with unique name "selector" does not exist // Steps to signal successful verification ... } }, filter);
Another way of obtaining an Item if the client has the selector is to use the Category.getItem() API, passing a ResultListener to Category.getItem(). Category.getItem() calls the succeeded method of the ResultListener if an Item with the specified selector exists in the system. Use the getResult() method of ResultEvent to get the Item. The Object returned by getResult() should be cast to an Item. getItem() calls the failed() method of the ResultListener if an Item with the specified selector does not exist in the system.
cat.getItem(selector, new ResultListener() { public void succeeded(ResultEvent event) { // UserAccount with unique name "selector" already exists // Steps to signal error ... } public void failed(ResultEvent event) { // UserAccount with unique name "selector" does not exist // Steps to signal successful verification ... } });Similar to Category.getItem(), Category.getItemCount() can be used to get the number of Items in a Category and Category.getItemList() can be used to get the list of Items in a Category.