Copyright (c) 1999, 2000 Silicon Graphics, Inc. All Rights Reserved.
Overview | Basic Concepts | GUI Components | Architecture | How To Write An App |
Outlined below are the basic steps involved in writing a Task for Rhino. Unless otherwise noted, the code examples below are for a Define User Account Task.
Before proceeding, you should familiarize yourself with Basic Concepts and at least look over the GUI Components, Architecture, and Task Internals documents.
The Task properties file is required to exist in the same directory as the Task class. The name of the properties file must be the Task class name followed by "P.properties". For example, a Task subclass named "DefineUserAccountTask" must have a properties file named "DefineUserAccountTaskP.properties".
The Task properties file contains static information about the Task, including the Task title, privilege list, and whether or not the Task accepts operands. The Task properties file also contains User-visible labels and messages, and interface characteristics such as fonts, colors, and sizes.
Below is a sample of what the DefineUserAccountTaskP.properties file might contain. Letters have been used to identify the lines to distinguish them from the Java code examples that follow.
A: # B: # Properties file for the Define User Account Task C: # D: Task.shortName = Define User Account E: Task.longName = Define a new User Account F: Task.keywords = define new add user account login home directory shell G: H: [...] I: J: # K: # DO NOT LOCALIZE BELOW THIS LINE L: # M: Task.privList0 = addUser N: Task.privList1 = listUsers O: Task.publicData0 = userName P: Task.ProductAttributes0 = com.sgi.psa
The Task subclass is the main entry point to the Task. Its functions are to verify prerequisites, initialize TaskData, coordinate the Task interface(s), and perform the Task operation when the User presses the OK button.
1: private TaskContext _taskContext; 2: private TaskData _taskData; 3: private ResourceStack _rs; 4: private HostContext _hc; 5: private Category _userCategory; 6: 7: private static final String USER_NAME = "userName"; 8: 9: public DefineUserAccountTask(TaskContext taskContext) { 10: super(taskContext); 11: 12: _taskContext = taskContext; 13: _taskData = _taskContext.getTaskData(); 14: _rs = _taskContext.getResourceStack(); 15: _hc = _taskContext.getHostContext(); 16: 17: _taskData.setString(USER_NAME, ""); 18: _taskContext.appendTaskDataVerifier(USER_NAME, new 19: TaskDataVerifier() { 20: public void dataOK(int browseFlag, Object context, 21: ResultListener listener) { 22: verifyUserName(browseFlag, context, listener); 23: } 24: }); 25: 26: [...] 27: } 28: 29: public void verifyUserName(int browseFlag, Object context, 30: ResultListener listener) { 31: String userName = _taskData.getString(USER_NAME); 32: ResultEvent result = new ResultEvent(this); 33: 34: if (userName.length() == 0) { 35: if (browseFlag) { 36: listener.succeeded(result); 37: } else { 38: result.setReason( 39: _rs.getString("Error.missingUserName")); 40: listener.failed(result); 41: } 42: } 43: 44: // Check user name for syntactic problems (length, 45: // unprintable characters, etc.). 46: [...] 47: 48: // Check for existing user name 49: verifyUniqueName(userName, listener); 50: } 51: 52: public void verifyUniqueName(final String userName, 53: final ResultListener listener) { 54: _userCategory = _hc.getCategory("UserAccountCategory"); 55: _userCategory.getItem(userName, new ResultListener() { 56: public void succeeded(ResultEvent event) { 57: event.setReason(MessageFormat.format( 58: _rs.getString("Error.userExists"), 59: new Object[] { userName } )); 60: listener.failed(event); 61: } 62: 63: public void failed(ResultEvent event) { 64: listener.succeeded(event); 65: } 66: }); 67: }
68: public void registerInterfaces() { 69: setForm(new DefineUserAccountForm(_taskContext)); 70: setGuide(new DefineUserAccountGuide(_taskContext)); 71: }
Although the DefineUserAccountTask does not take operands, below is some sample code for the ModifyUserAccountTask, which accepts a single User Account as an operand.
72: public void setOperands(vector Operands) 73: throws TaskInitFailedException { 74: if (operands == null || operands.size() == 0) { 75: // The operands are optional 76: return; 77: } 78: 79: if (operands.size() > 1) { 80: throw new TaskInitFailedException( 81: _rs.getString("Error.tooManyOperands"), 82: TaskInitFailedException.INVALID_OPERANDS); 83: } 84: 85: // In the future, operands could be Items that are 86: // dropped via the drag and drop interface. In 87: // Rhino 1.0, however, we can only pass String operands 88: // at this time. 89: if (!(operands.elementAt(0) instanceof String)) { 90: throw new TaskInitFailedException( 91: _rs.getString("Error.invalidOperandType"), 92: TaskInitFailedException.INVALID_OPERANDS); 93: } 94: 95: // Store the operand for later use 96: _taskData.setString(USER_NAME, (String)operands.elementAt(0)); 97: }
For illustrative purposes, imagine that the Define User Account Task allows the proposed User name be set via setTaskDataAttr() (note that "userName" was declared as a public TaskData attribute in the Task properties file above). A prerequiste could be that the User name may not already exist on the server. Below is the code that would be used to verify the User name as a prerequisite. Note that the "userName" TaskDataVerifier, defined in lines 18-24 of the constructor, is being referenced here by name.
98: public void verifyPrereqsBeforeCheckPrivs(ResultListener listener) { 99: _taskContext.dataOK(USER_NAME, TaskDataVerifier.MAY_BE_EMPTY, 100: null, listener); 101: }
Privileged verification requires a call to the version of Task.runPriv() that takes a ResultListener. A generic example follows.
102: public void verifyPrereqsAfterCheckPrivs(ResultListener listener) { 103: TaskData CLIArgs = new TaskData(); 104: CLIArgs.setAttr(_taskData.getAttr("prereqData")); 105: [...] 106: 107: OutputStream stream = runPriv("listUsers", CLIArgs, listener); 108: 109: try { 110: stream.close(); 111: } catch (IOException ex) { 112: Log.debug("DefineUserAccountTask", 113: "unable to close listUsers stream"); 114: } 115: }
116: public void ok() { 117: TaskData CLIArgs = new TaskData(); 118: CLIArgs.setAttr(_taskData.getAttr(USER_NAME)); 119: [...] 120: 121: OutputStream stream = runPriv("addUser", CLIArgs); 122: 123: try { 124: stream.close(); 125: } catch (IOException ex) { 126: Log.debug("DefineUserAccountTask", 127: "unable to close OutputStream from runPriv"); 128: } 129: }
130: public ResultViewPanel createResultViewPanel() { 131: return new ResultViewPanel(_taskContext, _rs, 132: "UserAccountCategory", 133: _rs.getString( 134: "DefineUserAccountTask.succeeded")); 135: }
The Form subclass sets up the visible components for the Form interface of a Task, and then binds the components to the appropriate TaskData attributes.
1:public class DefineUserAccountForm extends Form { 2: 3: private TaskContext _taskContext; 4: private TaskData _taskData; 5: 6: public void DefineUserAccountForm(TaskContext taskContext) { 7: super(taskContext); 8: 9: _taskContext = taskContext; 10: _taskData = _taskContext.getTaskData(); 11: }
12: public void createUI() { 13: super.createUI(); 14: 15: ResourceStack rs = _taskContext.getResourceStack(); 16: 17: FilteredTextField userName = 18: new FilteredTextField( 19: rs.getInt("DefineUserAccountForm.userNameFieldWidth"), 20: FilteredTextField.BEEP); 21: 22: addTaskComponent(userName, 23: rs.getString( 24: "DefineUserAccountForm.userNameLabel")); 25: StringJTextComponentBinder.bind(_taskData, USER_NAME, userName); 26: 27: // More visible components would be added here 28: [...] 29: }
The Guide subclass is slightly more complicated than the Form interface because the developer breaks the interface into multiple pages, each of which may have its own TaskDataVerifier that gets called when the User presses the Next button to leave that page.
1:public class DefineUserAccountGuide extends Guide { 2: 3: private TaskContext _taskContext; 4: private TaskData _taskData; 5: private ResourceStack _rs; 6: 7: public DefineUserAccountGuide(TaskContext taskContext) { 8: super(taskContext); 9: 10: _taskContext = taskContext; 11: _taskData = _taskContext.getTaskData(); 12: _rs = _taskContext.getResourceStack(); 13: }
The verification for a GuidePage is called when the User presses the Next button. The example below is very simple because there is only one input field on the page. TaskContext has additional versions of the dataOK() method that allow the developer to chain together a set of TaskDataVerifiers. If a verifier fails, the default action that the Task will take is to post an error dialog containing the reason field of the ResultEvent returned by the verifier. This error dialog will contain two buttons - one to stay on the current page and fix the error, and the other to ignore the error for now and go on to the next page. This second button gives users the ability to see what will appear on subsequent pages without having to fill in the current page. If your task does not support going to the next page until all the verifiers for the current page succeed (for example, if the input on current page determines which page the user sees next), then use GuidePage's setAllowTurnPageOnError method to tell the infrastructure to not let the user turn the page until all verifiers on the current page succeed.
14: public void registerPages() { 15: GuidePage userNamePage = 16: new GuidePage(_taskContext, "UserNamePage") { 17: public void createUI() { 18: super.createUI(); 19: 20: FilteredTextField userName = 21: new FilteredTextField(_rs.getInt( 22: "DefineUserAccountForm.userNameFieldWidth"), 23: FilteredTextField.BEEP); 24: 25: addTaskComponent(userName, 26: rs.getString("DefineUserAccountForm.userNameLabel")); 27: StringJTextComponentBinder.bind(_taskData, USER_NAME, 28: userName); 29: } 30: } 31: 32: userNamePage.setVerifier(new TaskDataVerifier() { 33: public void dataOK(final int browseFlag, final Object context, 34: final ResultListener linstener) { 35: _taskContext.dataOK(USER_NAME, browseFlag, context, 36: listener); 37: } 38: }); 39: 40: appendPage(userNamePage); 41: 42: // Additional pages would be added here 43: [...] 44: }
% setenv CLASSPATH \ /usr/sysadm/java/swingall.jar:/usr/sysadm/java/sysadm.jar:{task workarea} % java com.sgi.sysadm.manager.RunTask {package}.{taskname} [operands]
See the RunTask documentation for a list of available runtime options.