ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using Jini to Build a Catastrophe-Resistant System, Part 2
Pages: 1, 2, 3

Now, you could build such a system on top of any peer-to-peer system, and I'm sure, now that the idea is out there, someone will build a really great one. The main point of this example is to show you how simple it can be to build an app takes advantage of the inherent 911-proof nature of the COS system.



Within our TaskTrackerEntry we define a bunch of statuses:

    /** the task has not yet been assigned to anyone. **/
    public static final int STATUS_OPEN = 0;

    /** the task has been assigned to someone. use {@link #getCurrentOwner}
     *  to see who it has been assigned to. **/
    public static final int STATUS_ASSIGNED = 1;

    /** a person has accepted the task. **/
    public static final int STATUS_ACCEPTED = 2;

    /** a person thinks they have fixed their task. **/
    public static final int STATUS_COMPLETED = 3;

    /** a tester has tested the fixed task and the test passed. **/
    public static final int STATUS_TESTED = 4;

    /** the task is done with. **/
    public static final int STATUS_DONE = 5;

The rest of the Entry is taken up with properties such as

    public String mProjectName;
    public Integer mTaskNumber;
    public Long mReplaces;
    public Vector mDependencies;
    public Vector mSeeAlso;
    public String mName;
    public Integer mPriority;
    public Integer mUrgency;
    public String mCategory;
    public String mSubCategory;
    public Integer mStatus;
    public String mCurrentOwner;
    public String mManager;
    public Vector mPastOwners;
    public String mBlurb;
    public String mTestDescription;
    public Vector mMessages; 
    public Date mFinalised;
    public Date mEtc;

For completeness, the entry includes any associated getters and setters, and a few utility methods such as:

    public void done() {
        // the task has been COMPLETED and TESTED
        mStatus = new Integer(STATUS_DONE);
        mFinalised = new Date();
    }

    public void accepted(Date etc) {
        mStatus = new Integer(STATUS_ACCEPTED);
        mEtc = etc;
    }

and

    public void assignToOwner(String owner) {
    
        if (owner == null) {
            return; 
        }
    
        if (mCurrentOwner.equals(owner)) {
            return;
        }

        if (mCurrentOwner != null) {
            if (mPastOwners == null) {
                mPastOwners = new Vector();
            }
            mPastOwners.add(mCurrentOwner);
        }
        mCurrentOwner = owner;
        if (mCurrentOwner.equals(EMPTY_STRING)) {
            mStatus = new Integer(STATUS_OPEN);
        }
    }

In order to satisfy the requirements of the Archivable interface we must fill in the following methods:

    public boolean archive(SpaceConnectable spconn, Connection jdbcconn)
      throws CriticalObjectStoreException;
      
    public boolean extract(SpaceConnectable spconn, Connection jdbcconn)
      throws CriticalObjectStoreException;

Karen, the COS implementation of the Archivist service, has two service modes, archive and extract. When archive is called, the service is archiving the entry to a JDBC database at jdbcconn. Your entry should provide the specific detail needed to perform this archiving. extract is called when Karen is extracting the entry from a JDBC database and inserting it as a fully formed entry into the JavaSpace. In the case of the TaskTracker, for now we will just return true from these methods until we are ready to fill them in.

Quite a bit of the universal information in our TaskTracker is contained in a lot of small lists, so we will use a ListEntry to help us out a bit.

public class ListEntry extends CosEntry implements Archivable

Each list entry has two public properties:

    /** the name of this list */
    public String mListname;

    /** the list. **/
    public Vector mList;

As vectors get larger, they get harder to serialize and transport. With large lists, the cost of the RMI transactions makes them impractical. In this case, one can use more comprehensive techniques to message between the client, the COS, and any underlying database that stores the data. Messages in the form of SQL statements can be passed instead of the objects themselves.

The list includes an equals method to compare two lists, and methods to add items, remove items, and so forth. The ListEntry is, in reality, a named shared vector and is ideal for small lists.

From this basic ListEntry we make a UserListEntry for short lists of the user's buddies, tasks they are working on, and tasks they've reported.

