We have now seen enough of the server-side Objective‑C mapping to implement a server for the file system we developed in
Chapter 5. (You may find it useful to review the Slice definition for our file system in
Section 5.4 before studying the source code.)
#import <Ice/Ice.h>
#import <FileI.h>
#import <DirectoryI.h>
#import <Foundation/NSAutoreleasePool.h>
int
main(int argc, char* argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int status = 1;
id<ICECommunicator> communicator = nil;
@try {
communicator =
[ICEUtil createCommunicator:&argc argv:argv];
id<ICEObjectAdapter> adapter =
[communicator createObjectAdapterWithEndpoints:
@"SimpleFilesystem"
endpoints:@"default ‑p 10000"];
// Create the root directory (with name "/" and no parent)
//
DirectoryI *root =
[DirectoryI directoryi:@"/" parent:nil];
[root activate:adapter];
// Create a file called "README" in the root directory
//
FileI *file = [FileI filei:@"README" parent:root];
NSMutableArray *text = [NSMutableArray arrayWithObject:
@"This file system contains a collection of poetry."];
[file write:text current:nil];
[file activate:adapter];
// Create a directory called "Coleridge" in the root dir
//
DirectoryI *coleridge =
[DirectoryI directoryi:@"Coleridge" parent:root];
[coleridge activate:adapter];
// Create a file called "Kubla_Khan"
// in the Coleridge directory
//
file = [FileI filei:@"Kubla_Khan" parent:coleridge];
text = [NSMutableArray arrayWithObjects:
@"In Xanadu did Kubla Khan",
@"A stately pleasure‑dome decree:",
@"Where Alph, the sacred river, ran",
@"Through caverns measureless to man",
@"Down to a sunless sea.",
nil];
[file write:text current:nil];
[file activate:adapter];
// All objects are created, allow client requests now
//
[adapter activate];
// Wait until we are done
//
[communicator waitForShutdown];
status = 0;
} @catch (NSException* ex) {
NSLog(@"%@", ex);
}
@try {
[communicator destroy];
} @catch (NSException* ex) {
NSLog(@"%@", ex);
}
[pool release];
return status;
}
#import <Ice/Ice.h>
#import <FileI.h>
#import <DirectoryI.h>
#import <Foundation/NSAutoreleasePool.h>
The code includes the header Ice/Ice.h, which contains the definitions for the Ice run time, and the files
FileI.h and
DirectoryI.h, which contain the definitions of our servant implementations. Because we use an autorelease pool, we need to include
Foundation/NSAutoreleasePool.h as well.
The next part of the source code is mostly boiler plate that we saw previously: we create an object adapter, and, towards the end, activate the object adapter and call
waitForShutdown, which blocks the calling thread until you call
shutdown or
destroy on the communicator. (Ice does not make any demands on the main thread, so
waitForShutdown simply blocks the calling thread; if you want to use the main thread for other purposes, you are free to do so.)
int
main(int argc, char* argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int status = 1;
id<ICECommunicator> communicator = nil;
@try {
communicator =
[ICEUtil createCommunicator:&argc argv:argv];
id<ICEObjectAdapter> adapter =
[communicator createObjectAdapterWithEndpoints:
@"SimpleFilesystem"
endpoints:@"default ‑p 10000"];
// ...
// All objects are created, allow client requests now
//
[adapter activate];
// Wait until we are done
//
[communicator waitForShutdown];
status = 0;
} @catch (NSException* ex) {
NSLog(@"%@", ex);
}
@try {
[communicator destroy];
} @catch (NSException* ex) {
NSLog(@"%@", ex);
}
[pool release];
return status;
}
As we will see shortly, the servants for our directories and files are of type DirectoryI and
FileI, respectively. The constructor for either type of servant accepts two parameters: the name of the directory or file to be created and the servant for the parent directory. (For the root directory, which has no parent, we pass a
nil parent.) Thus, the statement
DirectoryI *root = [DirectoryI directoryi:@"/" parent:nil];
// Create the root directory (with name "/" and no parent)
//
DirectoryI *root =
[DirectoryI directoryi:@"/" parent:nil];
[root activate:adapter];
// Create a file called "README" in the root directory
//
FileI *file = [FileI filei:@"README" parent:root];
NSMutableArray *text = [NSMutableArray arrayWithObject:
@"This file system contains a collection of poetry."];
[file write:text current:nil];
[file activate:adapter];
// Create a directory called "Coleridge" in the root dir
//
DirectoryI *coleridge =
[DirectoryI directoryi:@"Coleridge" parent:root];
[coleridge activate:adapter];
// Create a file called "Kubla_Khan"
// in the Coleridge directory
//
file = [FileI filei:@"Kubla_Khan" parent:coleridge];
text = [NSMutableArray arrayWithObjects:
@"In Xanadu did Kubla Khan",
@"A stately pleasure‑dome decree:",
@"Where Alph, the sacred river, ran",
@"Through caverns measureless to man",
@"Down to a sunless sea.",
nil];
[file write:text current:nil];
[file activate:adapter];
We first create the root directory and a file README within the root directory. (Note that we pass the servant for the root directory as the parent pointer when we create the new node of type
FileI.)
After creating each servant, the code calls activate on the servant. (We will see the definition of this member function shortly.) The
activate member function adds the servant to the ASM.
// Create the root directory (with name "/" and no parent)
//
DirectoryI *root =
[DirectoryI directoryi:@"/" parent:nil];
[root activate:adapter];
// Create a file called "README" in the root directory
//
FileI *file = [FileI filei:@"README" parent:root];
NSMutableArray *text = [NSMutableArray arrayWithObject:
@"This file system contains a collection of poetry."];
[file write:text current:nil];
[file activate:adapter];
// Create a directory called "Coleridge" in the root dir
//
DirectoryI *coleridge =
[DirectoryI directoryi:@"Coleridge" parent:root];
[coleridge activate:adapter];
// Create a file called "Kubla_Khan"
// in the Coleridge directory
//
file = [FileI filei:@"Kubla_Khan" parent:coleridge];
text = [NSMutableArray arrayWithObjects:
@"In Xanadu did Kubla Khan",
@"A stately pleasure‑dome decree:",
@"Where Alph, the sacred river, ran",
@"Through caverns measureless to man",
@"Down to a sunless sea.",
nil];
[file write:text current:nil];
[file activate:adapter];
Recall from Section 18.8.3 that Slice string sequences map to
NSArray or
NSMutableArray, depending on the parameter direction. Here, we instantiate that array and add a line of text to it.
Finally, we call the Slice write operation on our
FileI servant by simply writing:
[file write:text current:nil];
This code is interesting: the server code invokes an operation on one of its own servants. Because the call happens via the pointer to the servant (of type
FileI) and
not via a proxy (of type
id<FilePrx>), the Ice run time does not know that this call is even taking place—such a direct call into a servant is not mediated by the Ice run time in any way and is dispatched as an ordinary Objective‑C function call. The operation implementation in the servant expects a
current object. In this case, we pass nil (which is fine because the operation implementation does not use it anyway).
In similar fashion, the remainder of the code creates a subdirectory called Coleridge and, within that directory, a file called
Kubla_Khan to complete the structure in
Figure 21.1.
We must provide servants for the concrete interfaces in our Slice specification, that is, we must provide servants for the
File and
Directory interfaces in the Objective‑C classes
FileI and
DirectoryI. This means that our servant classes look as follows:
#import <Filesystem.h>
@interface FileI : FSFile <FSFile>
// ...
@end
@interface DirectoryI : FSDirectory <FSDirectory>
// ...
@end
We now can think about how to implement our servants. One thing that is common to all nodes is that they have a name and a parent directory. As we saw earlier, we pass these details to a convenience constructor, which also takes care of calling
autorelease on the new servant.
In addition, we will use UUIDs as the object identities for files and directories. This relieves us of the need to otherwise come up with a unique identity for each servant (such as path names, which would only complicate our implementation). Because the
list operation returns proxies to nodes, and because each proxy carries the identity of the servant it denotes, this means that our servants must store their own identity, so we can create proxies to them when clients ask for them.
For File servants, we also need to store the contents of the file, leading to the following definition for the
FileI class:
#import <Filesystem.h>
@class DirectoryI;
@interface FileI : FSFile <FSFile>
{
@private
NSString *myName;
DirectoryI *parent;
ICEIdentity *ident;
NSArray *lines;
}
@property(nonatomic, retain) NSString *myName;
@property(nonatomic, retain) DirectoryI *parent;
@property(nonatomic, retain) ICEIdentity *ident;
@property(nonatomic, retain) NSArray *lines;
+(id) filei:(NSString *)name parent:(DirectoryI *)parent;
‑(void) write:(NSMutableArray *)text
current:(ICECurrent *)current;
‑(void) activate:(id<ICEObjectAdapter>)a;
@end
The instance variables store the name, parent node, identity, and the contents of the file. The
filei convenience constructor instantiates the servant, remembers the name and parent directory, assigns a new identity, and calls
autorelease.
Note that the only Slice operation we have defined here is the write method. This is necessary because, as we saw previously, the code in
Server.m calls this method to initialize the files it creates.
For directories, the requirements are similar. They also need to store a name, parent directory, and object identity. Directories are also responsible for keeping track of the child nodes. We can store these nodes in an array of proxies. This leads to the following definition:
#import <Filesystem.h>
@interface DirectoryI : FSDirectory <FSDirectory>
{
@private
NSString *myName;
DirectoryI *parent;
ICEIdentity *ident;
NSMutableArray *contents;
}
@property(nonatomic, retain) NSString *myName;
@property(nonatomic, retain) DirectoryI *parent;
@property(nonatomic, retain) ICEIdentity *ident;
@property(nonatomic, retain) NSMutableArray *contents;
+(id) directoryi:(NSString *)name parent:(DirectoryI *)parent;
‑(void) addChild:(id<FSNodePrx>)child;
‑(void) activate:(id<ICEObjectAdapter>)a;
@end
Because the code in Server.m does not call any Slice operations on directory servants, we have not declared any of the corresponding methods. (We will see the purpose of the
addChild method shortly.) As for files, the convenience constructor creates the servant, remembers the name and parent, and assigns an object identity, as well as calling
autorelease.
The implementation of the name,
read, and
write operations for files is trivial, returning or updating the corresponding instance variable:
‑(NSString *) name:(ICECurrent *)current
{
return myName;
}
‑(NSArray *) read:(ICECurrent *)current
{
return lines;
}
‑(void) write:(NSMutableArray *)text current:(ICECurrent *)current
{
self.lines = text;
}
+(id) filei:(NSString *)name parent:(DirectoryI *)parent
{
FileI *instance = [[[FileI alloc] init] autorelease];
if(instance == nil)
{
return nil;
}
instance.myName = name;
instance.parent = parent;
instance.ident = [ICEIdentity
identity:[ICEUtil generateUUID] category:nil];
return instance;
}
After allocating and autoreleasing the instance, the constructor initializes the instance variables. The only interesting part of this code is how we create the identity for the servant.
generateUUID is a class method of the
ICEUtil class that returns a UUID. We assign this UUID to the
name member of the identity. (The
category is unused—see
Section 32.5 for more information.)
We saw earlier that the server calls activate after it creates each servant. Here is the implementation of this method:
‑(void) activate:(id<ICEObjectAdapter>)a
{
id<FSNodePrx> thisNode =
[FSNodePrx uncheckedCast:[a add:self identity:ident]];
[parent addChild:thisNode];
}
This is how our code informs the Ice run time of the existence of a new servant. The call to
add on the object adapter adds the servant and object identity to the adapter’s servant map. In other words, this step creates the link between the object identity (which is embedded in proxies), and the actual Objective‑C class instance that provides the behavior for the Slice operations.
add returns a proxy to the servant, of type
id<ICEObjectPrx>. Because the
contents instance variable of directory servants stores proxies of type
id<FSNodePrx> (and
addChild expects a proxy of that type), we down-cast the returned proxy to
id<FSNodePrx>. In this case, because we know that the servant we just added to the adapter is indeed a servant that implements the operations on the Slice
Node interface, we can use an
uncheckedCast.
The call to addChild connects the new file to its parent directory.
Finally, we need a dealloc function so we do not leak the memory for the servant’s instance variables:
‑(void) dealloc
{
[myName release];
[parent release];
[ident release];
[lines release];
[super dealloc];
}
‑(NSString *) name:(ICECurrent *)current
{
return myName;
}
‑(NSArray *) list:(ICECurrent *)current
{
return contents;
}
Because the contents instance variable stores the proxies for child nodes of the directory, the list operation simply returns that variable.
+(id) directoryi:(NSString *)name parent:(DirectoryI *)parent
{
DirectoryI *instance =
[[[DirectoryI alloc] init] autorelease];
if(instance == nil)
{
return nil;
}
instance.myName = name;
instance.parent = parent;
instance.ident = [ICEIdentity
identity:(parent ? [ICEUtil generateUUID] : @"RootDir")
category:nil];
instance.contents = [[NSMutableArray alloc] init];
return instance;
}
The only noteworthy differences are that, for the root directory (which has no parent), the code uses
"RootDir" as the identity. (As we saw on
page 621, the client knows that this is the identity of the root directory and uses it to create its proxy.)
The addChild method connects our nodes into a hierarchy by updating the
contents instance variable. That way, each directory knows which nodes are contained in it:
‑(void) addChild:(id<FSNodePrx>)child
{
[contents addObject:child];
}
Finally, the activate and
dealloc methods are very much like the corresponding methods for files:
‑(void) activate:(id<ICEObjectAdapter>)a
{
id<FSNodePrx> thisNode
= [FSNodePrx uncheckedCast:[a add:self identity:ident]];
[parent addChild:thisNode];
}
‑(void) dealloc
{
[myName release];
[parent release];
[ident release];
[contents release];
[super dealloc];
}