December 31, 2005

Web Services in Objective C Part 3

This is my caller file. It is a .mm file not a .m file so I can micx C++ and Objective C. Sorry for my newbieness.

This is SakaiServer.mm - look in the doLogin method.

Again, Creative Commons Public Domain blah blah.

//
// SakaiServer.m
// SakaiDesktop
//
// Created by Charles Severance on 12/29/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//

// Needed for SoapUtils
#include

#import "SakaiServer.h"

#import "SoapUtils.h";

#define checkErr(err) \
while (err != noErr) { fprintf(stderr, "Failed at line %d, error %d\n", __LINE__, err); exit(-1); }

@implementation SakaiServer

- (void) init
{
host = nil;
title = nil;
xmlDoc = nil;
account = nil;
password = nil;
session = nil;

// Initially we allow logins, and have neither tried, nor failed logging in
suppressLogin = NO;
goodLogin = NO;
triedLogin = NO;
}

- (IBAction) setHost:(NSString *) theHost
{
host = [theHost retain];
}

- (IBAction) setTitle:(NSString *) theTitle
{
title = [theTitle retain];
}

- (IBAction) setAccount:(NSString *) theAccount
{
account = theAccount;
}

- (IBAction) setPassword:(NSString *) thePassword
{
password = thePassword;
}

- (IBAction) setSuppressLogin:(BOOL) theValue
{
suppressLogin = theValue;
}

- (NSString *)title
{
return title;
}

- (NSString *)host
{
return host;
}

- (NSString *)account
{
return account;
}

// Instead of handing back the password, we simply allow our caller
// to know if it is nil
- (BOOL) passwordIsNil
{
if ( password == nil ) return YES;
return NO;
}

- (BOOL) suppressLogin
{
return suppressLogin;
}

- (BOOL) goodLogin
{
return goodLogin;
}


- (BOOL) triedLogin
{
return triedLogin;
}

// Log in to a Sakai Server
- (BOOL) doLogin :(NSTextField *) statusField
{

if ( triedLogin == YES ) {
[statusField setStringValue:@"Prior login failed..."];
return goodLogin;
}

triedLogin = YES;
goodLogin = NO;

OSErr err;
AEDesc soapParms;
err = createSoapParameters(&soapParms);
checkErr(err);

// Build up Soap Parameters - Ignore Errors while building up parameters
err = addSoapParameter(&soapParms, "id", typeChar, [account cString]);
err = addSoapParameter(&soapParms, "pw", typeChar, [password cString]);

char newSession[1024];
char errorMessage[1024];
[statusField setStringValue:@"Making web service call for login..."];
err = makeSoapCall([[host stringByAppendingString:@"/sakai-axis/SakaiLogin.jws"] cString],
"login", &soapParms, newSession, sizeof(newSession), errorMessage, sizeof(errorMessage), false, true);
AEDisposeDesc(&soapParms);

if ( err == noErr ) {
fprintf(stderr,"Success String = %s!\n\n", newSession);
} else {
fprintf(stderr,"Error String = %s!\n\n", errorMessage);
[statusField setStringValue:[NSString stringWithUTF8String:errorMessage]];
return NO;
}

// Retrieve the sites
err = createSoapParameters(&soapParms);
checkErr(err);
err = addSoapParameter(&soapParms, "session", typeChar, newSession);
err = addSoapParameter(&soapParms, "search", typeChar, "");
int i = 1;
err = addSoapParameter(&soapParms, "first", typeSInt32, &i);
i = 999;
err = addSoapParameter(&soapParms, "last", typeSInt32, &i);

// fullString = [string stringByAppendingString:@"Extra Text"];
char sitesDom[100000];
[statusField setStringValue:@"Making web service call retrieving sites..."];
err = makeSoapCall([[host stringByAppendingString:@"/sakai-axis/SakaiSite.jws"] cString],
"getToolsDom", &soapParms, sitesDom, sizeof(sitesDom), errorMessage, sizeof(errorMessage), false, true);
AEDisposeDesc(&soapParms);

if ( err == noErr ) {
fprintf(stderr,"Success String = %s!\n\n", sitesDom);
} else {
fprintf(stderr,"Error String = %s!\n\n", errorMessage);
[statusField setStringValue:[NSString stringWithUTF8String:errorMessage]];
return NO;
}

// Parse it all
// Release if this has been done before
if ( xmlDoc != nil ) {
[xmlDoc release];
xmlDoc = nil;
}

// NSXMLDocument *xmlDoc;
NSString *bar;
NSError *nsErr = nil;
bar = [NSString stringWithUTF8String:sitesDom];
NSLog(bar);
xmlDoc = [ [ [ NSXMLDocument alloc] initWithXMLString:bar options:0 error:&nsErr ] retain ] ;
if ( xmlDoc == nil ) {
[statusField setStringValue:@"Unable to parse returned XML from web Service"];
return NO;
}

[statusField setStringValue:@"Web Service Login Successful"];
goodLogin = YES;
return goodLogin;
}

