Warning: include_once(/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php): failed to open stream: No such file or directory in /var/www/html/fields/dbp09/local/config.php on line 4

Warning: include_once(): Failed opening '/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php' for inclusion (include_path='.:/opt/php/lib/php') in /var/www/html/fields/dbp09/local/config.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fields/dbp09/local/config.php:4) in /var/www/html/pmwiki-2.2.86/pmwiki.php on line 1250
Datenbankpraktikum SS 2009 - Gruppe 1 - Iphone-PARS

Client/Server Kommunikation

Der XMLParser

Die Abbildung zeigt den grundlegenden Ablauf des Parsings. Das iPhone-SDK stellt mit NSXMLParser eine Bibliothek bereit, welche ein sogenanntes eventbasiertes XML-Parsing implementiert. Eventbasiert bedeutet, dass der Parser eine XML-Datei durchläuft und dabei verschiedene Ereignisse - wie z.B. ein öffnendes Elementtag, schließendes Elementtag oder das Auffinden von Elementdaten zwischen diesen beiden Tags - auftreten können.
Zur Verarbeitung dieser Ereignisse benötigt der Parser ein sogenanntes Delegate, welches bestimmte Methoden implementiert, die der Parser aufrufen kann. Die Auswahl der zu implementierenden Methoden hängt vom Einsatzzweck des Parsers ab. Die Delegateklasse ist in unserem Fall XMLNode bzw. eine der davon abgeleiteten Klassen.

XMLNode

Da wir eine XML-Datei in einen Baum umwandeln wollen, muessen wir wissen, wann ein Element beginnt und endet, daher implementieren wir didStartElement und didEndElement. Weiterhin moechten wir Elementdaten auslesen können, die Methode dazu ist foundCharacters.
Instanzen der Klasse XMLNode (oder einer ihrer Tochterklassen) sind, wie der Name vielleicht impliziert, nicht nur Delegate des Parsers, sondern auch gleichzeitig Knoten des zu erzeugenden Baumes. Daher wird in der Klasse ein parent-XMLNode sowie ein Array sons von untergeordneten Knoten verwaltet. sons wird bereits in didStartElement befüllt, die Initialisierung von parent sowie der Attribute (gespeichert im Dictionary attributeDict) wird an eine Methode getInstanceForString delegiert, welche in die Klasse Elemtypes ausgelagert ist.
Da wir jedes Element der XML-Datei seinem übergeordneten Element zuordnen wollen, wird in didStartElement auch das Delegate des Parsers auf die neue Knoteninstanz gesetzt, was dann in didEndElement wieder rückgängig gemacht wird.
Zuguterletzt wird die Methode foundCharacters implementiert. Diese Methode wird vom Parser immer dann aufgerufen, wenn er innerhalb eines Elements, also zwischen öffnendem und schließendem Tag, Zeichenketten findet. foundCharacters hängt diese Zeichenketten einfach an den String data der bisher schon gefundenen Zeichenketten an.

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)
        elementName namespaceURI:(NSString *)namespaceURI 
        qualifiedName:(NSString *)qualifiedName 
        attributes:(NSDictionary *)attributeDict
{
	XMLNode* new = [ElemTypes getInstanceForString:elementName 
                                                       andDictionary:attributeDict 
                                                       withParent:self];
	[self.sons addObject:new];
	[parser setDelegate:new];
}
 
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
        namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName 
{
	XMLNode* par = self.parent;
	parent = nil;
	[parser setDelegate:par];
 
}
 
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	[data appendString:string];
}

