Wednesday, October 19, 2011

Asynchronous web service client using NSURLConnection and SBJSON

The NSURLConnection class
A NSURLConnection object is used to perform the execution of a web service using HTTP.
When using NSURLConnection, requests are made in asynchronous form. This mean that you don't wait the end of the request to continue, but you do something else until the NSURLConnection object tell you that some data arrived or the connection started, failed on ended.
When initializing the NSURLConnection object you have to pass the reference of a delegate. This is the object called by NSURLConnection to inform your program about the status on the connection.
This delegate must have to implement the following methods  :
  • connection:didReceiveResponse : called after the connection is made successfully and before receiving any data. Can be called more than one time in case of redirection.
  • connection:didReceiveData : called for each bloc of data.
  • connectionDidFinishLoading : called only one time upon the completion of the request, if no error.
  • connection:didFailWithError : called on error.

The Google Books service
In this example, we will use the Google Book web service. We selected this service mainly for the easy of use and the concise results. We also selected this service because it use JSON to return the datas, and JSON is the best data interchange format for mobile devices like iPhone because it's lightweight and easy to parse.
A typical request to the search function of the Google Book web service is :
https://www.googleapis.com/books/v1/volumes?q=biologie
where searchterms are replaced by the searched keywords. You can see by clicking on the preceding link that the service return an array of items consisting of books description.
We will extract the volumeInfo/title, volumeInfo/description, volumeInfo/imageLinks/smallThumbnail and volumeInfo/previewLink in this example.

Integrating SBJSon
Unfortunately, neither the MacOS X nor the iOS SDK have some JSON parsing API, despite the fact that Apple use JSON intensively in their own applications.
There is several third party API that can easily be found on the Internet, and we selected SBJSon for this example.
You need to download the latest SBJSon distribution, extract it somewhere and import it in your XCode project under a new group :



Writing the Book business class
The Book class represent a book with only the attributes we need in the example: a title, a description, a thumbnail image and a URL to the book page. A Book object will be initialized using a JSON string corresponding to a single item from the Google Book web service.
This is the interface :
#import <Foundation/Foundation.h>

@interface Book : NSObject {
    NSString *title;
    NSString *desc;
    UIImage *smallThumbnail;
    NSURL *url;
}

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *desc;
@property (nonatomic, retain) UIImage *smallThumbnail;
@property (nonatomic, retain) NSURL *url;

-(id) initWithJSONDictionnary:(NSDictionary*)json;

@end


and the implementation :
#import "Book.h"

@implementation Book

@synthesize title, desc, smallThumbnail, url;

-(id) initWithJSONDictionnary:(NSDictionary*)json {
    self = [super init];
    if (self) {
        NSDictionary* volumeInfo = [json objectForKey:@"volumeInfo"];
        self.title = [volumeInfo objectForKey:@"title"];
        self.desc = [volumeInfo objectForKey:@"description"];
     
        NSString* path = [[volumeInfo objectForKey:@"imageLinks"] objectForKey:@"smallThumbnail"];
        UIImage* image = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:path]]];
        self.smallThumbnail = image;
        [image release];
     
        self.url = [NSURL URLWithString:[volumeInfo objectForKey:@"previewLink"]];
    }
    return self;
}

-(void) dealloc {
    [url release];
    [desc release];
    [title release];
    [smallThumbnail release];
    [super dealloc];
}

@end







The GoogleBookService class
This class implements the code to query the Google Books web service using HTTPS and translate the JSON resulting datas into Books business objects.
The resulting objects are stored in a NSMutableArray collection of Books, and the search is started by the -performSearch function :
#import <Foundation/Foundation.h>


@interface GoogleBooksService : NSObject {
    NSMutableArray *books;
    NSMutableData *data;
}

@property (nonatomic, retain) NSMutableArray *books;

-(void) performSearch:(NSString*)keyword;

@end


The data instance variable will be used by the same callbacks to store the raw datas received from by the NSURLConnection object.

Implementing the GoogleBookService class
The first thing is of course to write the -dealloc destructor :
-(void) dealloc {
    [data release];
    [books release];
    [super dealloc];
}


The -performSearch allocate the temporary data storage, build the URL using the keyword (note that you can build a better URL by encoding/escaping the keyword, not done here for clarity), build a NSURLRequest object using the URL then execute the request using NSURLConnection :
-(void) performSearch:(NSString*)keyword {
    data = [[NSMutableData alloc] init];


    NSURL *url = [NSURL URLWithString:

                     [NSString stringWithFormat:
                         @"https://www.googleapis.com/books/v1/volumes?q=%@"
                             keyword]];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
                                                                  delegate:self];
    if (!connection) {
        // this is better if you @throw an exception here
        NSLog(@"error while starting the connection");
        [data release];
    }
 
}


The request to the web service will be executed in a background thread by the connection object. This thread will call the callback functions, in order :
connection:didReceiveResponse, after the connection object have received the response and HTTP headers from the remote web server :
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [data setLength:0];
}

connection:didReceiveData, for each block of raw data received :
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)someData {
    [data appendData:someData];
}

connection:didFailWithError, in case of error :
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [connection release];
    [data release];
}

connectionDidFinishLoading, at the end of the request, if no error have happened. In this function we use SBJSon to translate the raw datas in Books objects :
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [connection release];
    NSString *jsonString = [[NSString alloc] initWithData:data
                                                 encoding:NSUTF8StringEncoding];
    [data release];

    NSArray* jsonItems = [[jsonString JSONValue] objectForKey:@"items"];
    id item;
    books = [[NSMutableArray alloc] init];
    for (item in jsonItems) {
        Book* book = [[Book alloc] initWithJSONDictionnary:item];
        [books addObject:book];
    }
    [jsonString release];
}

