Copyright (c) 1999, 2000 Silicon Graphics, Inc. All Rights Reserved.
Overview | Basic Concepts | GUI Components | Architecture | How To Write An App |
This document covers topics of interest to Task writers. See How To Write a Task for the steps in writing a Task.
The static information about a Task is stored in a text file called a properties file, which is similar to an X Windows app-defaults file. The use of properties files for Task writers will be covered in more depth in a later section of this document.
The TaskLoader class is used to implement the two-step loading process. A Task client that wishes to display a Task icon and link but not immediately launch the Task instantiates a TaskLoader, passing in the HostContext and CLASSPATH relative name of the Task. When the User clicks on the link to launch the Task, the client calls TaskLoader.loadTask() to load the Task class and initialize the Task instance.
The TaskLoader.loadTask() method goes through several steps, in order, to initialize a Task and verify that it is ready to run. This sequence is important to Task writers who need to know when each Task method is called during the initialization phase.
One component of TaskContext that is important to Task writers is TaskData. TaskData is a set of key/value pairs, also called attributes, representing the information entered by the User as well as other Task state. TaskData can be used to share information among different input components within a Task, as well as among different Tasks in a session. The use of TaskData will be covered in greater detail below.
Product Attributes are stored in the HostContext so that they can be shared by all components in a given session. When the Product Attributes are loaded for the first time, a product-specific plugin is invoked to set the attributes. The plugin may bring up a Frame that requests information from the User. The attribute values are then copied to the TaskData of the Task. Subsequent requests to load Product Attributes will not bring up a Frame, but will simply copy the attribute values cached in the HostContext into the TaskData of the requestor.
Not all TaskData attributes may be set by Task clients. Unless a Task has declared an attribute key public in its properties file, an attempt to call Task.setTaskDataAttr() or Task.getTaskDataAttr() on that attribute will cause the Task to exit with an assertion failure. This mechanism is in place to hide implementaiton details from Task clients. See the Task API documentation for more details about the Task.PUBLIC_DATA property.
Because operands may be passed to Tasks by a class with no specific knowledge about the Task (for example, a TaskShelf), no ordering of operands should be assumed or required by the Task. See the Task API documentation for more details about operands.
The principle behind verifying prerequisites is to detect error conditions as early as possible. For example, a Task that requires special system software to be installed should check the system for that software at this stage of Task loading. It is extremely annoying for Users to enter data and then find out that the system is not in a state to perform the Task.
TaskLoader calls three different verification methods. This three stage process is again aimed at providing error feedback to the User as early as possible.
Stage One:
Task.verifyPrereqsBeforeCheckPrivs()
This is the stage where most verification should occur. Only checks
that require privileges, such as accessing read-protected files,
should be deferred to the third stage.
Stage Two:
Task.checkPrivs()
Task.checkPrivs() automatically checks the privileges that are defined
in the properties file of the Task. If the User does not have the
required privileges, s/he will be asked to enter the root password to
continue. See the Task API documentation for Task.checkPrivs() and
Task.PRIV_LIST for more details.
Stage Three:
Task.verifyPrereqsAfterCheckPrivs()
This final verification stage is optional, but is provided for those rare
Tasks that need privileges to fully verify that the Task is ready to
run. For example, Tasks that require access to read-protected files
will need to have privileges before being able to verify that the Task
prerequisites are met.
Because Tasks extend the Swing JPanel class, they can be displayed within an existing Frame. However, it is more common to create a new Frame for a Task that has been launched. The TaskFrame class is the canonical container for Tasks. TaskFrame takes care of keeping the Frame title in sync with the state of the Task as well as posting ResultView frames and disposing of the TaskFrame when the Task has been completed or cancelled. A single TaskFrame can even be used to display multiple Tasks sequentially. See the TaskFrame API documentation for more details.
When a Task is made visible for the first time, it calls the method Task.registerInterfaces(), which must be implemented by the Task subclass. registerInterfaces() should create the Form interface and/or Guide interface classes of the Task, and then call Task.setForm() and/or Task.setGuide() to register those interfaces.
At this point there are still no visible components. After the Task subclass has registered its interface(s), the Task must then decide which interface to display. If only one interface has been registered, then the choice is obvious. Otherwise, the property Task.PREFERRED_UI is used to determine which interface should be displayed. See the Task API documentation for more details about the preferred interface.
Next, Task calls either Form.showForm() or Guide.showGuide(), as appropriate.
When the User presses "OK", Task calls TaskContext.allDataOK(), which fires each of the registered TaskDataVerifiers in the order they were registered. If a verifier fails, the verification process stops and an error dialog is displayed. After the dialog is dismissed, the User may either press the Cancel button or s/he can modify the data entered and press the OK button again.
If the Task operation(s) succeed(s), Task.taskSucceeded() must be called. Task.taskSucceeded() notifies TaskDoneListeners that the Task was successful. Typically this leads to the TaskFrame being closed and a ResultViewPanel being displayed. Some versions of Task.runPriv() call Task.succeeded() automatically, while others require that the Task subclass make the call. See the API documentation for details.
If the Task operation(s) fail(s), Task.taskFailed() must be called. Task.taskFailed() posts an error dialog. After the dialog is dismissed, the User may either press the Cancel button or s/he can modify the data entered and press the OK button again. Some versions of Task.runPriv() call Task.failed() automatically, while others require that the Task subclass make the call. See the API documentation for details.
The ResultListener succeeded() and failed() methods are each passed a ResultEvent object. In general, the result of a successful asynchronous call can be retrieved by ResultEvent.getResult() while the reason for failure of a failed asynchronous call can be retrieved by ResultEvent.getReason(). However, each asynchronous method will have its own protocol for handling success and failure, so check the API documentation for a method before making assumptions.
When an asynchronous call fails, the ResultListener that initiated the call is typically responsible for posting an error dialog. Sometimes asynchronous calls are chained together, in which case multiple ResultListeners are involved. The general rule is that the ResultListener first in the call chain is responsible for posting an error dialog. For example, the verifyPrereqsBeforeCheckPrivs() method is passed a ResultListener. None of the code within that method or called by that method should be responsible for posting an error dialog. The reason for error should be placed in a ResultEvent object via ResultEvent.setReason() and passed to ResultListener.failed().
The ResourceStack class was developed to allow the Rhino architecture to supply default property values while providing an opportunity for clients to override those values. The ResourceStack is a stack of ResourceBundles, where a search for a property starts at the top of the stack and works downward until the property is found. Clients can add new properties files to the stack by using the ResourceStack.pushBundle() method. Any property in the most-recently-pushed properties file will override all other properties of the same name lower in the stack. ResourceStack also provides an API that simplifies getting properties of different types, including numeric values, fonts, colors, and String arrays.