@end

Posted by csev at 09:56 AM

Web Services in Objective C Part 3

This is my caller file. It is a .mm file not a .m file so I can micx C++ and Objective C. Sorry for my newbieness.

This is SakaiServer.mm - look in the doLogin method.

Again, Creative Commons Public Domain blah blah.

//
// SakaiServer.m
// SakaiDesktop
//
// Created by Charles Severance on 12/29/05.
// Copyright 2005 __MyCompanyName__. All rights reserved.
//

// Needed for SoapUtils
#include

#import "SakaiServer.h"

#import "SoapUtils.h";

#define checkErr(err) \
while (err != noErr) { fprintf(stderr, "Failed at line %d, error %d\n", __LINE__, err); exit(-1); }

@implementation SakaiServer

- (void) init
{
host = nil;
title = nil;
xmlDoc = nil;
account = nil;
password = nil;
session = nil;

// Initially we allow logins, and have neither tried, nor failed logging in
suppressLogin = NO;
goodLogin = NO;
triedLogin = NO;
}

- (IBAction) setHost:(NSString *) theHost
{
host = [theHost retain];
}

- (IBAction) setTitle:(NSString *) theTitle
{
title = [theTitle retain];
}

- (IBAction) setAccount:(NSString *) theAccount
{
account = theAccount;
}

- (IBAction) setPassword:(NSString *) thePassword
{
password = thePassword;
}

- (IBAction) setSuppressLogin:(BOOL) theValue
{
suppressLogin = theValue;
}

- (NSString *)title
{
return title;
}

- (NSString *)host
{
return host;
}

- (NSString *)account
{
return account;
}

// Instead of handing back the password, we simply allow our caller
// to know if it is nil
- (BOOL) passwordIsNil
{
if ( password == nil ) return YES;
return NO;
}

- (BOOL) suppressLogin
{
return suppressLogin;
}

- (BOOL) goodLogin
{
return goodLogin;
}


- (BOOL) triedLogin
{
return triedLogin;
}

// Log in to a Sakai Server
- (BOOL) doLogin :(NSTextField *) statusField
{

if ( triedLogin == YES ) {
[statusField setStringValue:@"Prior login failed..."];
return goodLogin;
}

triedLogin = YES;
goodLogin = NO;

OSErr err;
AEDesc soapParms;
err = createSoapParameters(&soapParms);
checkErr(err);

// Build up Soap Parameters - Ignore Errors while building up parameters
err = addSoapParameter(&soapParms, "id", typeChar, [account cString]);
err = addSoapParameter(&soapParms, "pw", typeChar, [password cString]);

char newSession[1024];
char errorMessage[1024];
[statusField setStringValue:@"Making web service call for login..."];
err = makeSoapCall([[host stringByAppendingString:@"/sakai-axis/SakaiLogin.jws"] cString],
"login", &soapParms, newSession, sizeof(newSession), errorMessage, sizeof(errorMessage), false, true);
AEDisposeDesc(&soapParms);

if ( err == noErr ) {
fprintf(stderr,"Success String = %s!\n\n", newSession);
} else {
fprintf(stderr,"Error String = %s!\n\n", errorMessage);
[statusField setStringValue:[NSString stringWithUTF8String:errorMessage]];
return NO;
}

// Retrieve the sites
err = createSoapParameters(&soapParms);
checkErr(err);
err = addSoapParameter(&soapParms, "session", typeChar, newSession);
err = addSoapParameter(&soapParms, "search", typeChar, "");
int i = 1;
err = addSoapParameter(&soapParms, "first", typeSInt32, &i);
i = 999;
err = addSoapParameter(&soapParms, "last", typeSInt32, &i);

// fullString = [string stringByAppendingString:@"Extra Text"];
char sitesDom[100000];
[statusField setStringValue:@"Making web service call retrieving sites..."];
err = makeSoapCall([[host stringByAppendingString:@"/sakai-axis/SakaiSite.jws"] cString],
"getToolsDom", &soapParms, sitesDom, sizeof(sitesDom), errorMessage, sizeof(errorMessage), false, true);
AEDisposeDesc(&soapParms);

if ( err == noErr ) {
fprintf(stderr,"Success String = %s!\n\n", sitesDom);
} else {
fprintf(stderr,"Error String = %s!\n\n", errorMessage);
[statusField setStringValue:[NSString stringWithUTF8String:errorMessage]];
return NO;
}

// Parse it all
// Release if this has been done before
if ( xmlDoc != nil ) {
[xmlDoc release];
xmlDoc = nil;
}

// NSXMLDocument *xmlDoc;
NSString *bar;
NSError *nsErr = nil;
bar = [NSString stringWithUTF8String:sitesDom];
NSLog(bar);
xmlDoc = [ [ [ NSXMLDocument alloc] initWithXMLString:bar options:0 error:&nsErr ] retain ] ;
if ( xmlDoc == nil ) {
[statusField setStringValue:@"Unable to parse returned XML from web Service"];
return NO;
}

[statusField setStringValue:@"Web Service Login Successful"];
goodLogin = YES;
return goodLogin;
}