The client consists of a main application ClientApp that extends COS' AbstractClientApp and a bunch of Swing GUI JFrames for login, task lists, task editing, and so forth. Our client is 99% user interface.

The main ClientApp uses property change events to notify the various JFrames of changes, and registers a CosEventListener from the COS for changes to the global shared list of projects, the users's buddies, tasks, and so forth.

As an example, it is useful to work through what happens when a new task is created in the GUI. Every frame, when created, is passed the ClientApp, providing a local ApplicationContext. The New Task button's action listener creates a new TaskTrackerEntry locally and passes it to ClientApp.newTask(task). newTask adds a TaskTrackerEntry to the COS.

This involves the following train of logic:

  1. Create a transaction to wrap the entire creation of the task.
  2. Create the task, thus returning the task's universally unique ID.
  3. Examine the project name that came with this task.
    1. Does it exist in our global list of projects? Does the global list of projects exist? If it doesn't, we had best make one and add it to the system.
    2. If the supplied project doesn't exist in the current system, add it to the global project list.
  4. Examine the category that came with this task.
    1. Does it exist in our global list of tasks? Does the global list of tasks exist?
    2. If it doesn't, we had best make one and add it to the system.
    3. If the supplied category doesn't exist in the current system, add it to the global category list.
  5. Examine the user assigned to the task. Do we know him or her? Do we even have a buddy list? If we don't, then create it.
  6. If the user is not already in your buddy list, then add it.
  7. Get that user's list of tasks and add this task.
  8. If the list doesn't exist, you must create it. This way when the user first logs in he or she can have a task already assigned.
  9. Then, once again, for the list of tasks the user is working on.
  10. Commit the transaction if everything went smoothly. Abort if not.

In order to simplify our code we have written a small private utility method called addItemToGlobalList. Happily, this method provides an excellent example of the patterns used for writing COS code.


Example 1. addItemToGlobalList

/**
 *      addItemToGlobalList first checks to see if the named list exists.
 *      if it does exist then we check to see if the String item is contained 
 *      by the list.
 *      if it doesn't exist we build a list of that name, and add the item to it,
 *      then cos.create it.
 *      @param item the String we are seeking to add to the list.
 *      @param listname the name of the list
 *      @param tran the transaction covering this operation.
 *      @return true if all went okay, false if not. 
 *          you should abort the transaction if the result of this operation is false.
 */
private boolean addItemToGlobalList(String item,String listname, Transaction tran) {
    ListEntry template = new ListEntry();
    template.mListname = listname;
    
    ListEntry known;
    
    try {
        known = (ListEntry)mCosConnector.retrieve(template,tran);
    } catch (GeneralCosException e) {
        Messenger.logErr("GeneralCosException: Can't retrieve the list \""+listname+"\".
          "+e.getMessage());
        return false;
    }

    if (known == null) {
            // so create a default list and pop it into the cos.
        known = (ListEntry)template.clone();
        known.add(item);
        try {
            SpaceID kcid = mCosConnector.create(known,tran,getApplicationName());
            Messenger.debug("created a list of projects with only 
                one project, \""+item+"\".");
        } catch (GeneralCosException e) {
            Messenger.logErr("GeneralCosException: Can't create a default list of known 
              projects. "+e.getMessage());
            return false;
        }
    }
    
    Vector p = known.getList();
    
    try{
        if (p == null) {
            Messenger.logErr("Strange behaviour:  The List exists 
               but is empty.  It should always have 1 item in it at least.");
            known.add(item);
            mCosConnector.update(known,tran);
        } else {
            if (!(p.contains(item))) {
                known.add(item);
                mCosConnector.update(known,tran);
            }
        }
    } catch (GeneralCosException e) {
        Messenger.logErr("GeneralCosException: Can't update the 
          list of known categories. "+e.getMessage());
        return false;
    }
    
    return true;
}

The pattern is as simple as the general pattern for any Jini application; specify your template and perform an associative match to get the group of things you wish to examine in more detail.

By wrapping eveything in a transaction, if any part of the process fails, the whole thing fails.

Pages: 1, 2, 3

Next Pagearrow