blob: 9d6407a1d5347ac0d68b4454d20988103b6c650b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "UGClient.h"
#import "UGHTTPManager.h"
#import "SBJson.h"
#import "UGMultiStepAction.h"
#import "SSKeychain.h"
NSString *g_deviceUUID = nil;
@implementation UGClient
{
// the delegate for asynch callbacks
id m_delegate;
// the mutex to protect the delegate variable
NSRecursiveLock *m_delegateLock;
// a growing array of UGHTTPManager instances. See
// "HTTPMANAGER POOLING" further down in this file.
NSMutableArray *m_httpManagerPool;
// the base URL for the service
NSString *m_baseURL;
// the appID for the specific app
NSString *m_appID;
// the appID for the specific app
NSString *m_orgID;
// the cached auth token
UGUser *m_loggedInUser;
// the auth code
NSString *m_auth;
// the list of currently pending multi-step actions
NSMutableArray *m_pendingMultiStepActions;
// logging state
BOOL m_bLogging;
}
/************************** ACCESSORS *******************************/
/************************** ACCESSORS *******************************/
/************************** ACCESSORS *******************************/
+(NSString *) version
{
return @"0.1.1";
}
-(NSString *)getAccessToken
{
return m_auth;
}
-(UGUser *)getLoggedInUser
{
return m_loggedInUser;
}
-(id) getDelegate
{
return m_delegate;
}
/******************************* INIT *************************************/
/******************************* INIT *************************************/
/******************************* INIT *************************************/
-(id)init
{
// you are not allowed to init without an organization id and application id
// you can't init with [UGClient new]. You must call
// [[UGClient alloc] initWithOrganizationId: <your UG org id> withApplicationId:<your UG app id>]
assert(0);
return nil;
}
-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID
{
self = [super init];
if ( self )
{
m_delegate = nil;
m_httpManagerPool = [NSMutableArray new];
m_delegateLock = [NSRecursiveLock new];
m_appID = applicationID;
m_orgID = organizationID;
m_baseURL = @"http://api.usergrid.com";
m_pendingMultiStepActions = [NSMutableArray new];
m_loggedInUser = nil;
m_bLogging = NO;
}
return self;
}
-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID baseURL:(NSString *)baseURL
{
self = [super init];
if ( self )
{
m_delegate = nil;
m_httpManagerPool = [NSMutableArray new];
m_delegateLock = [NSRecursiveLock new];
m_appID = applicationID;
m_orgID = organizationID;
m_baseURL = baseURL;
}
return self;
}
-(BOOL) setDelegate:(id)delegate
{
// first off, clear any pending transactions
for ( int i=0 ; i<[m_httpManagerPool count] ; i++ )
{
UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i];
// it's safe to call cancel at all times.
[mgr cancel];
}
// nil is a valid answer. It means we're synchronous now.
if ( delegate == nil )
{
[m_delegateLock lock];
m_delegate = nil;
[m_delegateLock unlock];
return YES;
}
// if it's not nil, it has to have the delegation function
if ( ![delegate respondsToSelector:@selector(ugClientResponse:)] )
{
return NO;
}
// if we're here, it means the delegate is valid
[m_delegateLock lock];
m_delegate = delegate;
[m_delegateLock unlock];
return YES;
}
/************************* HTTPMANAGER POOLING *******************************/
/************************* HTTPMANAGER POOLING *******************************/
/************************* HTTPMANAGER POOLING *******************************/
// any given instance of UGHTTPManager can only manage one transaction at a time,
// but we want the client to be able to have as many going at once as he likes.
// so we have a pool of UGHTTPManagers as needed.
-(UGHTTPManager *)getHTTPManager;
{
// find the first unused HTTPManager
for ( int i=0 ; i<[m_httpManagerPool count] ; i++ )
{
UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i];
if ( [mgr isAvailable] )
{
// tag this guy as available
[mgr setAvailable:NO];
// return him
return mgr;
}
}
// if we're here, we didn't find any available managers
// so we'll need to make a new one
UGHTTPManager *newMgr = [UGHTTPManager new];
// mark it as in-use (we're about to return it)
[newMgr setAvailable:NO];
// tell it the auth to use
[newMgr setAuth:m_auth];
// add it to the array
[m_httpManagerPool addObject:newMgr];
// return it
return newMgr;
}
-(void)releaseHTTPManager:(UGHTTPManager *)toRelease
{
[toRelease setAvailable:YES];
}
-(void)setAuth:(NSString *)auth
{
// note the auth for ourselves
m_auth = auth;
// update all our managers
for ( int i=0 ; i<[m_httpManagerPool count] ; i++ )
{
UGHTTPManager *mgr = [m_httpManagerPool objectAtIndex:i];
[mgr setAuth:m_auth];
}
}
/************************* GENERAL WORKHORSES *******************************/
/************************* GENERAL WORKHORSES *******************************/
/************************* GENERAL WORKHORSES *******************************/
// url: the URL to hit
// op: a kUGHTTP constant. Example: kUGHTTPPost
// opData: The data to send along with the operation. Can be nil
-(UGClientResponse *)httpTransaction:(NSString *)url op:(int)op opData:(NSString *)opData
{
// get an http manager to do this transaction
UGHTTPManager *mgr = [self getHTTPManager];
if ( m_delegate )
{
if ( m_bLogging )
{
NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
NSLog(@"Asynch outgoing call: '%@'", url);
}
// asynch transaction
int transactionID = [mgr asyncTransaction:url operation:op operationData:opData delegate:self];
if ( m_bLogging )
{
NSLog(@"Transaction ID:%d", transactionID);
NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
}
if ( transactionID == -1 )
{
if ( m_bLogging )
{
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
NSLog(@"Response: ERROR: %@", [mgr getLastError]);
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
}
// there was an immediate failure in the transaction
UGClientResponse *response = [UGClientResponse new];
[response setTransactionID:-1];
[response setTransactionState:kUGClientResponseFailure];
[response setResponse:[mgr getLastError]];
[response setRawResponse:nil];
return response;
}
else
{
// the transaction is in progress and pending
UGClientResponse *response = [UGClientResponse new];
[response setTransactionID:transactionID];
[response setTransactionState:kUGClientResponsePending];
[response setResponse:nil];
[response setRawResponse:nil];
return response;
}
}
else
{
if ( m_bLogging )
{
NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
NSLog(@"Synch outgoing call: '%@'", url);
NSLog(@">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n");
}
// synch transaction
NSString *result = [mgr syncTransaction:url operation:op operationData:opData];
if ( m_bLogging )
{
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
if ( result )
{
NSLog(@"Response:\n%@", result);
}
else
{
NSLog(@"Response: ERROR: %@", [mgr getLastError]);
}
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
}
// since we're doing a synch transaction, we are now done with this manager.
[self releaseHTTPManager:mgr];
if ( result )
{
// got a valid result
UGClientResponse *response = [self createResponse:-1 jsonStr:result];
return response;
}
else
{
// there was an error. Note the failure state, set the response to
// be the error string
UGClientResponse *response = [UGClientResponse new];
[response setTransactionID:-1];
[response setTransactionState:kUGClientResponseFailure];
[response setResponse:[mgr getLastError]];
[response setRawResponse:nil];
return response;
}
}
}
-(UGClientResponse *)createResponse:(int)transactionID jsonStr:(NSString *)jsonStr
{
UGClientResponse *response = [UGClientResponse new];
// set the raw response and transaction id
[response setRawResponse:jsonStr];
[response setTransactionID:transactionID];
// parse the json
SBJsonParser *parser = [SBJsonParser new];
NSError *error;
id result = [parser objectWithString:jsonStr error:&error];
if ( result )
{
// first off, if the result is NOT an NSDictionary, something went wrong.
// there should never be an array response
if ( ![result isKindOfClass:[NSDictionary class]] )
{
[response setTransactionState:kUGClientResponseFailure];
[response setResponse:@"Internal error: Response parsed to something other than NSDictionary"];
return response;
}
// it successfully parsed. Though the result might still be an error.
// it could be the server returning an error in perfectly formated json.
NSString *err = [result valueForKey:@"error"];
if ( err )
{
// there was an error. See if there's a more detailed description.
// if there is, we'll use that. If not, we'll use the error value
// itself.
NSString *errDesc = [result valueForKey:@"error_description"];
NSString *toReport = errDesc;
if ( !toReport ) toReport = err;
[response setTransactionState:kUGClientResponseFailure];
[response setResponse:toReport];
return response;
}
// if we're here we have a good auth. make note of it
NSString *auth = [result valueForKey:@"access_token"];
if ( auth )
{
[self setAuth: auth];
// if there's an access token, there might be a user
NSDictionary *dict = [result objectForKey:@"user"];
if ( dict )
{
// get the fields for the user
m_loggedInUser = [UGUser new];
[m_loggedInUser setUsername:[dict valueForKey:@"username"]];
[m_loggedInUser setUuid:[dict valueForKey:@"uuid"]];
[m_loggedInUser setEmail:[dict valueForKey:@"email"]];
[m_loggedInUser setPicture:[dict valueForKey:@"picture"]];
}
}
[response setTransactionState:kUGClientResponseSuccess];
[response setResponse:result];
return response;
}
else
{
// there was an error during json parsing.
[response setTransactionState:kUGClientResponseFailure];
[response setResponse:[error localizedDescription]];
return response;
}
}
// basic URL assembly functions. For convenience
-(NSMutableString *)createURL:(NSString *)append1
{
NSMutableString *ret = [NSMutableString new];
[ret appendFormat:@"%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1];
return ret;
}
-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2
{
NSMutableString *ret = [NSMutableString new];
[ret appendFormat:@"%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2];
return ret;
}
-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3
{
NSMutableString *ret = [NSMutableString new];
[ret appendFormat:@"%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3];
return ret;
}
-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 append4:(NSString *)append4
{
NSMutableString *ret = [NSMutableString new];
[ret appendFormat:@"%@/%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3, append4];
return ret;
}
-(NSMutableString *)createURL:(NSString *)append1 append2:(NSString *)append2 append3:(NSString *)append3 append4:(NSString *)append4 append5:(NSString *)append5
{
NSMutableString *ret = [NSMutableString new];
[ret appendFormat:@"%@/%@/%@/%@/%@/%@/%@/%@", m_baseURL, m_orgID, m_appID, append1, append2, append3, append4, append5];
return ret;
}
-(void)appendQueryToURL:(NSMutableString *)url query:(UGQuery *)query
{
if ( query )
{
[url appendFormat:@"%@", [query getURLAppend]];
}
}
-(NSString *)createJSON:(NSDictionary *)data
{
NSString *ret = [self createJSON:data error:nil];
// the only way for ret to be nil here is for an internal
// function to have a bug.
assert(ret);
return ret;
}
-(NSString *)createJSON:(NSDictionary *)data error:(NSString **)error
{
SBJsonWriter *writer = [SBJsonWriter new];
NSError *jsonError;
NSString *jsonStr = [writer stringWithObject:data error:&jsonError];
if ( jsonStr )
{
return jsonStr;
}
// if we're here, there was an assembly error
if ( error )
{
*error = [jsonError localizedDescription];
}
return nil;
}
/************************** UGHTTPMANAGER DELEGATES *******************************/
/************************** UGHTTPMANAGER DELEGATES *******************************/
/************************** UGHTTPMANAGER DELEGATES *******************************/
-(void)httpManagerError:(UGHTTPManager *)manager error:(NSString *)error
{
// prep an error response
UGClientResponse *response = [UGClientResponse new];
[response setTransactionID:[manager getTransactionID]];
[response setTransactionState:kUGClientResponseFailure];
[response setResponse:error];
[response setRawResponse:nil];
if ( m_bLogging )
{
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
NSLog(@"Response: ERROR: %@", error);
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
}
// fire it off. Wrap in mutex locks to ensure we don't get
// race conditions that cause us to fire it off to Mr. Nil.
[m_delegateLock lock];
if ( m_delegate )
{
[m_delegate performSelector:@selector(ugClientResponse:) withObject:response];
}
[m_delegateLock unlock];
// now that the callback is complete, it's safe to release this manager
[self releaseHTTPManager:manager];
}
-(void)httpManagerResponse:(UGHTTPManager *)manager response:(NSString *)response
{
if ( m_bLogging )
{
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
NSLog(@"Response (Transaction ID %d):\n%@", [manager getTransactionID], response);
NSLog(@"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n");
}
// form up the response
UGClientResponse *ugResponse = [self createResponse:[manager getTransactionID] jsonStr:response];
// if this is part of a multi-step call, we press on.
for ( int i=0 ; i<[m_pendingMultiStepActions count] ; i++ )
{
UGMultiStepAction *action = [m_pendingMultiStepActions objectAtIndex:i];
if ( [action transactionID] == [ugResponse transactionID] )
{
// multi-step call. Fire off the action.
ugResponse = [self doMultiStepAction:action mostRecentResponse:ugResponse];
if ( ![action reportToClient] )
{
// the action is still pending. We do not report this
// to the user. We're done with the httpmanager we were using,
// though.
[self releaseHTTPManager:manager];
return;
}
// when the action is complete, we want to immediately break
// from this loop, then fall through to the normal reporting
// to the user.
break;
}
}
// fire it off
[m_delegateLock lock];
if ( m_delegate )
{
[m_delegate performSelector:@selector(ugClientResponse:) withObject:ugResponse];
}
[m_delegateLock unlock];
// now that the callback is complete, it's safe to release this manager
[self releaseHTTPManager:manager];
}
// multi-step follow-up function
-(UGClientResponse *)multiStepAction: (UGMultiStepAction *)action
{
// different behavior if synch or asynch
if ( m_delegate )
{
// asynch. Fire it off and we're done
return [self doMultiStepAction:action mostRecentResponse:nil];
}
else
{
// synchronous. keep calling until it finished or fails
UGClientResponse *response = nil;
do
{
response = [self doMultiStepAction:action mostRecentResponse:response];
if ( [action reportToClient] )
{
// done
return response;
}
} while ([response transactionState] == kUGClientResponseSuccess);
// if we're here, there was an error
return response;
}
}
-(UGClientResponse *)doMultiStepAction: (UGMultiStepAction *)action mostRecentResponse:(UGClientResponse *)mostRecentResponse
{
// clear the pending array of this object
[m_pendingMultiStepActions removeObject:action];
// assume we aren't reporting to the client
[action setReportToClient:NO];
if ( mostRecentResponse )
{
// we don't care about pending responses
if ( [mostRecentResponse transactionState] == kUGClientResponsePending )
{
// put ourselves back in the list
[m_pendingMultiStepActions addObject:action];
return mostRecentResponse;
}
// any failure is an immediate game ender
if ( [mostRecentResponse transactionState] == kUGClientResponseFailure )
{
[mostRecentResponse setTransactionID:[action transactionID]];
return mostRecentResponse;
}
}
// if mostRecentRespons is nil, that means it's the first call to initiate
// the chain. So we continue on with processing.
// so either we are reacting to a success or we are starting off the chain
UGClientResponse *result = nil;
if ( [action nextAction] == kMultiStepCreateActivity )
{
// create the activity
result = [self createActivity:[action activity]];
// advance ourselves to the next step
[action setNextAction:kMultiStepPostActivity];
}
else if ( [action nextAction] == kMultiStepCreateGroupActivity )
{
// create the activity
result = [self createActivity:[action activity]];
// advance ourselves to the next step
[action setNextAction:kMultiStepPostGroupActivity];
}
else if ( [action nextAction] == kMultiStepPostActivity )
{
// we just created an activity, now we need to associate it with a user.
// first, we'll need the activity's uuid
NSDictionary *dict = [mostRecentResponse response]; // dictionary for the response
NSArray *entities = [dict objectForKey:@"entities"]; // array for the entities
NSDictionary *activity = [entities objectAtIndex:0]; // dict for the activity
NSString *activityUUID = [activity valueForKey:@"uuid"]; // and finally the uuid string
// fire off the next step
result = [self postUserActivityByUUID:[action userID] activity:activityUUID];
// advance the action
[action setNextAction:kMultiStepCleanup];
}
else if ( [action nextAction] == kMultiStepPostGroupActivity )
{
// we just created an activity, now we need to associate it with a user.
// first, we'll need the activity's uuid
NSDictionary *dict = [mostRecentResponse response]; // dictionary for the response
NSArray *entities = [dict objectForKey:@"entities"]; // array for the entities
NSDictionary *activity = [entities objectAtIndex:0]; // dict for the activity
NSString *activityUUID = [activity valueForKey:@"uuid"]; // and finally the uuid string
// fire off the next step
result = [self postGroupActivityByUUID:[action groupID] activity:activityUUID];
// advance the action
[action setNextAction:kMultiStepCleanup];
}
else if ( [action nextAction] == kMultiStepCleanup )
{
// all we do in cleanup is update the transaction ID of the
// response that was sent in. We do this to ensure that the transaction
// id is constant across the entire transaction
result = mostRecentResponse;
[result setTransactionID:[action outwardTransactionID]];
[action setReportToClient:YES];
}
if ( !mostRecentResponse )
{
// if mostRecentResponse is nil, it means we're on the first step. That means
// we need to adopt a unique outward transaction ID. We'll simply use
// the ID given back by the first transaction in the chain. This also means
// we can simply return the first transaction pending response without modification.
[action setOutwardTransactionID:[result transactionID]];
}
// wherever we landed, if it's a pending transaction, the action needs to
// know that transaction ID. Also, we need to go in to the pending array
if ( [result transactionState] == kUGClientResponsePending )
{
[action setTransactionID:[result transactionID]];
[m_pendingMultiStepActions addObject:action];
}
// result is now properly set up and ready to be handed to the user.
return result;
}
/*************************** LOGIN / LOGOUT ****************************/
/*************************** LOGIN / LOGOUT ****************************/
/*************************** LOGIN / LOGOUT ****************************/
-(UGClientResponse *)logInUser: (NSString *)userName password:(NSString *)password
{
return [self logIn:@"password" userKey:@"username" userValue:userName pwdKey:@"password" pwdValue:password];
}
-(UGClientResponse *)logInUserWithPin: (NSString *)userName pin:(NSString *)pin
{
return [self logIn:@"pin" userKey:@"username" userValue:userName pwdKey:@"pin" pwdValue:pin];
}
-(UGClientResponse *)logInUserWithFacebook: (NSString *)facebookToken
{
NSMutableString *url = [self createURL:@"auth/facebook"];
UGQuery *query = [[UGQuery alloc] init];
[query addURLTerm:@"fb_access_token" equals:facebookToken];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)logInAdmin: (NSString *)adminUserName secret:(NSString *)adminSecret
{
return [self logIn:@"client_credentials" userKey:@"client_id" userValue:adminUserName pwdKey:@"client_secret" pwdValue:adminSecret];
}
-(void)logOut
{
// clear out auth
[self setAuth: nil];
}
// general workhorse for auth logins
-(UGClientResponse *)logIn:(NSString *)grantType userKey:(NSString *)userKey userValue:(NSString *)userValue pwdKey:(NSString *)pwdKey pwdValue:(NSString *)pwdValue
{
// create the URL
NSString *url = [self createURL:@"token"];
// because it's read as form data, we need to escape special characters.
NSString *escapedUserValue = [UGHTTPManager escapeSpecials:userValue];
NSString *escapedPwdValue = [UGHTTPManager escapeSpecials:pwdValue];
// create the post data. For auth functions, we don't use json,
// but instead use web form style data
NSMutableString *postData = [NSMutableString new];
[postData appendFormat:@"grant_type=%@&%@=%@&%@=%@", grantType, userKey, escapedUserValue, pwdKey, escapedPwdValue];
// fire off the request
return [self httpTransaction:url op:kUGHTTPPostAuth opData:postData];
}
/*************************** USER MANAGEMENT ***************************/
/*************************** USER MANAGEMENT ***************************/
/*************************** USER MANAGEMENT ***************************/
-(UGClientResponse *)addUser:(NSString *)username email:(NSString *)email name:(NSString *)name password:(NSString *)password
{
// make the URL we'll be posting to
NSString *url = [self createURL:@"users"];
// make the post data we'll be sending along with it.
NSMutableDictionary *toPost = [NSMutableDictionary new];
[toPost setObject:username forKey:@"username"];
[toPost setObject:name forKey:@"name"];
[toPost setObject:email forKey:@"email"];
[toPost setObject:password forKey:@"password"];
NSString *toPostStr = [self createJSON:toPost];
// fire it off
return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr];
}
// updates a user's password
-(UGClientResponse *)updateUserPassword:(NSString *)usernameOrEmail oldPassword:(NSString *)oldPassword newPassword:(NSString *)newPassword
{
// make the URL we'll be posting to
NSString *url = [self createURL:@"users" append2:usernameOrEmail append3:@"password"];
// make the post data we'll be sending along with it.
NSMutableDictionary *toPost = [NSMutableDictionary new];
[toPost setObject:oldPassword forKey:@"oldpassword"];
[toPost setObject:newPassword forKey:@"newpassword"];
NSString *toPostStr = [self createJSON:toPost];
// fire it off
return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr];
}
-(UGClientResponse *)getGroupsForUser: (NSString *)userID;
{
// make the URL, and fire off the get
NSString *url = [self createURL:@"users" append2:userID append3:@"groups"];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)getUsers: (UGQuery *)query
{
// create the URL
NSMutableString *url = [self createURL:@"users"];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
/************************** ACTIVITY MANAGEMENT **************************/
/************************** ACTIVITY MANAGEMENT **************************/
/************************** ACTIVITY MANAGEMENT **************************/
-(UGClientResponse *)createActivity: (NSDictionary *)activity
{
// make the URL
NSString *url = [self createURL:@"activity"];
// get the json to send.
// we have to json-ify a dictionary that was sent
// in by the client. So naturally, we can't just trust it
// to work. Therefore we can't use our internal convenience
// function for making the json. We go straight to SBJson, so
// we can identify and report any errors.
SBJsonWriter *writer = [SBJsonWriter new];
NSError *jsonError;
NSString *toPostStr = [writer stringWithObject:activity error:&jsonError];
if ( !toPostStr )
{
// error during json assembly
UGClientResponse *ret = [UGClientResponse new];
[ret setTransactionState:kUGClientResponseFailure];
[ret setTransactionID:-1];
[ret setResponse:[jsonError localizedDescription]];
[ret setRawResponse:nil];
return ret;
}
// fire it off
return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr];
}
// create an activity and post it to a user in a single step
-(UGClientResponse *)postUserActivity: (NSString *)userID activity:(NSDictionary *)activity
{
// prep a multi-step action
UGMultiStepAction *action = [UGMultiStepAction new];
// set it up to start the create activity / post to user chain
[action setNextAction:kMultiStepCreateActivity];
[action setUserID:userID];
[action setActivity:activity];
// fire it off
return [self multiStepAction:action];
}
-(UGClientResponse *)postUserActivityByUUID: (NSString *)userID activity:(NSString *)activityUUID
{
// make the URL and fire off the post. there is no data
NSString *url = [self createURL:@"users" append2:userID append3:@"activities" append4:activityUUID];
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
-(UGClientResponse *)postGroupActivity:(NSString *)groupID activity:(NSDictionary *)activity
{
// prep a multi-step action
UGMultiStepAction *action = [UGMultiStepAction new];
// set it up to start the create activity / post to user chain
[action setNextAction:kMultiStepCreateGroupActivity];
[action setGroupID:groupID];
[action setActivity:activity];
// fire it off
return [self multiStepAction:action];
}
-(UGClientResponse *)postGroupActivityByUUID: (NSString *)groupID activity:(NSString *)activityUUID
{
// make the URL and fire off the post. there is no data
NSString *url = [self createURL:@"groups" append2:groupID append3:@"activities" append4:activityUUID];
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
-(UGClientResponse *)getActivitiesForUser: (NSString *)userID query:(UGQuery *)query
{
NSMutableString *url = [self createURL:@"users" append2:userID append3:@"activities"];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)getActivityFeedForUser: (NSString *)userID query:(UGQuery *)query
{
NSMutableString *url = [self createURL:@"users" append2:userID append3:@"feed"];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)getActivitiesForGroup: (NSString *)groupID query:(UGQuery *)query
{
NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"activities"];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)getActivityFeedForGroup: (NSString *)groupID query:(UGQuery *)query
{
NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"feed"];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)removeActivity:(NSString *)activityUUID
{
NSString *url = [self createURL:@"activities" append2:activityUUID];
return [self httpTransaction:url op:kUGHTTPDelete opData:nil];
}
/************************** GROUP MANAGEMENT **************************/
/************************** GROUP MANAGEMENT **************************/
/************************** GROUP MANAGEMENT **************************/
-(UGClientResponse *)createGroup:(NSString *)groupPath groupTitle:(NSString *)groupTitle
{
// make the URL
NSString *url = [self createURL:@"groups"];
// make the post data we'll be sending along with it.
NSMutableDictionary *toPost = [NSMutableDictionary new];
[toPost setObject:groupPath forKey:@"path"];
if ( groupTitle )
{
[toPost setObject:groupTitle forKey:@"title"];
}
NSString *toPostStr = [self createJSON:toPost];
// fire it off
return [self httpTransaction:url op:kUGHTTPPost opData:toPostStr];
}
-(UGClientResponse *)addUserToGroup:(NSString *)userID group:(NSString *)groupID
{
// make the URL
NSString *url = [self createURL:@"groups" append2:groupID append3:@"users" append4:userID];
// fire it off. This is a data-less POST
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
-(UGClientResponse *)removeUserFromGroup:(NSString *)userID group:(NSString *)groupID
{
// this is identical to addUserToGroup, except we use the DELETE method instead of POST
// make the URL
NSString *url = [self createURL:@"groups" append2:groupID append3:@"users" append4:userID];
// fire it off. This is a data-less POST
return [self httpTransaction:url op:kUGHTTPDelete opData:nil];}
-(UGClientResponse *)getUsersForGroup:(NSString *)groupID query:(UGQuery *)query
{
// create the URL
NSMutableString *url = [self createURL:@"groups" append2:groupID append3:@"users"];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
/************************** ENTITY MANAGEMENT **************************/
/************************** ENTITY MANAGEMENT **************************/
/************************** ENTITY MANAGEMENT **************************/
// jsonify the entity. If there's an error, it creates a UGClientResponse and
// returns it. If there's no error, it returns nil, and the outJson field and
// the type field will be set correctly.
// yes, it's odd to have a function return nil on success, but it's internal.
-(UGClientResponse *)validateEntity:(NSDictionary *)newEntity outJson:(NSString **)jsonStr outType:(NSString **)type
{
// validation
NSString *error = nil;
// the entity must exist
if ( !newEntity )
{
error =@"entity is nil";
}
// the entity must have a "type" field
*type = [newEntity valueForKey:@"type"];
if ( !*type )
{
error = @"entity is missing a type field";
}
// make sure it can parse to a json
SBJsonWriter *writer = [SBJsonWriter new];
NSError *jsonError;
*jsonStr = [writer stringWithObject:newEntity error:&jsonError];
if ( !*jsonStr )
{
error = [jsonError localizedDescription];
}
// if error got set to anything, it means we failed
if ( error )
{
UGClientResponse *ret = [UGClientResponse new];
[ret setTransactionState:kUGClientResponseFailure];
[ret setTransactionID:-1];
[ret setResponse:error];
[ret setRawResponse:nil];
return ret;
}
// if we're here, it's a good json and we're done
return nil;
}
-(UGClientResponse *)createEntity:(NSDictionary *)newEntity
{
NSString *jsonStr;
NSString *type;
UGClientResponse *errorRet = [self validateEntity:newEntity outJson:&jsonStr outType:&type];
if ( errorRet ) return errorRet;
// we have a valid entity, ready to post. Make the URL
NSString *url = [self createURL:type];
// post it
return [self httpTransaction:url op:kUGHTTPPost opData:jsonStr];
}
-(UGClientResponse *)getEntities: (NSString *)type query:(UGQuery *)query
{
NSMutableString *url = [self createURL:type];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)updateEntity: (NSString *)entityID entity:(NSDictionary *)updatedEntity
{
NSString *jsonStr;
NSString *type;
UGClientResponse *errorRet = [self validateEntity:updatedEntity outJson:&jsonStr outType:&type];
if ( errorRet ) return errorRet;
// we have a valid entity, ready to post. Make the URL
NSString *url = [self createURL:type append2:entityID];
// post it
return [self httpTransaction:url op:kUGHTTPPut opData:jsonStr];
}
-(UGClientResponse *)removeEntity: (NSString *)type entityID:(NSString *)entityID
{
// Make the URL, then fire off the delete
NSString *url = [self createURL:type append2:entityID];
return [self httpTransaction:url op:kUGHTTPDelete opData:nil];
}
-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType connecteeType:(NSString *)connecteeType connecteeID:(NSString *)connecteeID
{
NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeType append5:connecteeID];
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID
{
NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeID];
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
-(UGClientResponse *)disconnectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID
{
NSString *url = [self createURL:connectorType append2:connectorID append3:connectionType append4:connecteeID];
return [self httpTransaction:url op:kUGHTTPDelete opData:nil];
}
-(UGClientResponse *)getEntityConnections: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType query:(UGQuery *)query
{
NSMutableString *url = [self createURL:connectorType append2:connectorID append3:connectionType];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
/************************** MESSAGE MANAGEMENT **************************/
/************************** MESSAGE MANAGEMENT **************************/
/************************** MESSAGE MANAGEMENT **************************/
-(UGClientResponse *)postMessage: (NSString *)queuePath message:(NSDictionary *)message
{
// because the NSDictionary is from the client, we can't trust it. We need
// to go through full error checking
NSString *error;
NSString *jsonStr = [self createJSON:message error:&error];
if ( !jsonStr )
{
// report the error
UGClientResponse *ret = [UGClientResponse new];
[ret setTransactionID:-1];
[ret setTransactionState:kUGClientResponseFailure];
[ret setResponse:error];
[ret setRawResponse:nil];
return ret;
}
// make the path and fire it off
NSString *url = [self createURL:@"queues" append2:queuePath];
return [self httpTransaction:url op:kUGHTTPPost opData:jsonStr];
}
-(UGClientResponse *)getMessages: (NSString *)queuePath query:(UGQuery *)query;
{
NSMutableString *url = [self createURL:@"queues" append2:queuePath];
[self appendQueryToURL:url query:query];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
-(UGClientResponse *)addSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath
{
NSString *url = [self createURL:@"queues" append2:queuePath append3:@"subscribers" append4:subscriberPath];
return [self httpTransaction:url op:kUGHTTPPost opData:nil];
}
-(UGClientResponse *)removeSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath
{
NSString *url = [self createURL:@"queues" append2:queuePath append3:@"subscribers" append4:subscriberPath];
return [self httpTransaction:url op:kUGHTTPDelete opData:nil];
}
/*************************** REMOTE PUSH NOTIFICATIONS ***************************/
/*************************** REMOTE PUSH NOTIFICATIONS ***************************/
/*************************** REMOTE PUSH NOTIFICATIONS ***************************/
- (UGClientResponse *)setDevicePushToken:(NSData *)newDeviceToken forNotifier:(NSString *)notifier
{
// Pull the push token string out of the device token data
NSString *tokenString = [[[newDeviceToken description]
stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]
stringByReplacingOccurrencesOfString:@" " withString:@""];
// Register device and push token to App Services
NSString *deviceId = [UGClient getUniqueDeviceID];
// create/update device - use deviceId for App Services entity UUID
NSMutableDictionary *entity = [[NSMutableDictionary alloc] init];
[entity setObject: @"device" forKey: @"type"];
[entity setObject: deviceId forKey: @"uuid"];
NSString *notifierKey = [notifier stringByAppendingString: @".notifier.id"];
[entity setObject: tokenString forKey: notifierKey];
return [self updateEntity: deviceId entity: entity];
}
- (UGClientResponse *)pushAlert:(NSString *)message
withSound:(NSString *)sound
to:(NSString *)path
usingNotifier:(NSString *)notifier
{
NSDictionary *apsDict = [NSDictionary dictionaryWithObjectsAndKeys:
message, @"alert",
sound, @"sound",
nil];
NSDictionary *notifierDict = [NSDictionary dictionaryWithObjectsAndKeys:
apsDict, @"aps",
nil];
NSDictionary *payloadsDict = [NSDictionary dictionaryWithObjectsAndKeys:
notifierDict, notifier,
nil];
NSString *notificationsPath = [path stringByAppendingString: @"/notifications"];
NSMutableDictionary *entity = [[NSMutableDictionary alloc] init];
[entity setObject: notificationsPath forKey: @"type"];
[entity setObject: payloadsDict forKey: @"payloads"];
return [self createEntity: entity];
}
/*************************** SERVER-SIDE STORAGE ***************************/
/*************************** SERVER-SIDE STORAGE ***************************/
/*************************** SERVER-SIDE STORAGE ***************************/
+(NSString *)getUniqueDeviceID
{
// cached?
if (g_deviceUUID) return g_deviceUUID;
// in our keychain?
g_deviceUUID = [SSKeychain passwordForService:@"Usergrid" account:@"DeviceUUID"];
if (g_deviceUUID) return g_deviceUUID;
// in the (legacy) app defaults?
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
g_deviceUUID = [defaults valueForKey:@"UGClientDeviceUUID"];
// if none found in storage, generate one
if (!g_deviceUUID) {
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"6.0")) {
// use identifierForVendor where possible
g_deviceUUID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
}
else {
// otherwise, create a UUID (legacy method)
CFUUIDRef uuidRef = CFUUIDCreate(nil);
CFStringRef uuidStringRef = CFUUIDCreateString(nil, uuidRef);
CFRelease(uuidRef);
g_deviceUUID = [NSString stringWithString:(__bridge NSString *)uuidStringRef];
}
}
// store in keychain for future reference
[SSKeychain setPassword:g_deviceUUID forService:@"Usergrid" account:@"DeviceUUID"];
return g_deviceUUID;
}
-(UGClientResponse *)setRemoteStorage: (NSDictionary *)data
{
// prep and validate the sent-in dict
NSString *error;
NSString *jsonStr = [self createJSON:data error:&error];
if ( !jsonStr )
{
// report the error
UGClientResponse *ret = [UGClientResponse new];
[ret setTransactionID:-1];
[ret setTransactionState:kUGClientResponseFailure];
[ret setResponse:error];
[ret setRawResponse:nil];
return ret;
}
NSString *handsetUUID = [UGClient getUniqueDeviceID];
NSString *url = [self createURL:@"devices" append2:handsetUUID];
// this is a put. We replace whatever was there before
return [self httpTransaction:url op:kUGHTTPPut opData:jsonStr];
}
-(UGClientResponse *)getRemoteStorage
{
NSString *handsetUUID = [UGClient getUniqueDeviceID];
NSString *url = [self createURL:@"devices" append2:handsetUUID];
return [self httpTransaction:url op:kUGHTTPGet opData:nil];
}
/***************************** OBLIQUE USAGE ******************************/
-(UGClientResponse *)apiRequest: (NSString *)url operation:(NSString *)op data:(NSString *)opData
{
// work out the op to use
int opID = kUGHTTPGet;
if ( [op isEqualToString:@"GET"] ) opID = kUGHTTPGet;
if ( [op isEqualToString:@"POST"] ) opID = kUGHTTPPost;
if ( [op isEqualToString:@"POSTFORM"] ) opID = kUGHTTPPostAuth;
if ( [op isEqualToString:@"PUT"] ) opID = kUGHTTPPut;
if ( [op isEqualToString:@"DELETE"] ) opID = kUGHTTPDelete;
// fire it off. The data, formatting, etc. is all the client's problem.
// That's the way oblique functionality is.
return [self httpTransaction:url op:opID opData:opData];
}
/**************************** LOGGING ************************************/
-(void)setLogging: (BOOL)loggingState
{
m_bLogging = loggingState;
}
@end