@end

Posted by csev at 09:56 AM

Web Services in Objective C Part 2

This is the SoapUtils.h - you include this.

Again if someone knows how to make this a set of utilities in a separate file that are linked together - let me know. I am an Xcode Newbie :)

/*
* SoapUtils.h
* SakaiDesktop
*
* Created by Charles Severance on 12/29/05.
* Copyright 2005 __MyCompanyName__. All rights reserved.
*
*/

#include


#define cleanupErr(err) \
while (err != noErr) { fprintf(stderr, "Failed at line %d, error %d\n", __LINE__, err); goto cleanup; }

static OSStatus getUserRecordField(const AEDesc* record, const char* fieldName, OSType desiredType, void* buffer, Size bufferSize, Size* actualSize)
{
// Extract the userfield list from the record.
AEDesc userField;
OSStatus err = AEGetParamDesc(record, keyASUserRecordFields, typeAEList, &userField);
cleanupErr(err);

// The userfield is an array of name,data, so we compare the name against what we're looking for,
// and if it maches return nameIndex + 1 coerced appropriately
long count;
err = AECountItems(&userField, &count);
cleanupErr(err);

for (long i = 1; i <= count; i += 2) {
char tmpName[255];
Size tmpNameSize;
OSType tossKeyword;
OSType tossType;

err = AEGetNthPtr(&userField, i, typeChar, &tossKeyword, &tossType, tmpName, sizeof(tmpName), &tmpNameSize);
cleanupErr(err);

if (strncmp(fieldName, tmpName, tmpNameSize) == 0) {
err = AEGetNthPtr(&userField, i + 1, desiredType, &tossKeyword, &tossType, buffer, bufferSize, actualSize);
cleanupErr(err);
break;
}
}


cleanup:
return err;
}


static void dumpDebug(const char* msg, const AppleEvent* event, OSType parameter)
{
fprintf(stderr, "%s:\n", msg);

AEDesc paramDesc;
OSErr err = AEGetParamDesc(event, parameter, typeChar, ¶mDesc);
if (err != noErr)
fprintf(stderr, "\tCan't get parameter %4.4s - %d returned\n", ¶meter, err);
else {
int len = AEGetDescDataSize(¶mDesc);
char* buffer = new char[len];
AEGetDescData(¶mDesc, buffer, len);

char* p = buffer;
char* pEnd = buffer + len;

while (p < pEnd) {
char* pNext = strpbrk(p, "\r\n");
if (pNext == NULL)
pNext = pEnd;
else {
while (pNext < pEnd && (*pNext == '\r' || *pNext == '\n')) {
*pNext++ = '\0';
}
}
fprintf(stderr, "\t%.*s\n", pNext - p, p);
p = pNext;
}

AEDisposeDesc(¶mDesc);
delete[] buffer;
}
fprintf(stderr, "\n\n");
}