Die eigentliche Initialisierung der zum Elementtypen passenden, von XMLNode abgeleiteten Klasse übernimmt, wie bereits erwähnt, die Methode getInstanceForString aus der Klasse Elemtypes. Ein Ausschnitt ist in der Abbildung zu sehen. Die Methode erzeugt je nach eType, welches der vom Parser gefundene Elementtyp aus dem öffnenden Tag ist, eine Instanz der passenden Modelklasse. Sollte es keine passende Klasse geben, wird eine Instanz der allgemeinen Klasse Element erzeugt. Danach wird an der neuen Instanz die Methode initWithDictionary aufgerufen, welcher der parent und das AttributeDict aus didStartElement durchgereicht werden.
initWithDictionary kann nun mit den Attributen aus dem Dictionary nun modelklassenspezifische Initialisierungen vornehmen. Ein Beispiel aus der Klasse Message ist in der Abbildung zu sehen. Dort werden beispielsweise einfach nur properties mit den entsprechenden Werten gefüllt. Wichtig ist, dass jede initWithDictionary die aus XMLNode geerbte property parent füllt, damit didEndElement das Delegate des Parsers zurücksetzen kann.

+(id) getInstanceForString:(NSString *)eType andDictionary:(NSDictionary *)dict 
        withParent:(XMLNode*)par{
	XMLNode* instance = nil;
 
	if([eType isEqualToString:@"planet"]){
		instance = [Planet alloc];
	}
	if([eType isEqualToString:@"solar_system"]){
		instance = [Solar_System alloc];
	}
        if (!instance){ instance=[Element alloc]; NSLog(@"Brauche Element");}
	[instance initWithDictionary:dict andParent:par];
	instance.type=eType;
	[instance autorelease];
	return instance;
}
 
-(id) initWithDictionary:(NSDictionary *) dic andParent:(XMLNode*)par{
	if(self=[super init]){
		NSLog(@"Message");
		parent = par;
		message_id=[[dic objectForKey:@"message_id"] intValue];
		sender=[dic objectForKey:@"sender"];
		[sender retain];
		subject=[dic objectForKey:@"subject"];
		[subject retain];
		is_read=[[dic objectForKey:@"is_read"] boolValue];
 
		return self;
	}
	return nil;
}

SendQuery

+(XMLNode*) query: (NSString*) q {
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	NSString* server = [defaults stringForKey:@"server"];
	NSString* url = [server stringByAppendingString:q];	
	XMLNode* result = [self parseQuery: url];	
	if(result == nil) {
		return nil; //Kein Ergebnis des Parsing: kein Queryergebnis
	}
	if([result.type isEqual: @"html"]){ //es ist html zurueckgekommen 
                                                           //=> User nicht eingelogt
		if([SendQuery login]){
			result = [self parseQuery: url]; //Login erfolgreich, 
                                                //Query wiederholen
		}
		else {
			return nil; //Login fehlgeschlagen, kein Queryergebnis
		}
	}
	return [[result retain] autorelease]; //Es ist ein Ergebnis vorhanden, 
                                         //zurueckgeben
}

Die Klasse Sendquery enthält als von außen anzustoßende Methode die Methode query. Diese Methode nutzt die interne Methode parseQuery, um eine zu einer vollständigen URL gehörende XML-Antwort zu parsen. Diese URL wird aus der Server-URL und den daran zu verschickenden, in q übergebenen Pfadangabe und Parametern zusammengesetzt. Die Server-URL stammt aus dem sogenannten Settingsbundle der Applikation, einer XML-Datei, die auf dem iPhone gespeichert ist und dort mittels der Settings-Applikation modifiziert werden kann.
Wichtig an der Methode ist, dass hier der Login-Mechanismus realisiert ist. Die vom Methodenaufrufer geforderte Query wird versucht, wobei es drei Ergebnisse geben kann:

  1. Das Ergebnis ist nil: in parseQuery ist ein Fehler aufgetreten, es gibt kein Ergebnis, nil wird zurückgegeben.
  2. Der Rootknoten des Ergebnisses ist vom Typ html: Der Login-Bildschirm ist zurückgekommen.
    Es gibt keine spezifische Antwort der iPhone-Backend-Schnittstelle, falls der User nicht eingeloggt ist, da dies das Login-Framework im Backend nicht erlaubt. Daher kommt hier der Login-Bildschirm des Webfrontends zurück.
  3. In jedem anderen Fall geht die Methode von einem Erfolg aus.
