You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by sn...@apache.org on 2016/10/24 13:06:18 UTC

[33/83] [abbrv] usergrid git commit: Moving older SDKs to a difference location and updating main README to link to new SDK locations.

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClient.h
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGClient.h b/sdks/other/ios/UGAPI/UGClient.h
new file mode 100755
index 0000000..4e6a31f
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGClient.h
@@ -0,0 +1,328 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "UGClientResponse.h"
+#import "UGQuery.h"
+#import "UGActivity.h"
+#import "UGUser.h"
+
+/******************** A WORD ON NETWORK COMMUNICATION CALLS ****************
+Some calls require network communication with UserGrid. Therefore,
+they all have the option of being synchronous (blocking) or asynchronous.
+
+You may specify an asynchronous delegate with the call setDelegate. If you
+do, all calls will be asynchronous and responses will be sent to that delegate. 
+The immediate return value (a UGClientResponse *) from any call will have its
+transactionState set to kUGClientResponsePending, and the transactionID will be
+properly set (allowing you to identify the specific call in your callback if you
+wish.)
+ 
+The delegate must support the following message:
+-(void)ugClientResponse:(UGClientResponse *)response
+
+If you do not set a delegate, all functions will run synchronously, blocking 
+until a response has been received or an error detected. 
+****************************************************************************/
+
+
+/**************************** A WORD ON UGQUERY *****************************
+Some calls take a UGQuery *. These are functions that return a lot of data, as 
+opposed to a simple answer. You may use the UGQuery to control the data with filters
+and response limits. See UGQuery.h for more information.
+
+In all cases, where a UGQuery is one of the parameters, you may send nil. If you
+do, the query will be completely unfiltered, and you will receive back *all* the data
+associated with the operation, up to the response limit, which is 10. You can
+set the response limit in UGQuery as well. 
+****************************************************************************/
+
+
+@interface UGClient : NSObject
+
++(NSString *) version;
+
+/********************* INIT AND SETUP *********************/
+// init with an app ID
+-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID;
+
+// init with an app ID and a base UserGrid URL. This is useful if you
+// are running a local UG server or your company has its own public
+// UG server. The default URL is http://api.usergrid.com. The base URL
+// must be a fully formated http link, including the "http://" at the
+// beginning.
+-(id) initWithOrganizationId: (NSString *)organizationID withApplicationID:(NSString *)applicationID baseURL:(NSString *)baseURL;
+
+// set the delegate. See "A WORD ON NETWORK COMMUNICATION CALLS"
+// at the top of the file for a detailed explanation. The function
+// will return NO if the delegate is rejected. This means the delegate
+// does not support the required delegation function "ugClientResponse".
+//
+// This is the formal declaration of ugClientResponse:
+// -(void)ugClientResponse:(UGClientResponse *)response
+// 
+// You may change the delegate at any time, but be forewarned that any
+// pending transactions in progress will be abandoned. Changing the delegate
+// (especially setting it to nil) ensures that hte previous delegate will
+// receive no further messages from this instance of UGClient. 
+// 
+// Setting the delegate to nil puts the API in to synchronous mode.
+-(BOOL) setDelegate:(id)delegate;
+
+
+/********************* LOGIN / LOGOUT *********************/
+// log in with the given username and password
+-(UGClientResponse *)logInUser: (NSString *)userName password:(NSString *)password;
+
+// log in with the given username and PIN value
+-(UGClientResponse *)logInUserWithPin: (NSString *)userName pin:(NSString *)pin;
+
+// log in user with Facebook token
+//
+//  //sample usage:
+//  NSString * facebookToken = @"your-facebook-token";
+//  UGClientResponse *response = [usergridClient logInUserWithFacebook:facebookToken];
+//  user = [usergridClient getLoggedInUser];
+//  if (user.username){
+//    return true;
+//  } else {
+//    return false;
+//  }
+//
+-(UGClientResponse *)logInUserWithFacebook: (NSString *)facebookToken;
+
+// log in as the administrator of the application. Generally used for applications
+// that have an "administrator" feature. Not the sort of thing you want normal
+// users doing. 
+-(UGClientResponse *)logInAdmin: (NSString *)adminUserName secret:(NSString *)adminSecret;
+
+// log out the current user. The Client only supports one user logged in at a time.
+// You can have multiple instances of UGClient if you want multiple
+// users doing transactions simultaneously. This does not require network communication,
+// so it has no return. It doesn't actually "log out" from the server. It simply clears 
+// the locally stored auth information
+-(void)logOut;
+
+
+
+/********************* USER MANAGEMENT *********************/
+//adds a new user
+-(UGClientResponse *)addUser:(NSString *)username email:(NSString *)email name:(NSString *)name password:(NSString *)password;
+
+// updates a user's password
+-(UGClientResponse *)updateUserPassword:(NSString *)usernameOrEmail oldPassword:(NSString *)oldPassword newPassword:(NSString *)newPassword;
+
+// get all the groups this user is in
+-(UGClientResponse *)getGroupsForUser: (NSString *)userID;
+
+// get users in this app. Definitely want to consider sending a Query along
+// with this call
+-(UGClientResponse *)getUsers: (UGQuery *)query;
+
+/********************* ACTIVITY MANAGEMENT *********************/
+// create a new activity. 
+// Note that there is a class, UGActivity, which can help
+// you create and validate an Activity, and will generate the needed
+// NSDictionary for you.
+-(UGClientResponse *)createActivity: (NSDictionary *)activity;
+
+// create an activity and post it to a user in a single step. See comment
+// above createActivity for information on making Activity creation easier
+-(UGClientResponse *)postUserActivity: (NSString *)userID activity:(NSDictionary *)activity;
+
+// post an already-created activity to a user
+-(UGClientResponse *)postUserActivityByUUID: (NSString *)userID activity:(NSString *)activityUUID;
+
+// create an activity and post it to a group in a single step. See comment
+// above createActivity for information on making Activity creation easier
+-(UGClientResponse *)postGroupActivity: (NSString *)groupID activity:(NSDictionary *)activity;
+
+// post an already-created activity to a group
+-(UGClientResponse *)postGroupActivityByUUID: (NSString *)groupID activity:(NSString *)activityUUID;
+
+// get the activities this user is in
+-(UGClientResponse *)getActivitiesForUser: (NSString *)userID query:(UGQuery *)query;
+
+// get the activities this group is in
+-(UGClientResponse *)getActivitiesForGroup: (NSString *)groupID query:(UGQuery *)query;
+
+// get the activity feed for a user
+-(UGClientResponse *)getActivityFeedForUser: (NSString *)userID query:(UGQuery *)query;
+
+// get the activity feed for a group
+-(UGClientResponse *)getActivityFeedForGroup: (NSString *)groupID query:(UGQuery *)query;
+
+// remove an activity 
+-(UGClientResponse *)removeActivity:(NSString *)activityUUID;
+
+/********************* GROUP MANAGEMENT *********************/
+// create a new group. The groupPath can be a path with slashes to make for
+// a hierarchical structure of your own design (if you want). groupTitle is
+// optional, you can send nil if you don't want to provide one.
+-(UGClientResponse *)createGroup:(NSString *)groupPath groupTitle:(NSString *)groupTitle;
+
+// add a user to a group
+-(UGClientResponse *)addUserToGroup:(NSString *)userID group:(NSString *)groupID;
+
+// remove a user from a group
+-(UGClientResponse *)removeUserFromGroup:(NSString *)userID group:(NSString *)groupID;
+
+// get all the users in this group
+-(UGClientResponse *)getUsersForGroup:(NSString *)groupID query:(UGQuery *)query;
+
+
+
+/******************** ENTITY MANAGEMENT ********************/
+// adds an entity to the specified collection. 
+-(UGClientResponse *)createEntity: (NSDictionary *)newEntity;
+
+// get a list of entities that meet the specified query.
+-(UGClientResponse *)getEntities: (NSString *)type query:(UGQuery *)query;
+
+// updates an entity (it knows the type from the entity data) 
+-(UGClientResponse *)updateEntity: (NSString *)entityID entity:(NSDictionary *)updatedEntity;
+
+// removes an entity of the specified type
+-(UGClientResponse *)removeEntity: (NSString *)type entityID:(NSString *)entityID;
+
+// Directionally connect two entities. For instance, user "Bob" might like Lyons Restaurant.
+// connectorType would be "users" (because Bob is a user)
+// connectorID would be Bob's userID
+// connectionType would be "like"
+// connecteeID would be the UUID of Lyons Restaurant
+-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID;
+
+// Directionally connect two entities. For instance, user "Bob" might follow user "Mary".
+// connectorType would be "users" (because Bob is a user)
+// connectorID would be Bob's userID
+// connectionType would be "like"
+// connecteeType would  be "users" (because Mary is a user)
+// connecteeID would be Mary's userID
+-(UGClientResponse *)connectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType connecteeType:(NSString *)connecteeType connecteeID:(NSString *)connecteeID;
+
+// disconnect two entities. It uses the same parameters and calling rules as connectEntities
+-(UGClientResponse *)disconnectEntities: (NSString *)connectorType connectorID:(NSString *)connectorID type:(NSString *)connectionType connecteeID:(NSString *)connecteeID;
+
+// get entity connections
+-(UGClientResponse *)getEntityConnections: (NSString *)connectorType connectorID:(NSString *)connectorID connectionType:(NSString *)connectionType query:(UGQuery *)query;
+
+
+
+/********************* MESSAGE MANAGEMENT *********************/
+// post a message to a given queue
+-(UGClientResponse *)postMessage: (NSString *)queuePath message:(NSDictionary *)message;
+
+// get all messages from the queue path
+-(UGClientResponse *)getMessages: (NSString *)queuePath query:(UGQuery *)query;
+
+// add a subscriber to a queue
+-(UGClientResponse *)addSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath;
+
+// remove a subscriber from a queue
+-(UGClientResponse *)removeSubscriber: (NSString *)queuePath subscriberPath:(NSString *)subscriberPath;
+
+
+/********************* SERVER-SIDE STORAGE *********************/
+// these functions refer to data that can be put in a special place 
+// specific to this device. Every call to remoteStorage replaces whatever 
+// was there before
+
+// put the data in to the remote storage
+-(UGClientResponse *)setRemoteStorage: (NSDictionary *)data;
+
+// get the data from remote storage
+-(UGClientResponse *)getRemoteStorage;
+
+// a class function that returns a uuid for this
+// device. It will be globally unique, and will always
+// return the same value for the same handset.
+// NOTE - This value will change if the operating
+// system is reinstalled. This function is used internally, but
+// is also handy for clients, so it is part of the interface.
++(NSString *)getUniqueDeviceID;
+
+/***************** REMOTE PUSH NOTIFICATIONS *****************/
+
+// call from application:didRegisterForRemoteNotificationsWithDeviceToken: callback
+// will automatically register the passed deviceToken with the usergrid system
+// using the getUniqueDeviceID method to associate this device on the server
+- (UGClientResponse *)setDevicePushToken:(NSData *)newDeviceToken forNotifier:(NSString *)notifier;
+
+// push an "alert" type notification to the remote group, user, or device specified
+// in the path argument. the notifer may be a name or UUID of an apns notifier
+// that has been set up on the usergrid server.
+- (UGClientResponse *)pushAlert:(NSString *)message
+                      withSound:(NSString *)sound
+                             to:(NSString *)path
+                  usingNotifier:(NSString *)notifier;
+
+/*********************** ACCESSORS ************************/
+// if a user is logged in, this returns the OAuth token for this session. 
+// UGClient manages this internally, so you never really need it. But if you
+// want it for other reasons, this accessor gives it to you. If you have not
+// successfully logged in, this will return nil
+-(NSString *)getAccessToken;
+
+// returns information about the logged in user
+-(UGUser *)getLoggedInUser;
+
+// returns the delegate that is currently being used for asynch
+// calls. Returns nil if there is no delegate (synch mode)
+-(id) getDelegate;
+
+/*********************** OBLIQUE USAGE ************************/
+// This is a general purpose function for directly accessing the
+// UserGrid service. This is useful if the service has new features 
+// that the API has not yet supported, or if you are using an older 
+// version of the API and don't want to upgrade.
+// 
+// url: The full URL that you are accessing. You are responsible for 
+//      assembling it, including the appID and all sub-sections down the line
+//
+// op: The HttpMethod being invoked. Examples: @"POST", @"PUT", etc. You may
+//     send nil. If you do, the operation is GET. There is one specially supported
+//     method called "POSTFORM". This will post with the data type set to 
+//     application/x-www-form-urlencoded instead of the more likely needed
+//     application/json. This is necessary if you are doing authentication
+//     or if you are sending form data up. 
+//
+// opData: The data sent along with the operation. You may send nil. If the 
+//         operation is GET, this value is ignored. Usually, this would be
+//         expected to be in json format. With this oblique approach, it is
+//         your responsibility to format the data correctly for whatever you're
+//         doing. Bear in mind that this api comes with SBJson, which provides
+//         some very simple ways to assemble json formatted strings. See SBJsonWriter.
+//
+// NOTE - This function will be synchronous or asynchronous the same as any
+// other function in the API. It is based on the value sent to setDelegate.
+-(UGClientResponse *)apiRequest: (NSString *)url operation:(NSString *)op data:(NSString *)opData;
+
+/*********************** DEBUGGING ASSISTANCE ************************/
+// when logging is on, all outgoing URLs are logged via NSLog, and all
+// incoming data from the service is also logged. Additionally, any errors
+// encountered internally are logged. This can be helpful to see the actual
+// service communication in progress and help debug problems you may be having. 
+-(void)setLogging: (BOOL)loggingState;
+
+/*********************** VERSION CHECKING ************************/
+#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
+#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
+#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
+#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
+#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClient.m
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGClient.m b/sdks/other/ios/UGAPI/UGClient.m
new file mode 100755
index 0000000..9d6407a
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGClient.m
@@ -0,0 +1,1252 @@
+/*
+ * 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
+
+

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClientDelegate.h
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGClientDelegate.h b/sdks/other/ios/UGAPI/UGClientDelegate.h
new file mode 100644
index 0000000..28bc996
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGClientDelegate.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <Foundation/Foundation.h>
+#import "UGClientResponse.h"
+
+/******************************A NOTE ON THIS DELEGATE********************************
+Objects conform to this protocol to take advantage of the Asynchronus SDK functionality.
+The setDelegate method needs to be called on the current UGClient for this function to
+be called on an implemented delegate.
+ 
+If you do not set a delegate, all functions will run synchronously, blocking
+until a response has been received or an error detected.
+*************************************************************************************/
+@protocol UGClientDelegate <NSObject>
+
+//This method is called after every request to the UserGrid API.
+//It passes in the response to the API request, and returns nothing.
+-(void)ugClientResponse:(UGClientResponse *)response;
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClientResponse.h
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGClientResponse.h b/sdks/other/ios/UGAPI/UGClientResponse.h
new file mode 100755
index 0000000..01bc5a1
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGClientResponse.h
@@ -0,0 +1,59 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+
+// response states
+enum
+{
+    kUGClientResponseSuccess = 0,
+    kUGClientResponseFailure = 1,
+    kUGClientResponsePending = 2
+};
+
+
+@interface UGClientResponse : NSObject
+
+// this will be a unique ID for this transaction. If you have
+// multiple transactions in progress, you can keep track of them
+// with this value. Note: The transaction ID of a synchronous
+// call response is always -1.
+@property int transactionID;
+
+// this will be one of three possible valuse:
+// kUGClientResponseSuccess: The operation is complete and was successful. response will 
+//                          be valid, as will rawResponse
+//
+// kUGClientResponseFailure: There was an error with the operation. No further 
+//                          processing will be done. response will be an NSString with
+//                          a plain-text description of what went wrong. rawResponse
+//                          will be valid if the error occurred after receiving data from
+//                          the service. If it occurred before, rawResponse will be nil.
+//
+// kUGClientResponsePending: The call is being handled asynchronously and not yet complete. 
+//                          response will be nil. rawResponse will also be nil
+@property int transactionState;
+
+// This is the response. The type of this variable is dependant on the call that caused
+// this response. 
+@property id response;
+
+// This is the raw text that was returned by the server. 
+@property NSString *rawResponse;
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGClientResponse.m
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGClientResponse.m b/sdks/other/ios/UGAPI/UGClientResponse.m
new file mode 100755
index 0000000..dc49fb4
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGClientResponse.m
@@ -0,0 +1,40 @@
+/*
+ * 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 "UGClientResponse.h"
+
+@implementation UGClientResponse
+
+@synthesize transactionID;
+@synthesize transactionState;
+@synthesize response;
+@synthesize rawResponse;
+
+-(id)init
+{
+    self = [super init];
+    if ( self )
+    {
+        transactionID = -1;
+        transactionState = -1;
+        response = nil;
+        rawResponse = nil;
+    }
+    return self;
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGHTTPManager.h
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGHTTPManager.h b/sdks/other/ios/UGAPI/UGHTTPManager.h
new file mode 100755
index 0000000..d6c1822
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGHTTPManager.h
@@ -0,0 +1,74 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+enum
+{
+    kUGHTTPGet = 0,
+    kUGHTTPPost = 1,
+    kUGHTTPPostAuth = 2,
+    kUGHTTPPut = 3,
+    kUGHTTPDelete = 4
+};
+
+@interface UGHTTPManager : NSObject
+
+// blocks until a response is received, or until there's an error.
+// in the event of a response, it's returned. If there's an error, 
+// the funciton returns nil and you can call getLastError to see what
+// went wrong.
+-(NSString *)syncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData;
+
+// sets up the transaction asynchronously. The delegate that's sent in
+// must have the following functions: 
+//
+// -(void)httpManagerError:(UGHTTPManager *)manager error:(NSString *)error
+// -(void)httpManagerResponse:(UGHTTPManager *)manager response:(NSString *)response
+//
+// In all cases, it returns a transaction ID. A return value
+// of -1 means there was an error.
+// You can call getLastError to find out what went wrong. 
+-(int)asyncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData delegate:(id)delegate;
+
+// get the current transactionID
+-(int)getTransactionID;
+
+// sets the auth key
+-(void)setAuth: (NSString *)auth;
+
+// cancel a pending transaction. The delegate will not be called and the results
+// will be ignored. Though the server side will still have happened.
+-(void)cancel;
+
+// returns YES if this instance is available. NO if this instance is currently
+// in use as part of an asynchronous transaction.
+-(BOOL)isAvailable;
+
+// sets the availability flag of this instance. This is done by UGClient
+-(void)setAvailable:(BOOL)available;
+
+// a helpful utility function to make a string comform to URL
+// rules. It will escape all the special characters.
++(NSString *)escapeSpecials:(NSString *)raw;
+
+// At all times, this will return the plain-text explanation of the last
+// thing that went wrong. It is cleared to "No Error" at the beginnign of 
+// each new transaction.
+-(NSString *)getLastError;
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGHTTPManager.m
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGHTTPManager.m b/sdks/other/ios/UGAPI/UGHTTPManager.m
new file mode 100755
index 0000000..5d62fe9
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGHTTPManager.m
@@ -0,0 +1,328 @@
+/*
+ * 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 "UGHTTPManager.h"
+
+// all transaction IDs are unique across all UGHTTPManagers. 
+// this global increases every time there's an asynchronous 
+// transaction. 
+static int g_nextTransactionID = 1;  
+
+// a mutex to protect against multiple threads getting
+// IDs at the same time, and possibly getting the same ID
+// because of it
+NSRecursiveLock *g_transactionIDLock = nil;
+
+@implementation UGHTTPManager
+{
+    // data management for collecting incoming HTTP data
+    // during asynch transactions
+    NSMutableData *m_receivedData;
+    
+    // a general error string
+    NSString *m_lastError;
+    
+    // the delegate sent in to the asynch method
+    id m_delegate;
+    
+    // the transaction ID of the current (or most recent) transaction
+    int m_transactionID;
+    
+    // availability of this instance. Managed by UGClient
+    BOOL m_bAvailable;
+    
+    // mutex used to ensure the delegate is not changed
+    // in a way that would cause a race condition.
+    NSRecursiveLock *m_delegateLock;        
+    
+    // the auth key to send along to requests
+    NSString *m_auth;
+}
+
+-(id)init
+{
+    self = [super init];
+    if ( self )
+    {
+        m_lastError = @"No error";
+        m_receivedData = [NSMutableData data];
+        m_bAvailable = YES;
+        m_delegateLock = [NSRecursiveLock new];
+        m_transactionID = -1;
+        m_auth = nil;
+        
+        // lazy-init the transaction lock
+        if ( !g_transactionIDLock )
+        {
+            g_transactionIDLock = [NSRecursiveLock new];
+        }
+    }
+    return self;
+}
+
+-(void)setAuth: (NSString *)auth
+{
+    m_auth = auth;
+}
+
+-(NSString *)getLastError
+{
+    return m_lastError;
+}
+
+-(int)getNextTransactionID
+{
+    // make sure we can use this lock
+    assert(g_transactionIDLock);
+    
+    [g_transactionIDLock lock];
+    int ret = g_nextTransactionID++;
+    [g_transactionIDLock unlock];
+    
+    return ret;
+}
+
+-(int)getTransactionID
+{
+    return m_transactionID;
+}
+
+
+//----------------------- SYNCHRONOUS CALLING ------------------------
+-(NSString *)syncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData;
+{
+    // clear the transaction ID
+    m_transactionID = -1;
+    
+    // use the synchronous funcitonality of NSURLConnection
+    // clear the error
+    m_lastError = @"No error";
+    
+    // formulate the request
+    NSURLRequest *req = [self getRequest:url operation:op operationData:opData];
+    
+    NSURLResponse *response;
+    NSError *error;
+    NSData *resultData = [NSURLConnection sendSynchronousRequest:req returningResponse:&response error:&error];
+    
+    if ( resultData )
+    {
+        // we got results
+        NSString *resultString = [[NSString alloc] initWithData:resultData encoding:NSUTF8StringEncoding];        
+        return resultString;
+    }
+    
+    // if we're here, it means we got nil as the result
+    m_lastError = [error localizedDescription];
+    return nil;
+}
+
+//----------------------- ASYNCHRONOUS CALLING ------------------------
+-(int)asyncTransaction:(NSString *)url operation:(int)op operationData:(NSString *)opData delegate:(id)delegate;
+{
+    // clear the transaction ID
+    m_transactionID = -1;
+    
+    // clear the error
+    m_lastError = @"No error";
+    
+    if ( !delegate )
+    {
+        // an asynch transaction with no delegate has no meaning
+        m_lastError = @"Delegate was nil";
+        return -1;
+    }
+    
+    // make sure the delegate responds to the various messages that
+    // are required
+    if ( ![delegate respondsToSelector:@selector(httpManagerError:error:)] )
+    {
+        m_lastError = @"Delegate does not have httpManagerError:error: method";
+        return -1;
+    }
+    if ( ![delegate respondsToSelector:@selector(httpManagerResponse:response:)] )
+    {
+        m_lastError = @"Delegate does not have httpManagerResponse:response: method";
+        return -1;
+    }
+    
+    // only once we're assured everything is right do we set the internal value
+    [m_delegateLock lock];
+    m_delegate = delegate;
+    [m_delegateLock unlock];
+    
+    // prep a transaction ID for this transaction
+    m_transactionID = [self getNextTransactionID];
+   
+    // formulate the request
+    NSURLRequest *req = [self getRequest:url operation:op operationData:opData];
+        
+    // fire it off
+    NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
+    
+    // Note failure
+    if ( !conn )
+    {
+        // failed to connect
+        m_lastError = @"Unable to initiate connection.";
+        return -1;       
+    }
+    
+    // success
+    return m_transactionID;
+}
+
+-(BOOL)isAvailable
+{
+    return m_bAvailable;
+}
+
+-(void)setAvailable:(BOOL)available
+{
+    m_bAvailable = available;
+}
+
+-(void)cancel
+{
+    // we wrap this in a lock to ensure that the client can
+    // call it at any time. It will not cause a race condition in 
+    // any callback thread.
+    [m_delegateLock lock];
+    m_delegate = nil;
+    [m_delegateLock unlock];
+    
+    // note that we do not modify the "in use" flag. If we werei n use,
+    // we remain in use until we receive a response or error. This ensures 
+    // no confusion on any subsequent transaction. We don't want the case 
+    // where a transaction is started, then cancelled, then a new transaction begun
+    // before the first transaction's result comes in. That would lead to the second 
+    // transaction being answered with the first's reply. We avoid that possibility by
+    // simply remaining "in use" until we get the reply or error.
+}
+
+// general helper function form aking escaped-strings
++(NSString *)escapeSpecials:(NSString *)raw;
+{
+    NSString *converted = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)raw, nil, CFSTR(";/?:@&=$+{}<>"), kCFStringEncodingUTF8);
+    return converted;
+}
+
+// INTERNAL function for forming the request
+-(NSURLRequest *)getRequest:(NSString *)url operation:(int)op operationData:(NSString *)opStr;
+{
+    // make the url
+    NSURL *nsurl = [NSURL URLWithString:url];
+        
+    NSMutableURLRequest *req = [NSMutableURLRequest new];
+    [req setURL:nsurl];
+    
+    switch ( op ) 
+    {
+        case kUGHTTPGet: [req setHTTPMethod:@"GET"]; break;
+        case kUGHTTPPost: [req setHTTPMethod:@"POST"]; break;
+        case kUGHTTPPostAuth: [req setHTTPMethod:@"POST"]; break;
+        case kUGHTTPPut: [req setHTTPMethod:@"PUT"]; break;
+        case kUGHTTPDelete: [req setHTTPMethod:@"DELETE"]; break;
+    }
+
+    // set the auth, if any is available
+    if ( m_auth )
+    {
+        NSMutableString *authStr = [NSMutableString new];
+        [authStr appendFormat:@"Bearer %@", m_auth];
+        [req setValue:authStr forHTTPHeaderField:@"Authorization"];
+    }
+
+    // if they sent an opStr, we make that the content
+    if ( opStr )
+    {
+        // prep the post data
+        NSData *opData = [opStr dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
+        
+        // make a string that tells the length of the post data. We'll need that for the HTTP header setup
+        NSString *opLength = [NSString stringWithFormat:@"%d", [opData length]];
+        
+        [req setValue:opLength forHTTPHeaderField:@"Content-Length"];
+        
+        // PostAuth uses form encoding. All other operations use json
+        if ( op == kUGHTTPPostAuth )
+        {
+            [req setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
+        }
+        else
+        {
+            [req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
+        }
+        
+        [req setHTTPBody:opData];
+    }
+    
+    // all set up and ready for use
+    return req;
+    
+}
+
+//------------------------------------------------------------------------------------------
+//-------------------------- NSURLCONNECTION DELEGATE METHODS ------------------------------
+//------------------------------------------------------------------------------------------
+-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
+{
+    // got a response. clear out the data
+    [m_receivedData setLength:0];
+}
+
+-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
+{
+    // got some data. Append it.
+    [m_receivedData appendData:data];
+}
+
+-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
+{
+     // connection failed. Note the error
+    m_lastError = [error localizedDescription];
+    
+    // send the error to the delegate. We wrap this in 
+    // send the result to the delegate.
+    // wrap this in mutex locks, then check for validity of m_delegate inside.
+    // this ensures no race conditions and allows arbitrary cancellation of callbacks
+    [m_delegateLock lock];
+    if ( m_delegate )
+    {
+        [m_delegate performSelector:@selector(httpManagerError:error:) withObject:self withObject:m_lastError];
+    }
+    m_delegate = nil;
+    [m_delegateLock unlock];
+}
+
+-(void)connectionDidFinishLoading:(NSURLConnection*)connection
+{
+    // all done. Let's turn it in to a string
+    NSString *resultString = [[NSString alloc] initWithData:m_receivedData encoding:NSUTF8StringEncoding];
+    
+    // send it to the delegate. See connection:didFailWithError: for an explanation
+    // of the mutex locks
+    [m_delegateLock lock];
+    if ( m_delegate )
+    {
+        [m_delegate performSelector:@selector(httpManagerResponse:response:) withObject:self withObject:resultString];   
+    }
+    m_delegate = nil;
+    [m_delegateLock unlock];
+}
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGMultiStepAction.h
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGMultiStepAction.h b/sdks/other/ios/UGAPI/UGMultiStepAction.h
new file mode 100755
index 0000000..fbc1259
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGMultiStepAction.h
@@ -0,0 +1,58 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+// an enumeration for multi-step processes. 
+enum
+{
+    kMultiStepNone = 0,
+    kMultiStepCreateActivity = 1, // create an activity
+    kMultiStepPostActivity = 2,   // after creating an activity, post it to the user
+    kMultiStepCreateGroupActivity = 3, // create an activity
+    kMultiStepPostGroupActivity = 4,   // after creating an activity, post it to the user
+    kMultiStepCleanup = 5         // the final step of all multi-step transactions
+};
+
+// UGMultiStepAction is used internally for client actions that
+// require multiple transactions with the service. It is simple data
+// storage, used by UGClient in abstracting out multi-step transactions
+@interface UGMultiStepAction : NSObject
+
+// the transaction ID that this multistep is associated
+// with. When a transaction of this ID is complete, this is the
+// UGMultiStepAction instance ot ask what to do next
+@property int transactionID;
+
+// the next action this transaction should take
+@property int nextAction;
+
+// data necessary for subsequent steps
+@property NSString *userID; 
+@property NSString *groupID; 
+@property NSDictionary *activity; 
+
+// the transactionID that will be sent to the user.
+// This is distinct from normal transaction IDs,
+// which we use internally at each step. 
+@property int outwardTransactionID;
+
+// YES if this action should be reported to the
+// caller .No if not.
+@property BOOL reportToClient;
+
+@end

http://git-wip-us.apache.org/repos/asf/usergrid/blob/867060fa/sdks/other/ios/UGAPI/UGMultiStepAction.m
----------------------------------------------------------------------
diff --git a/sdks/other/ios/UGAPI/UGMultiStepAction.m b/sdks/other/ios/UGAPI/UGMultiStepAction.m
new file mode 100755
index 0000000..6a1b7c7
--- /dev/null
+++ b/sdks/other/ios/UGAPI/UGMultiStepAction.m
@@ -0,0 +1,40 @@
+/*
+ * 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 "UGMultiStepAction.h"
+
+@implementation UGMultiStepAction
+
+@synthesize transactionID;
+@synthesize nextAction;
+@synthesize userID;
+@synthesize groupID;
+@synthesize activity;
+@synthesize outwardTransactionID;
+@synthesize reportToClient;
+
+
+-(id)init
+{
+    self = [super init];
+    if ( self )
+    {
+        reportToClient = NO;
+    }
+    return self;
+}
+@end