/* Sample code

// Java Signature in jws
// public String mymethod(String id, Integer i, Double d)

OSErr err;
AEDesc soapParms;
err = createSoapParameters(&soapParms);
err = addSoapParameter(&soapParms, "id", typeChar, "fred");
int i=25;
err = addSoapParameter(&soapParms, "i", typeSInt32, &i);
double d=123.456;
err = addSoapParameter(&soapParms, "d", typeIEEE64BitFloatingPoint, &d);
char buffer[100000];
char errorMessage[1024];
err = makeSoapCall(url, "mymethod", &soapParms ,buffer, sizeof(buffer), errorMessage, sizeof(errorMessage), false, true);
AEDisposeDesc(&soapParms);
if ( err == noErr ) {
fprintf(stderr,"Success String = %s!\n\n", buffer);
} else {
fprintf(stderr,"Error String = %s!\n\n", errorMessage);
}

*/

static OSStatus createSoapParameters(AEDesc* termsList)
{
OSErr err = AECreateList(NULL, 0, false, termsList);
return err;
}

static OSStatus addSoapParameter(AEDesc* termsList, const char* name, OSType dataType, const void* data)
{
OSErr err = AEPutPtr(termsList, 0, typeChar, name, strlen(name));

if ( err != noErr ) return err;
switch (dataType) {
case typeChar:
err = AEPutPtr(termsList, 0, typeChar, data, strlen((const char*)data));
break;
case typeSInt32:
err = AEPutPtr(termsList, 0, typeSInt32, data, 4);
break;
case typeIEEE64BitFloatingPoint:
err = AEPutPtr(termsList, 0, typeIEEE64BitFloatingPoint, data, 8);
break;
default:
fprintf(stderr,"Error, unknown type in addSoapParameter: %4s\n\n",dataType);
}
return err;
}