+(BOOL) login{
	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
	NSString* server = [defaults stringForKey:@"server"];
	NSMutableString* urlString = [NSMutableString stringWithString:server];
	[urlString appendString:@"/user_sessions/create.xml?user_session[login]="];
	[urlString appendString:[defaults stringForKey:@"user"]];
	[urlString appendString:@"&user_session[password]="];
	[urlString appendString:[defaults stringForKey:@"pw"]];
	[urlString appendString:@"&user_session[remember_me]=1"];
	XMLNode* result = [self parseQuery: urlString];
	if (!result) return NO; //Fehler in parseQuery; Misserfolg
	return([result.type isEqual:@"success"]); //Ansonsten success als YES, 
                                        //failure als NO zurueckgeben
}

Im Fall das ein Login vorgenommen werden muss, wird die Methode login in der selben Klasse aufgerufen, welche einen Boolean zurückgibt. Im Falle des Erfolgs versucht query die geforderte Anfrage nochmal, ansonsten wird nil als Misserfolgsmeldung zurückgegeben.
login sucht sich die Logindaten(Benutzername und Passwort) ebenfalls aus dem Settingsbundle.
Da es für den Login eine speziell für das iPhone angepasste Action im Framework gibt, welche eine XML-Datei mit entweder einem success- oder failure-Element zurückgibt, ist die Erfolgsprüfung hier einfach. Ist kein Ergebnis oder ein failure zurückgekommen, melde Misserfolg, sonst melde Erfolg.

+(XMLNode*) parseQuery: (NSString*) urlString {
	NSURL* url = [[NSURL alloc] initWithString:urlString];
	if(!url) return nil;        //URL konnte nicht angelegt werden, 
                          //da nicht wohlgeformt 
                          //=> Misserfolg
	NSXMLParser* parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
	XMLNode* del = [[XMLNode alloc] init];
	parser.delegate = del; //Dummy-Delegate
	if(![parser parse]) {
                [parser release]; 
                [url release];    
                [del release]; 
                return nil;     //keine/fehlerhafte XML-Daten => Misserfolg
        } /
	XMLNode* root = [del getRoot];  //Falls Daten vorhanden, 
                                                           //gebe Rootknoten zurück.
	[parser release];
	[url release];
	[del release];    
	return [[root retain] autorelease]; 
}

Die Methode parseQuery ist die zentrale Methode des Querying-Mechanismus. Sie versucht zuerst eine NSURL - Instanz mit dem übergebenen String aufzubauen. Enthält der String keine wohlgeformte URL, ist das Ergebnis des Instanziierungsversuchs nil, wir können nil als Misserfolgsmeldung zurückgeben. Ansonsten erstellen wir mit der URL den NSXMLParser und übergeben ihm ein XMLNode - Delegate. Danach wird der Parser mit parse gestartet. Gibt die Methode NO zurück, so gab es einen Fehler (keine/unvollständige Daten). Dies wird mittels nil dem Methodenaufrufer mitgeteilt.
Im Erfolgsfall jedoch holen wir uns mit getRoot aus dem Delegate - XMLNode den Rootknoten des XML-Baumes und geben diesen zurück. Hintergund: Der Parser benötigt schon vor Erreichen des Rootelements der XML-Daten ein Delegate. Also gibt es ein Dummy-Delegate, dessen einziger Sohn der Rootknoten des Baumes ist. Diesen einzigen Sohn gibt getRoot zurück.

DataController

Die Klasse DataController bildet die zentrale Verbindung zwischen dem User Interface und der SendQuery Backend-Klasse und damit zur Serverdatenquelle. Sie stellt diverse Klassenmethoden zur Beschaffung von Daten für die verschiedenen Sichten bereit. Grob kann hier zwischen zwei Typen unterschieden werden. Zum einen Methoden die Objekte retunieren, welche konkrete Spielinstanzen bzw. Spielstände repräsentieren, zum anderen Methoden welche eine Result Objekt zurückliefern um den Erfolg bzw. Misserfolg einer vom Spieler angestossenen Operation dazustellen.

