oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Sweetening Your Xgrid with Cocoa
Pages: 1, 2, 3, 4, 5

Submitting a Job

Whenever an object's attribute changes, and it is being observed with KVO, the observer's observeValueForKeyPath:ofObject:change:context: method is invoked. This method needs to identify which object is calling, and take appropriate action.

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
    change:(NSDictionary *)change 
    context:(void *)context {
    if ( object == [self controller] ) {
        [self controllerStateDidChange];
    else if ( object == [self jobSubmissionMonitor] ) {
        [self jobSubmissionStateDidChange];
    else if ( object == [self outputRetrievalMonitor] ) {
        [self outputRetrievalStateDidChange];
    else if ( [object isKindOfClass:[XGJob class]] ) {
        [self jobStateDidChange:object];
    else if ( [object isKindOfClass:[XGGrid class]] ) {
        [self gridStateDidChange:object];

It may be tempting to include the code for dealing with the various state changes directly in this method, but in practice this leads to unreadable code. It is better to factorize your code as shown, keeping the observeValueForKeyPath:ofObject:change:context: simple, and calling other methods to do the dirty work.

When the XGController's state attribute changes, the controllerStateDidChange method is invoked. This simple little method checks if the new state of the XGController is equal to XGResourceStateAvailable, and if it is, submits a job by calling submitJob.

-(void)controllerStateDidChange {
    XGController *conn = [self controller];
    if ( [conn state] == XGResourceStateAvailable ) [self submitJob];

Submitting a job via XGridFoundation bears a strong resemblance to how it is done in batch mode with the xgrid command-line tool. You have to create a property list dictionary with the job specification. You can do this by reading in a property list file, or you can combine objects of property list classes like NSArray, NSString, and NSDictionary directly in the code. The latter approach is taken by Central Command.

-(void)submitJob {
    // Create the job specification
    NSArray *commandAndArgs = [jobCommand componentsSeparatedByString:@" "];
    NSString *command = [commandAndArgs objectAtIndex:0];
    NSArray *commandArgs = [commandAndArgs subarrayWithRange:NSMakeRange(1,[commandAndArgs count]-1)];
    NSDictionary *commandDict = 
        [NSDictionary dictionaryWithObjectsAndKeys:
            commandArgs,    XGJobSpecificationArgumentsKey, 
            command,        XGJobSpecificationCommandKey, nil];
    NSDictionary *tasksDict = 
        [NSDictionary dictionaryWithObject:commandDict forKey:@"0"];
    NSDictionary *jobSpecification = 
        [NSDictionary dictionaryWithObjectsAndKeys:
            @"com.maniacalextent.centralcommand",   XGJobSpecificationApplicationIdentifierKey,
            @"Central Command",                     XGJobSpecificationNameKey,
            tasksDict,                              XGJobSpecificationTaskSpecificationsKey,
    // Submit job, and beginning monitoring.
    XGActionMonitor *newJobSubmissionMonitor = 
        [controller performSubmitJobActionWithJobSpecification:jobSpecification gridIdentifier:nil];
    [[self jobSubmissionMonitor] removeObserver:self forKeyPath:@"outcome"]; // Stop observing previous monitor
    [newJobSubmissionMonitor addObserver:self forKeyPath:@"outcome" options:0 context:NULL];
    [self setJobSubmissionMonitor:newJobSubmissionMonitor]; 

The first half of this method combines objects into a property list tree. If you were to write this out in property list format, it would look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" 

<plist version="1.0">
                <string>Central Command</string>

The property list includes a cal command, but Central Command actually determines the command and its arguments at runtime from the entries made by the user, as you can see from the code above.

The second half of the submitJob method requests the XGController to submit a job with the specification given, by invoking performSubmitJobActionWithJobSpecification:gridIdentifier:. Setting the grid identifier to nil causes the Xgrid controller's default grid to be used. The performSubmitJobActionWithJobSpecification:gridIdentifier: returns an XGActionMonitor object, which is observed using KVO to determine when its outcome attribute changes. When this happens, observeValueForKeyPath:ofObject:change:context: invokes the jobSubmissionStateDidChange method.

The jobSubmissionStateDidChange method checks the value of the XGActionMonitor outcome attribute; if a failure occurred, it is reported to the user, and if the job submission was successful, it retrieves the job's identifier from the results dictionary of the action monitor, and invokes a method that begins to monitor the job's progress.

-(void)jobSubmissionStateDidChange {
    XGActionMonitor *monitor = [self jobSubmissionMonitor];
    NSAlert *alert;
    NSString *jobId;
    switch ( [monitor outcome] ) {
        case XGActionMonitorOutcomeFailure:
            [self setRunning:NO];
            alert = [NSAlert alertWithError:[monitor error]];
            [alert runModal];
        case XGActionMonitorOutcomeSuccess:
            jobId = [[monitor results] objectForKey:@"jobIdentifier"];
            [self setJobIdentifier:jobId];
            [self attemptToMonitorJob];

The method attemptToMonitorJob may sound like it was christened when the developer was feeling particularly pessimistic. Afterall, why couldn't you just start monitoring the job, and forego the "attempt"? It turns out that even when a job is successfully submitted, the XGGrid in which it runs may not be fully up-to-date, and may not yet know of the existence of the job. The attemptToMonitorJob method is thus designed to be invoked multiple times, until conditions are such that the XGJob can be retrieved from the XGGrid:

-(void)attemptToMonitorJob {
    XGGrid *grid = [[self controller] defaultGrid];
    XGJob *job = [grid jobForIdentifier:[self jobIdentifier]];
    if ( nil == job ) { 
        // Job has not been added to the grid yet, so monitor the grid until it has
        [grid addObserver:self forKeyPath:@"jobs" options:0 context:NULL];
    else {
        // Job is available from grid, so start observing it, and stop observing the grid
        [job addObserver:self forKeyPath:@"state" options:0 context:NULL];  
        [grid removeObserver:self forKeyPath:@"jobs"]; 

If the XGJob is not yet associated with the default grid, the XGGrid method jobForIdentifier: will return nil. If this happens, KVO is used to monitor the state of the jobs attribute of the XGGrid, which is an array of the XGJob objects under the control of the grid. When the jobs array changes, observeValueForKeyPath:ofObject:change:context: reinvokes attemptToMonitorJob, to see if the desired XGJob has become available. It does this until jobForIdentifier returns a non-nil value, at which point KVO is used to start observing the XGJob's state attribute.

Pages: 1, 2, 3, 4, 5

Next Pagearrow