static OSErr makeSoapCall(const char* url, const char* methodName, AEDesc* soapParms,
char* buffer, int bufferSize,
char *errorMessage, int errorMessageLength,
int doDebug, int doDebugError )
{
if ( bufferSize < 1 || errorMessageLength < 1 ) return eLenErr;

errorMessage[0] = '\0'; // No Error Message

OSErr err;

AEDesc targetAddress;
// Create the target address
err = AECreateDesc(typeApplicationURL, url, strlen(url), &targetAddress);
cleanupErr(err);

AppleEvent event;
err = AECreateAppleEvent(kAERPCClass, kAESOAPScheme, &targetAddress, kAutoGenerateReturnID, kAnyTransactionID, &event);
cleanupErr(err);
AEDisposeDesc(&targetAddress);

// Create the parameters for the event - the direct object is a record that contains
// the method name, and a list of parameters

AEDesc directObject;
err = AECreateList(NULL, 0, true, &directObject);
cleanupErr(err);

// Put the method name
err = AEPutParamPtr(&directObject, keyRPCMethodName, typeChar, methodName, strlen(methodName));
cleanupErr(err);

AEDesc paramRecord;
err = AECreateList(NULL, 0, true, ¶mRecord);
if (err == noErr)
err = AEPutParamDesc(¶mRecord, keyASUserRecordFields, soapParms);


// Put the parameter record into the direct object
err = AEPutParamDesc(&directObject, keyRPCMethodParam, ¶mRecord);
cleanupErr(err);
AEDisposeDesc(¶mRecord);

// Additional pieces for soap are the method namespace, namespaceURI and
// SOAPAction. If the SOAPAction isn't explicitly specified, it
// will be the path part of the url (in this case, "/examples")
// You don't need to supply the method namespace...
// static const char* methodNameSpaceURI = "http://www.soapware.org/";
// err = AEPutParamPtr(&directObject, keySOAPMethodNameSpaceURI, typeChar, methodNameSpaceURI, strlen(methodNameSpaceURI));
// cleanupErr(err);

// Put the direct object into the event
err = AEPutParamDesc(&event, keyDirectObject, &directObject);
cleanupErr(err);
AEDisposeDesc(&directObject);

// Request debugging information
if ( doDebug ) {
SInt32 debugAttr = kAEDebugXMLDebugAll;
err = AEPutAttributePtr(&event, keyXMLDebuggingAttr, typeSInt32, &debugAttr, sizeof(debugAttr));
}

// Finally, send the event (we're using AESendMessage so we don't link against Carbon, just ApplicationServices)
AppleEvent reply;
AEInitializeDescInline(&reply);
err = AESendMessage(&event, &reply, kAEWaitReply, kAEDefaultTimeout);
cleanupErr(err);

// The direct object contains our result (the name of the state)
Size actualSize;

// See if we got a return value
err = AEGetParamPtr(&reply, keyDirectObject, typeChar, NULL, buffer, bufferSize, &actualSize);

// Upon success return after optionally dumping information
if ( err == noErr ) {
if ( actualSize < bufferSize ) {
buffer[actualSize] = '\0'; // Terminate
} else {
buffer [ bufferSize-1 ] = '\0';
err = eLenErr;
if ( errorMessageLength > 60 ) sprintf(errorMessage,"SOAP return buffer too short partial results returned, length= %d", bufferSize);
}
}

// If we still have no error, lets return
if ( err == noErr ) {
if ( doDebug ) {
dumpDebug("HTTP POST header", &reply, keyAEPOSTHeaderData);
dumpDebug("XML Request", &reply, keyAEXMLRequestData);
dumpDebug("HTTP Reply header", &reply, keyAEReplyHeaderData);
dumpDebug("XML Reply", &reply, keyAEXMLReplyData);
}
return err;
}

// Dump debug information in this error case
if ( doDebug || doDebugError ) {
dumpDebug("HTTP POST header", &reply, keyAEPOSTHeaderData);
dumpDebug("XML Request", &reply, keyAEXMLRequestData);
dumpDebug("HTTP Reply header", &reply, keyAEReplyHeaderData);
dumpDebug("XML Reply", &reply, keyAEXMLReplyData);
}

// Something is wrong, lets whip up a nice return error string for them
err = AEGetParamPtr(&reply, keyErrorString, typeChar, NULL, errorMessage, errorMessageLength, &actualSize);

// If the host is up, the web service is right, and the method name is right,
// and a parameter name is wrong (i.e. bad signature) - we come here with
// A 1701 (errAEDescNotFound - Descriptor Record Not Found)
// There may be other scenarrios that get us here - but for now, lets return the best
// error message we know about - it is better than a generic "interal fault 1701"

if ( err != noErr ) {
if ( errorMessageLength > 60 ) sprintf(errorMessage,"No Matching Web Service: %i\n\n",err);

} else { // Normal error situation, like AxisFault, Error message already set
// Host does not exist: Transport error!
// Host is up, but no web service: java.io.FileNotFoundException: /SakaiLogin.jws!
// Host is up, no matching method: No such operation 'login'!

// force error return
err = errAEDescNotFound;
}

cleanup:

if ( doDebug || doDebugError ) {
fprintf(stderr,"makeSoapCall() error=%d url=%s (%s) message=%s\n",err,url,methodName, errorMessage);
}

return err;
}

Posted by csev at 09:55 AM

Doing Web Services in Objective C

The examples for web services in Objective C in Xcode 2 are pretty lame. I am surprised that things are not cleaner - the WebKit is very sweet and cool.

I took one of the examples and refactored it to make it really cool. I will post code in the blog in my next two posts.

This is a mix of C++ and Objective C. I am not an expert in either in Xcode2 having learned Xcode 4 days ago. If someone smarter than me wants to redo this Apple event code using purely Objective C - that is fine with me. I would actually like that refactored code.

This is all Creative commons public domain - sample code stuff. Use, tear up reuse, don't sue me.

This is the meat of host things are used again - note my weird dialect where I switch back form C, C++, and Objective C. I am still a newbie and fall back to things I know like fprintf from time to time :)

// Java Signature in jws
// public String mymethod(String id, Integer i, Double d)

OSErr err;
AEDesc soapParms;
err = createSoapParameters(&soapParms);
err = addSoapParameter(&soapParms, "id", typeChar, "fred");
int i=25;
err = addSoapParameter(&soapParms, "i", typeSInt32, &i);
double d=123.456;
err = addSoapParameter(&soapParms, "d", typeIEEE64BitFloatingPoint, &d);
char buffer[100000];
char errorMessage[1024];
err = makeSoapCall(url, "mymethod", &soapParms ,buffer, sizeof(buffer), errorMessage, sizeof(errorMessage), false, true);
AEDisposeDesc(&soapParms);
if ( err == noErr ) {
fprintf(stderr,"Success String = %s!\n\n", buffer);
} else {
fprintf(stderr,"Error String = %s!\n\n", errorMessage);
}

