Pling for iPhone
Join the DZone community and get the full member experience.
Join For FreeIntroduction
The purpose of this article is to give an overview and also some best practices on how to use the JBOSS RestEasy framework as a supporting service layer for an Apple IOS based application hosted in a cloud environment such as Google App Engine.
Technology stack
Client
iPhone IOS 4.2
JSON-Framework for IOS
Facebook Connect for IOS
Server
JBOSS RestEasy
UrbanAirship Notification Service
Architecture Overview
The following diagram
highlights the main components used in the Pling application.Communication between IOS and Google App Engine is handled by:
· IOS Foundation Framework using NSURL related classes
· RestEasy framework on Google App Engine for handling the incoming IOS issues requests
Communication between Google App Engine and UrbanAirship for handling push notifications is handled by:
· Google App Engine URLFetchService
· Jackson to serialize and de-serialize JSON data
We shall note that Google App Engine can only communicate with external web services using the provided URLFetchService framework. Frameworks that rely on HttpClient can however take advantage of Google App Engine by providing custom classes for implementing the underlying network access (HttpClientManager, HttpConnection or ClientConnectionManager, ManagedClientConnection depending on the HttpClient version). Pling does not rely on such feature.
RestEasy Implementation
Using and configuring RestEasy in Google App Engine is quite easy.
After dropping the RestEasy framework libraries into Eclipse, it is time to configure the web.xml file in the war/WEB-INF directory.
As Google App Engine application instances are started on demand (unless you pay for reserved instances.), startup time is critical and therefore automatic RestEasy providers and resources scanning must be turned off. This increases response time on the first REST request as the Pling instance is re-started by Google App Engine.
As a consequence of turning off resources and providers scanning, the web.xml file must contain dedicated entries for implemented Pling related classes.
There are two resources classes:
· User Management service for handling user registration and keep a mapping between push notification tokens (IOS specific) and Facebook user identifier (provided upon successful login through Facebook Connect)
· Push Notification service for handling IOS device registration and push notification request. 2 types of requests are implemented for Pling:
o A request for pushing location information to a Facebook Pling friend
o A request for requesting location information from a Facebook Pling friend
There is one Provider class:
· Pling Exception Mapper for handling errors occurring in the application layer
Below
is an extract for the RestEasy specific elements pertaining to the web.xml
file.
<context-param>
<param-name>resteasy.scan.providers</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>resteasy.scan.resources</param-name>
<param-value>false</param-value>
</context-param>
<context-param>
<param-name>resteasy.resources</param-name>
<param-value>
com.plingmyphone.service.impl.UserManagementServiceImpl,
com.plingmyphone.service.impl.PushNotificationServiceImpl
</param-value>
</context-param>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>
<context-param>
<param-name>resteasy.providers</param-name>
<param-value>
com.plingmyphone.service.PlingExceptionMapper
</param-value>
</context-param>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
Exception Handling
All exceptions that need to be communicated back to the IOS client are handled by the JAX-RS ExceptionMapper and ResponseBuilder functionalities.
In Pling, all exceptions are channeled via a unique exception using the 409 HTTP status.
Below
is the code related to our custom Pling JSON exception.
@Provider
public class PlingExceptionMapper implements ExceptionMapper<com.plingmyphone.service.PlingException>
{
public Response toResponse(PlingException exception)
{
ResponseBuilder rb =
Response.status(409)
.entity("{\"error\": { \"type\": \"" +
exception.getClass().getSimpleName() +
"\", \"message\": \"" + exception.getMessage() + "\"}}")
.type("application/json");
return rb.build();
}
}
Services
User management and push notification services are the 2 REST entry points.
HTTP methods used in these services are:
· POST, using a query parameter and a domain class that automatically gets deserialized from JSON into Java (Thanks to JAX-RS / RestEasy!)
· GET, using a path parameter and returning a domain class that automatically gets serialized into JSON from Java (Thanks, once again, to JAX-RS / RestEasy!)
· PUT, using path, query parameters as well as a domain class that automatically gets deserialized from JSON into Java (Thanks to… JAX-RS / RestEasy!)
· DELETE, using path and query parameters.
Any related Push Notification methods are also using the JAX-RS DefaultValue in order to default to the UrbanAirship development instance. Apple, defines 2 instance types:
· Development, push notification sandbox
· Production, push notification
The
following diagram shows the 2 instances configured on the UrbanAirship web
console.
Below
is an example of a method using the DefaultValue for driving the selection of
the underlying UrbanAirship instance.
@Override
@POST
@Path("/msg")
public void pushMessage(@QueryParam("envType") @DefaultValue("DEV") String envType,
PlingNotification notification)
{
…
}
PlingNotification is a domain
object that is both used by:
· RestEasy for JSON serialization/de-serialization
· JPA for underlying Google App Engine datastore management
If the envType query parameter is omitted in the REST call, the push notification instance will be DEV, therefore pointing to the UrbanAirship pling instance (see diagram above), if the envType parameter is set to PROD, the pling-prod UrbanAirhsip instance will be used.
The following section
highlights the definition of the PlingNotification class.
@Entity
@Table(name = "PlingNotification")
@XmlRootElement
public class PlingNotification implements Serializable
{
…
}
PlingNotification is marked as
a JPA entity and given a name as a table for the underlying persistent
capabilities offered by Google App Engine.
The following diagram depicts
what is available on the Pling specific Google App Engine Web dashboard.
The
Java XML RootElement binding annotation is used here for allowing RestEasy to
serialize/de-serialize the PlingNotification object from JSON to Java and Java
to JSON.
IOS Access to exposed RestEasy services
Access to user management and push notification services RestEasy endpoints on IOS platform is done with a combination of built-in network and URL access capabilities as well as an external JSON framework for serializing and de-serializing JSON to IOS Objective-C representation.
2 REST request types are used in the Pling application:
· Asynchronous calls using [NSURLConnection connectionWithRequest…]
· Synchronous calls using [NSURLConnection sendSynchronousRequest…]
Example of an asynchronous
request for pushing a location notification to a RestEasy endpoint is:
NSMutableURLRequest *pushNotificationRequest;
NSString *UAServer = @"http://url_to_gae_instance/service/notification/msg";
// Construct URL to access RestEasy endpoint
pushNotificationRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[pushNotificationRequest setHTTPMethod:@"POST"];
// Send along our device alias as the JSON encoded request body
[pushNotificationRequest addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[pushNotificationRequest setHTTPBody:[jsonNotification
dataUsingEncoding:NSUTF8StringEncoding]];
[[NSURLConnection connectionWithRequest:pushNotificationRequest delegate:self] start];
[pushNotificationRequest release];
Asynchronous calls rely on the
NSURLConnection delegate message (similar to a callback):
- (void)connection:(NSURLConnection *)theConnection didReceiveResponse:(NSURLResponse *)response
Example of a synchronous
request for retrieving details related to a push notification message from a
RestEasy endpoint is:
NSURLResponse *response = nil;
NSError *error = nil;
PlingNotification *plingNotification = nil;
// Construct the url to access RestEasy endpoint
pushNotificationRequest = [[NSMutableURLRequest alloc] initWithURL:url];
[pushNotificationRequest setHTTPMethod:@"GET"];
[pushNotificationRequest addValue:@"application/json" forHTTPHeaderField:@"Accept"];
NSData *returnData = [NSURLConnection sendSynchronousRequest:pushNotificationRequest returningResponse:&response error:&error];
[pushNotificationRequest release];
// Convert returned JSON data to a PlingNotification object
if(returnData != nil)
{
plingNotification = [[PlingNotification alloc] initWithJSON:[[[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding] autorelease]];
}
As PlingNotification is a
complex object, JSON Framework on IOS allows to extend the object for providing
the helper methods for serializing and de-serializing JSON to Objective-C and
Objective-C to JSON.
The 2 methods are:
· - (id) initWithJSON:(NSString *)JSONString
· - (id)proxyForJson
The first method is used to convert JSON into the object itself. The second one converts the object into JSON.
The following shows a snippet
of each method implementation:
- (id) initWithJSON:(NSString *)JSONString
{
self = [super init];
if (self != nil)
{
SBJsonParser *parser = [SBJsonParser new];
NSDictionary *jsonObject = (NSDictionary *)[parser objectWithString:JSONString];
NSLog(@"NB ENTRIES IN JSON: %d", [jsonObject count]);
NSLog(@"JSON STRING: %@", JSONString);
self.fromFacebookUID = [jsonObject objectForKey:@"fromFacebookUID"];
// Do the rest here (many fields…)
}
return self;
}
- (id)proxyForJson
{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
fromFacebookUID, @"fromFacebookUID",
// more key value pairs here
nil];
for(NSString *key in [dict allKeys])
{
NSLog(@"=====> KEY: %@", key);
}
return dict;
}
Opinions expressed by DZone contributors are their own.
Comments