Friday, October 14, 2011

How to schedule the execution of a function using NSTimer

The NSTimer class
The NSTimer class can be used to schedule the execution of a function in a program for MacOS X, iPhone /  iOs.
You can execute a function for any object, pass some user defined datas, and optionally you can repeat the scheduled execution automagically.

The target function
First, you have to write the target function.
This function must have a NSTimer* parameter because you can use it with multiple timers.
In this exemple, the target function called timerEnd stops the animation of a UIActivityIndicatorView called indicator then posts a notification if the user have entered some text in a UITextField named textNom :

-(void) timerEnd: (NSTimer *) theTimer { 
    [indicator stopAnimating]; 
    if ([textNom.text length] > 0) {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"My Notification"  
                                                            object:textNom.text]; 
    } 
    textNom.text = @""; 
} 

Scheduling the timer
Once you have your target function ready, you can schedule the execution using the +NSTimer:scheduledTimerWithTimeInterval class function.
In the following example, the timer execute the [self timerEnd] message after 5 seconds :

[NSTimer scheduledTimerWithTimeInterval: 5.0  
                                 target: self  
                               selector: @selector(timerEnd:)  
                               userInfo: nil  
                                repeats: NO];





Notes on NSTimer
Never forget that NSTimer relies on the run loop. It means :

  • You must have a run loop. If you write a MacOS X console application, you must write it yourself. If you write a library or a Framework, you must document the fact that a run loop is mandatory.
  • Run loops retain timers. So you can release your timers once scheduled.
  • It's not real time. The run loop execute the timers once it have checked that the timer time have been passed. Hence in the preceding example the "after 5 seconds" and not "in 5 seconds".
  • the +NSTimer:scheduleTimer* functions creates the timer and pushes it in the run loop. If you only want to create the timer use +NSTimer:timerWithTimeInterval functions then use -NSRunLoop:addTimer to push the timer (you can use +NSRunLoop:currentRunLoop to get the current run loop).

Wednesday, March 5, 2008

Glassfish login module : a simple example

In this tutorial we will see how to write a very simple custom login module for the Glassfish application server.
Even if Glassfish ships with some useful login modules, there are not adapted to every situations, most notably with custom LDAP directories or strange authentication methods.
Chapter 5 of the Glassfish developer documentation is a must read on this subject, and reading it is recommended before trying to write your own custom login module.

Our custom authentication scheme
In this example we want to implement a very simple authentication method : a user is authenticated if is username start with a specific string. For example, if the string is "z", then the users zed and zorro can login.

A configurable login module
The login module must be fully configurable. The application server administrator can configure the string used in our authentication scheme.

The custom realm
A custom login module in glassfish is composed by two classes :
- a custom realm, which handle realm configuration
- a custom login module, which handle authentication based on the informations given by the custom realm
A custom realm must extends AppservRealm :
public class MyRealm extends AppservRealm {
  private String jaasCtxName;
  private String startWith;

@Override protected void init(Properties props) throws BadRealmException, NoSuchRealmException { _
  logger.info("X-TECH MyRealm: init()");
  jaasCtxName = props.getProperty("jaas-context""xtechMyRealm");
  startWith = props.getProperty("startWith""z");
}

@Override public String getJAASContext() { return jaasCtxName; }

public Enumeration getGroupNames(String string) throws InvalidOperationException, NoSuchUserException {
  List groupNames = new LinkedList(); 

  return (Enumeration) groupNames;
}

public String getAuthType() { 

  return "X-TECH MyRealm"; 
}


public String getStartWith() { 
  return startWith; 
}

}


The custom login module
A custom login must extends AppservPasswordLoginModule and override authenticateUser():
public class MyLoginModule extends AppservPasswordLoginModule {

protected void authenticateUser() throws LoginException {
  _logger.info("X-TECH MyLoginModule : authenticateUser for " +
    _username);
  MyRealm realm = (MyRealm) _currentRealm;

  if (!_username.startsWith(realm.getStartWith())) {
    _logger.info("Invalid credentials.");
    throw new LoginException ("Invalid credentials.");
  }

  _logger.info("User authenticated");
  Set principals = _subject.getPrincipals();
  principals.add(new PrincipalImpl(_username));

  String grpList[] = new String[1];
  grpList[0] = "User";
  this.commitUserAuthentication(grpList);
}

}




Installing the custom login module
Installing and activating the customs realm and login module involve three steps :
- Configuring a realm using the glassfish administration console
- Linking the custom realm with the custom login module
- Make the classes avaible to glassfish
The first step is illustrated in the screenshot below :
The second step is done by editing the login.conf file located in the config directory of the domain. Add a line like this one to the end of the login.conf file:
xtechMyRealm { org.xtech.examples.gfloginmodule.MyLoginModule required; };
For the last step just copy the .jar file containing the customs realm and login module classes in the lib directory of glassfish (you must restart glassfish).

Writing an example application
To try the new login module create a web application with only a index.jsp page. Put this line to display the authenticated user name :
Welcome ${pageContext.request.userPrincipal.name}
Add security to the web.xml file:


and link the web application security roles with the group name used in the custom login module by adding the folowing lines to the sun-web.xml file:

Deploy your application and give it a try !