Posted by csev at 09:53 AM

Doing a Modal Dialog in Objective C

See this URL:

http://developer.apple.com/documentation/Cocoa/Conceptual/Sheets/Tasks/UsingAppModalDialogs.html

This is a better modal dialog than in the Hillegass book pages 306-311. The Hillegass book is great showing the wiring of such a sheet - but the code below is better for the actual showing of the dialog. The Hillegass book uses the selector (nifty pet trick - but overkill for most modal dialogs).

Use this code (I have added some comments below):

- (void)showCustomDialog: (NSWindow *)window
// User has asked to see the dialog. Display it.
{
if (!myCustomDialog)
[NSBundle loadNibNamed: @"MyCustomDialog" owner: self];

[NSApp beginSheet: myCustomDialog
modalForWindow: window
modalDelegate: nil
didEndSelector: nil
contextInfo: nil];
[NSApp runModalForWindow: myCustomDialog];
// Dialog is up here.

// This coee is executed after the dialog closes
// Pull final values for variables from of the modal Dialog here
// Note - any type of return code needs to be placed in a class-global variable

NSLog([_sheetPasswordTextField stringValue]);
fprintf(stderr,"Return value = %d\n",sheetResult);

[NSApp endSheet: myCustomDialog];
[myCustomDialog orderOut: self];
}

Listing 2 Closing an application-modal dialog
- (IBAction)closeMyCustomDialog: (id)sender
{
// If you have more than one button, set a class-global integer
// to indicate which button was pressed to close the dialog (OK, Login, etc)
sheetResult = 0;
[NSApp stopModal];
}

Posted by csev at 09:28 AM

The most Important things to Know in Xcode2

There are a few things are are *SOOO* subtle in Xcode. You need to know them of you will hate Xcode2. Thanks to my buddy Jeff Kahn and some good IM - I learned these tricks. Each thing easily has wasted several hours to a half day for me. But once you know them Xcode2 becomes more tolerable.

I am having fun with Objective C. I like the message pattern. It is fun to send messages to things. I worry a *lot* about memory leaks though.

On to the critical bits.

Autocomplete: While typing, when there is an underline, you can press Escape to see possible completions. I love autocompletion but after four days- I still had not picked up this subtle clue.

Editing a .h file and using it in NIB: Only use Classes -> Create files *once* - for iterative development, simply edit the class interface file (xyz.h) using the editor and then save the file and drag the .h file into NIB. Then NIB will know about new or changed outliet and actions and you don't have to use the (yet another hard to use GUI) FileMerge program to retain your local instance variables and commments in the .h etc. Once you have dragged the file in, you can wire up the new outlets and actions using NIB. This makes iterative development FAR cleaner. Lesson: Use Xcode2 as the IDE - use NIB for layout and wiring - not code editing :).

Also see my prior post on how to set a delegate for an onScreen control.

Posted by csev at 09:17 AM

December 29, 2005

How does one Set the delegate value for an NSOutlineView in Interface Builder in Xcode 2

Sometimes these implicit interfaces are just too obtuse. I wasted a DAY figuring this out.

I needed to mark my class (SakaiDesktop) as a delegate for a NSOutlineView control on my window. I knew that it had something to do with dragging. The last step is what is missing.

Place Mouse over the Outline Control on the Window

Control-Drag from the Window to the instance of SakaiDesktop and release

The Inspector will switch to Outlets (this is what kept confusing me)

Switch *back* to Actions in the Inspector - delegate and datasource will be there - click on the row for delegate and the instance willbe the designated delegate.

Funny how this did not work:

[myOutline setDelegate:self];


When I did it in init.

The problem I had was without being set to the delegate, the

- (void)outlineViewSelectionDidChange:(NSNotification *)notification

Notifications were not being delivered.

If the people who built NIB builder just made it so that picking a delegate used a drop down list instead of this multi-click gymnastics, I would be much happier.

Posted by csev at 03:26 PM