Hier ein Beispiel für den ersten "Typ" wo ein Array aller Truppenbewegungen besorgt wird:

+(NSMutableArray*) allTroopMovements{
        XMLNode* tree = [SendQuery query:@"/troop_movement/show_armadas.xml"];
	[tree toLog: @""]; 
	NSMutableArray* sons = tree.sons;
	if(sons==nil) return nil;
	for(XMLNode* n in sons)NSLog(n.type);
	NSMutableArray* ownMovements = ((XMLNode*)[sons objectAtIndex:0]).sons;
	for(TroopMovement* t in ownMovements){
		t.foreign = NO; NSLog(t.type);
	}
	NSMutableArray* foreignMovements = ((XMLNode*)[sons objectAtIndex:1]).sons;
	for(TroopMovement* t in foreignMovements){
		t.foreign = YES; NSLog(t.type);
	}
	NSMutableArray* transportMovements = ((XMLNode*)[sons objectAtIndex:2]).sons;
	for(TroopMovement* t in transportMovements){
		t.foreign = NO; NSLog(t.type);
	}
	NSMutableArray* result = [NSMutableArray arrayWithArray:ownMovements];
	[result addObjectsFromArray:foreignMovements];
	[result addObjectsFromArray:transportMovements];
	return [[result retain]autorelease];
 
}


... und ein Beispiel für den zweiten "Typ" in dem eine Flottenbewegung erzeugt und in Gang gesetzt wird:

+(Result*) createTroopMovement:(int)mission from:(Planet*)start to:(Planet*)target withShips:(NSArray*) ships 
                          andResources:(NSDictionary*)resources{
 
        if(!target) return nil;
	if(!start) return nil;
	if(!ships) return nil;
	if([ships count] == 0) return nil;
	NSMutableString* query = [NSMutableString stringWithString: 
                         @"/troop_movement/create_new_troop_movement.xml?    mission="];
	[query appendString:[NSMutableString stringWithFormat:@"%i", mission]];
	[query appendString:@"&startplanet_id="];
	[query appendString:[NSMutableString stringWithFormat:@"%i",[start planet_id]]];
	[query appendString:@"&targetplanet_id="];
	[query appendString:[NSMutableString stringWithFormat:@"%i",[target planet_id]]];
	int i = 0;
	NSMutableString* number = @"0";
	for(Spaceship* s in ships) {
		[query appendString:@"&ship["];
		[query appendString:number];
		[query appendString:@"]="];
		[query appendString:[NSMutableString stringWithFormat:@"%i",[s spaceshipid]]];
		[query appendString:@"&count["];
		[query appendString:number];
		[query appendString:@"]="];
		[query appendString:[NSMutableString stringWithFormat:@"%i",[s count]]];
		i++;
		number = [NSMutableString stringWithFormat:@"%i",i];
	}
 
	if(resources){
		NSArray* keys = [resources allKeys];
		for(NSString* key in keys) {
			[query appendString:@"&"];
			[query appendString:key];
			[query appendString:@"="];
			[query appendString:(NSString*)[resources objectForKey:key]];
		}
	}
	return (Result*)[SendQuery query:query];	
}

Modelklassen

Diverse Modellklassen bilden die Basis für die Vorhaltung der Spieldaten zur Laufzeit. Sie sind Subklasse des XMLNode damit wie schon geschildert direkt dynamisch mit Hilfe der Klasse ElemTypes die spezialisieren Spielinstanzen erzeugt werden können.
Hier zum Beispiel die Klasse Planet:

/*!
 @header Planet
 @abstract   Repräsentiert einen Planeten
 */
@interface Planet : XMLNode {
	BOOL own;
	BOOL hasOwner;
	BOOL homeplanet;
	int solarsystem_id;
	int number;
	int owner_id;
	float ore;
	float crystal;
	float gas;
	int fields;
	int used_fields;
	NSString *name;
	NSString * owner;
	int planet_id;
	float crystal_boni;
	float gas_boni;
	float ore_boni;
	float capacity;
}
@end


Page last modified on August 23, 2009, at 09:17 AM