http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridEnums.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridEnums.swift b/sdks/swift/Source/UsergridEnums.swift new file mode 100644 index 0000000..1cc0c9c --- /dev/null +++ b/sdks/swift/Source/UsergridEnums.swift @@ -0,0 +1,415 @@ +// +// UsergridEnums.swift +// UsergridSDK +// +// Created by Robert Walsh on 10/21/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +/** +An enumeration that is used to determine what the `UsergridClient` will fallback to depending on certain authorization conditions. +*/ +@objc public enum UsergridAuthFallback : Int { + + // MARK: - Values - + + /** + If a non-expired user auth token exists in `UsergridClient.currentUser`, this token is used to authenticate all API calls. + + If the API call fails, the activity is treated as a failure with an appropriate HTTP status code. + + If a non-expired user auth token does not exist, all API calls will be made unauthenticated. + */ + case None + /** + If a non-expired user auth token exists in `UsergridClient.currentUser`, this token is used to authenticate all API calls. + + If the API call fails, the activity is treated as a failure with an appropriate HTTP status code (This behavior is identical to authFallback=.None). + + If a non-expired user auth does not exist, all API calls will be made using stored app auth. + */ + case App +} + +/** +`UsergridEntity` specific properties keys. Note that trying to mutate the values of these properties will not be allowed in most cases. +*/ +@objc public enum UsergridEntityProperties : Int { + + // MARK: - Values - + + /// Corresponds to the property 'type' + case EntityType + /// Corresponds to the property 'uuid' + case UUID + /// Corresponds to the property 'name' + case Name + /// Corresponds to the property 'created' + case Created + /// Corresponds to the property 'modified' + case Modified + /// Corresponds to the property 'location' + case Location + + // MARK: - Methods - + + /** + Gets the corresponding `UsergridEntityProperties` from a string if it's valid. + + - parameter stringValue: The string value to convert. + + - returns: The corresponding `UsergridEntityProperties` or nil. + */ + public static func fromString(stringValue: String) -> UsergridEntityProperties? { + switch stringValue.lowercaseString { + case ENTITY_TYPE: return .EntityType + case ENTITY_UUID: return .UUID + case ENTITY_NAME: return .Name + case ENTITY_CREATED: return .Created + case ENTITY_MODIFIED: return .Modified + case ENTITY_LOCATION: return .Location + default: return nil + } + } + + /// Returns the string value. + public var stringValue: String { + switch self { + case .EntityType: return ENTITY_TYPE + case .UUID: return ENTITY_UUID + case .Name: return ENTITY_NAME + case .Created: return ENTITY_CREATED + case .Modified: return ENTITY_MODIFIED + case .Location: return ENTITY_LOCATION + } + } + + /** + Determines if the `UsergridEntityProperties` is mutable for the given entity. + + - parameter entity: The entity to check. + + - returns: If the `UsergridEntityProperties` is mutable for the given entity + */ + public func isMutableForEntity(entity:UsergridEntity) -> Bool { + switch self { + case .EntityType,.UUID,.Created,.Modified: return false + case .Location: return true + case .Name: return entity.isUser + } + } +} + +/** +`UsergridDeviceProperties` specific properties keys. Note that trying to mutate the values of these properties will not be allowed in most cases. +*/ +@objc public enum UsergridDeviceProperties : Int { + + // MARK: - Values - + + /// Corresponds to the property 'deviceModel' + case Model + /// Corresponds to the property 'devicePlatform' + case Platform + /// Corresponds to the property 'deviceOSVersion' + case OSVersion + + // MARK: - Methods - + + /** + Gets the corresponding `UsergridDeviceProperties` from a string if it's valid. + + - parameter stringValue: The string value to convert. + + - returns: The corresponding `UsergridDeviceProperties` or nil. + */ + public static func fromString(stringValue: String) -> UsergridDeviceProperties? { + switch stringValue.lowercaseString { + case DEVICE_MODEL: return .Model + case DEVICE_PLATFORM: return .Platform + case DEVICE_OSVERSION: return .OSVersion + default: return nil + } + } + + /// Returns the string value. + public var stringValue: String { + switch self { + case .Model: return DEVICE_MODEL + case .Platform: return DEVICE_PLATFORM + case .OSVersion: return DEVICE_OSVERSION + } + } +} + +/** +`UsergridUser` specific properties keys. +*/ +@objc public enum UsergridUserProperties: Int { + + // MARK: - Values - + + /// Corresponds to the property 'name' + case Name + /// Corresponds to the property 'username' + case Username + /// Corresponds to the property 'password' + case Password + /// Corresponds to the property 'email' + case Email + /// Corresponds to the property 'age' + case Age + /// Corresponds to the property 'activated' + case Activated + /// Corresponds to the property 'disabled' + case Disabled + /// Corresponds to the property 'picture' + case Picture + + // MARK: - Methods - + + /** + Gets the corresponding `UsergridUserProperties` from a string if it's valid. + + - parameter stringValue: The string value to convert. + + - returns: The corresponding `UsergridUserProperties` or nil. + */ + public static func fromString(stringValue: String) -> UsergridUserProperties? { + switch stringValue.lowercaseString { + case ENTITY_NAME: return .Name + case USER_USERNAME: return .Username + case USER_PASSWORD: return .Password + case USER_EMAIL: return .Email + case USER_AGE: return .Age + case USER_ACTIVATED: return .Activated + case USER_DISABLED: return .Disabled + case USER_PICTURE: return .Picture + default: return nil + } + } + + /// Returns the string value. + public var stringValue: String { + switch self { + case .Name: return ENTITY_NAME + case .Username: return USER_USERNAME + case .Password: return USER_PASSWORD + case .Email: return USER_EMAIL + case .Age: return USER_AGE + case .Activated: return USER_ACTIVATED + case .Disabled: return USER_DISABLED + case .Picture: return USER_PICTURE + } + } +} + +/** +`UsergridQuery` specific operators. +*/ +@objc public enum UsergridQueryOperator: Int { + + // MARK: - Values - + + /// '=' + case Equal + /// '>' + case GreaterThan + /// '>=' + case GreaterThanEqualTo + /// '<' + case LessThan + /// '<=' + case LessThanEqualTo + + // MARK: - Methods - + + /** + Gets the corresponding `UsergridQueryOperator` from a string if it's valid. + + - parameter stringValue: The string value to convert. + + - returns: The corresponding `UsergridQueryOperator` or nil. + */ + public static func fromString(stringValue: String) -> UsergridQueryOperator? { + switch stringValue.lowercaseString { + case UsergridQuery.EQUAL: return .Equal + case UsergridQuery.GREATER_THAN: return .GreaterThan + case UsergridQuery.GREATER_THAN_EQUAL_TO: return .GreaterThanEqualTo + case UsergridQuery.LESS_THAN: return .LessThan + case UsergridQuery.LESS_THAN_EQUAL_TO: return .LessThanEqualTo + default: return nil + } + } + + /// Returns the string value. + public var stringValue: String { + switch self { + case .Equal: return UsergridQuery.EQUAL + case .GreaterThan: return UsergridQuery.GREATER_THAN + case .GreaterThanEqualTo: return UsergridQuery.GREATER_THAN_EQUAL_TO + case .LessThan: return UsergridQuery.LESS_THAN + case .LessThanEqualTo: return UsergridQuery.LESS_THAN_EQUAL_TO + } + } +} + +/** +`UsergridQuery` specific sort orders. +*/ +@objc public enum UsergridQuerySortOrder: Int { + + // MARK: - Values - + + /// Sort order is ascending. + case Asc + /// Sort order is descending. + case Desc + + // MARK: - Methods - + + /** + Gets the corresponding `UsergridQuerySortOrder` from a string if it's valid. + + - parameter stringValue: The string value to convert. + + - returns: The corresponding `UsergridQuerySortOrder` or nil. + */ + public static func fromString(stringValue: String) -> UsergridQuerySortOrder? { + switch stringValue.lowercaseString { + case UsergridQuery.ASC: return .Asc + case UsergridQuery.DESC: return .Desc + default: return nil + } + } + + /// Returns the string value. + public var stringValue: String { + switch self { + case .Asc: return UsergridQuery.ASC + case .Desc: return UsergridQuery.DESC + } + } +} + +/** +`UsergridAsset` image specific content types. +*/ +@objc public enum UsergridImageContentType : Int { + + // MARK: - Values - + + /// Content type: 'image/png' + case Png + /// Content type: 'image/jpeg' + case Jpeg + + // MARK: - Methods - + + /// Returns the string value. + public var stringValue: String { + switch self { + case .Png: return ASSET_IMAGE_PNG + case .Jpeg: return ASSET_IMAGE_JPEG + } + } +} + +/** + An enumeration that is used when getting connections to entity objects. Used to determine which the direction of the connection is wanted. + */ +@objc public enum UsergridDirection : Int { + + // MARK: - Values - + + /// To get the entities that have created a connection to an entity. aka `connecting` + case In + + /// To get the entities an entity has connected to. aka `connections` + case Out + + // MARK: - Methods - + + /// Returns the connection value. + public var connectionValue: String { + switch self { + case .In: return CONNECTION_TYPE_IN + case .Out: return CONNECTION_TYPE_OUT + } + } +} + +/** + An enumeration for defining the HTTP methods used by Usergrid. + */ +@objc public enum UsergridHttpMethod : Int { + + /// GET + case Get + + /// PUT + case Put + + /// POST + case Post + + /// DELETE + case Delete + + /// Returns the string value. + public var stringValue: String { + switch self { + case .Get: return "GET" + case .Put: return "PUT" + case .Post: return "POST" + case .Delete: return "DELETE" + } + } +} + +let ENTITY_TYPE = "type" +let ENTITY_UUID = "uuid" +let ENTITY_NAME = "name" +let ENTITY_CREATED = "created" +let ENTITY_MODIFIED = "modified" +let ENTITY_LOCATION = "location" +let ENTITY_LATITUDE = "latitude" +let ENTITY_LONGITUDE = "longitude" + +let USER_USERNAME = "username" +let USER_PASSWORD = "password" +let USER_EMAIL = "email" +let USER_AGE = "age" +let USER_ACTIVATED = "activated" +let USER_DISABLED = "disabled" +let USER_PICTURE = "picture" + +let DEVICE_MODEL = "deviceModel" +let DEVICE_PLATFORM = "devicePlatform" +let DEVICE_OSVERSION = "devicePlatform" + +let ASSET_IMAGE_PNG = "image/png" +let ASSET_IMAGE_JPEG = "image/jpeg" + +let CONNECTION_TYPE_IN = "connecting" +let CONNECTION_TYPE_OUT = "connections"
http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridExtensions.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridExtensions.swift b/sdks/swift/Source/UsergridExtensions.swift new file mode 100644 index 0000000..050145c --- /dev/null +++ b/sdks/swift/Source/UsergridExtensions.swift @@ -0,0 +1,42 @@ +// +// UsergridExtensions.swift +// UsergridSDK +// +// Created by Robert Walsh on 10/6/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +internal extension NSDate { + convenience init(utcTimeStamp: String) { + self.init(timeIntervalSince1970: (utcTimeStamp as NSString).doubleValue / 1000 ) + } + func utcTimeStamp() -> Int { + return Int(self.timeIntervalSince1970 * 1000) + } +} + +internal extension String { + func isUuid() -> Bool { + return (NSUUID(UUIDString: self) != nil) ? true : false + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridFileMetaData.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridFileMetaData.swift b/sdks/swift/Source/UsergridFileMetaData.swift new file mode 100644 index 0000000..c3e7f52 --- /dev/null +++ b/sdks/swift/Source/UsergridFileMetaData.swift @@ -0,0 +1,114 @@ +// +// UsergridFileMetaData.swift +// UsergridSDK +// +// Created by Robert Walsh on 10/6/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +/** +`UsergridFileMetaData` is a helper class for dealing with reading `UsergridEntity` file meta data. +*/ +public class UsergridFileMetaData : NSObject,NSCoding { + + internal static let FILE_METADATA = "file-metadata" + + // MARK: - Instance Properties - + + /// The eTag. + public let eTag: String? + + /// The check sum. + public let checkSum: String? + + /// The content type associated with the file data. + public let contentType: String? + + /// The content length of the file data. + public let contentLength: Int + + /// The last modified time stamp. + public let lastModifiedTimeStamp: Int + + /// The `NSDate` object corresponding to the last modified time stamp. + public let lastModifiedDate: NSDate? + + // MARK: - Initialization - + + /** + Designated initializer for `UsergridFileMetaData` objects. + + - parameter fileMetaDataJSON: The file meta data JSON dictionary. + + - returns: A new instance of `UsergridFileMetaData`. + */ + public init(fileMetaDataJSON:[String:AnyObject]) { + self.eTag = fileMetaDataJSON["etag"] as? String + self.checkSum = fileMetaDataJSON["checksum"] as? String + self.contentType = fileMetaDataJSON["content-type"] as? String + self.contentLength = fileMetaDataJSON["content-length"] as? Int ?? 0 + self.lastModifiedTimeStamp = fileMetaDataJSON["last-modified"] as? Int ?? 0 + + if self.lastModifiedTimeStamp > 0 { + self.lastModifiedDate = NSDate(utcTimeStamp: self.lastModifiedTimeStamp.description) + } else { + self.lastModifiedDate = nil + } + } + + // MARK: - NSCoding - + + /** + NSCoding protocol initializer. + + - parameter aDecoder: The decoder. + + - returns: A decoded `UsergridUser` object. + */ + required public init?(coder aDecoder: NSCoder) { + self.eTag = aDecoder.decodeObjectForKey("etag") as? String + self.checkSum = aDecoder.decodeObjectForKey("checksum") as? String + self.contentType = aDecoder.decodeObjectForKey("content-type") as? String + self.contentLength = aDecoder.decodeIntegerForKey("content-length") ?? 0 + self.lastModifiedTimeStamp = aDecoder.decodeIntegerForKey("last-modified") ?? 0 + + if self.lastModifiedTimeStamp > 0 { + self.lastModifiedDate = NSDate(utcTimeStamp: self.lastModifiedTimeStamp.description) + } else { + self.lastModifiedDate = nil + } + } + + /** + NSCoding protocol encoder. + + - parameter aCoder: The encoder. + */ + public func encodeWithCoder(aCoder: NSCoder) { + aCoder.encodeObject(self.eTag, forKey: "etag") + aCoder.encodeObject(self.checkSum, forKey: "checksum") + aCoder.encodeObject(self.contentType, forKey: "content-type") + aCoder.encodeInteger(self.contentLength, forKey: "content-length") + aCoder.encodeInteger(self.lastModifiedTimeStamp, forKey: "last-modified") + } +} http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridKeychainHelpers.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridKeychainHelpers.swift b/sdks/swift/Source/UsergridKeychainHelpers.swift new file mode 100644 index 0000000..2d7dee6 --- /dev/null +++ b/sdks/swift/Source/UsergridKeychainHelpers.swift @@ -0,0 +1,148 @@ +// +// UsergridKeychainHelpers.swift +// UsergridSDK +// +// Created by Robert Walsh on 12/21/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +#if os(iOS) || os(tvOS) || os(watchOS) +import UIKit +#endif + +private let USERGRID_KEYCHAIN_NAME = "Usergrid" +private let USERGRID_DEVICE_KEYCHAIN_SERVICE = "DeviceUUID" +private let USERGRID_CURRENT_USER_KEYCHAIN_SERVICE = "CurrentUser" + +private func usergridGenericKeychainItem() -> [String:AnyObject] { + var keychainItem: [String:AnyObject] = [:] + keychainItem[kSecClass as String] = kSecClassGenericPassword as String + keychainItem[kSecAttrAccessible as String] = kSecAttrAccessibleAlways as String + keychainItem[kSecAttrAccount as String] = USERGRID_KEYCHAIN_NAME + return keychainItem +} + +internal extension UsergridDevice { + + static func deviceKeychainItem() -> [String:AnyObject] { + var keychainItem = usergridGenericKeychainItem() + keychainItem[kSecAttrService as String] = USERGRID_DEVICE_KEYCHAIN_SERVICE + return keychainItem + } + + static func createNewUsergridKeychainUUID() -> String { + + #if os(watchOS) || os(OSX) + let usergridUUID = NSUUID().UUIDString + #elseif os(iOS) || os(tvOS) + let usergridUUID = UIDevice.currentDevice().identifierForVendor?.UUIDString ?? NSUUID().UUIDString + #endif + + var keychainItem = UsergridDevice.deviceKeychainItem() + keychainItem[kSecValueData as String] = (usergridUUID as NSString).dataUsingEncoding(NSUTF8StringEncoding) + SecItemAdd(keychainItem, nil) + return usergridUUID + } + + static func usergridDeviceUUID() -> String { + var queryAttributes = UsergridDevice.deviceKeychainItem() + queryAttributes[kSecReturnData as String] = kCFBooleanTrue as Bool + queryAttributes[kSecReturnAttributes as String] = kCFBooleanTrue as Bool + var result: AnyObject? + let status = withUnsafeMutablePointer(&result) { SecItemCopyMatching(queryAttributes, UnsafeMutablePointer($0)) } + if status == errSecSuccess { + if let resultDictionary = result as? NSDictionary { + if let resultData = resultDictionary[kSecValueData as String] as? NSData { + if let keychainUUID = String(data: resultData, encoding: NSUTF8StringEncoding) { + return keychainUUID + } + } + } + } + return UsergridDevice.createNewUsergridKeychainUUID() + } +} + +internal extension UsergridUser { + + static func userKeychainItem(client:UsergridClient) -> [String:AnyObject] { + var keychainItem = usergridGenericKeychainItem() + keychainItem[kSecAttrService as String] = USERGRID_CURRENT_USER_KEYCHAIN_SERVICE + "." + client.appId + "." + client.orgId + return keychainItem + } + + static func getCurrentUserFromKeychain(client:UsergridClient) -> UsergridUser? { + var queryAttributes = UsergridUser.userKeychainItem(client) + queryAttributes[kSecReturnData as String] = kCFBooleanTrue as Bool + queryAttributes[kSecReturnAttributes as String] = kCFBooleanTrue as Bool + + var result: AnyObject? + let status = withUnsafeMutablePointer(&result) { SecItemCopyMatching(queryAttributes, UnsafeMutablePointer($0)) } + if status == errSecSuccess { + if let resultDictionary = result as? NSDictionary { + if let resultData = resultDictionary[kSecValueData as String] as? NSData { + if let currentUser = NSKeyedUnarchiver.unarchiveObjectWithData(resultData) as? UsergridUser { + return currentUser + } + } + } + } + return nil + } + + static func saveCurrentUserKeychainItem(client:UsergridClient, currentUser:UsergridUser) { + var queryAttributes = UsergridUser.userKeychainItem(client) + queryAttributes[kSecReturnData as String] = kCFBooleanTrue as Bool + queryAttributes[kSecReturnAttributes as String] = kCFBooleanTrue as Bool + + if SecItemCopyMatching(queryAttributes,nil) == errSecSuccess // Do we need to update keychain item or add a new one. + { + let attributesToUpdate = [kSecValueData as String:NSKeyedArchiver.archivedDataWithRootObject(currentUser)] + let updateStatus = SecItemUpdate(UsergridUser.userKeychainItem(client), attributesToUpdate) + if updateStatus != errSecSuccess { + print("Error updating current user data to keychain!") + } + } + else + { + var keychainItem = UsergridUser.userKeychainItem(client) + keychainItem[kSecValueData as String] = NSKeyedArchiver.archivedDataWithRootObject(currentUser) + let status = SecItemAdd(keychainItem, nil) + if status != errSecSuccess { + print("Error adding current user data to keychain!") + } + } + } + + static func deleteCurrentUserKeychainItem(client:UsergridClient) { + var queryAttributes = UsergridUser.userKeychainItem(client) + queryAttributes[kSecReturnData as String] = kCFBooleanFalse as Bool + queryAttributes[kSecReturnAttributes as String] = kCFBooleanFalse as Bool + if SecItemCopyMatching(queryAttributes,nil) == errSecSuccess { + let deleteStatus = SecItemDelete(queryAttributes) + if deleteStatus != errSecSuccess { + print("Error deleting current user data to keychain!") + } + } + } +} http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridQuery.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridQuery.swift b/sdks/swift/Source/UsergridQuery.swift new file mode 100644 index 0000000..9dfd10a --- /dev/null +++ b/sdks/swift/Source/UsergridQuery.swift @@ -0,0 +1,530 @@ +// +// UsergridQuery.swift +// UsergridSDK +// +// Created by Robert Walsh on 7/22/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +/** + `UsergridQuery` is builder class used to construct filtered requests to Usergrid. + + `UsergridQuery` objects are then passed to `UsergridClient` or `Usergrid` methods which support `UsergridQuery` as a parameter are .GET(), .PUT(), and .DELETE(). + */ +public class UsergridQuery : NSObject,NSCopying { + + // MARK: - Initialization - + + /** + Desingated initializer for `UsergridQuery` objects. + + - parameter collectionName: The collection name or `type` of entities you want to query. + + - returns: A new instance of `UsergridQuery`. + */ + public init(_ collectionName: String? = nil) { + self.collectionName = collectionName + } + + // MARK: - NSCopying - + + /** + See the NSCopying protocol. + + - parameter zone: Ignored + + - returns: Returns a new instance thatâs a copy of the receiver. + */ + public func copyWithZone(zone: NSZone) -> AnyObject { + let queryCopy = UsergridQuery(self.collectionName) + queryCopy.requirementStrings = NSArray(array:self.requirementStrings, copyItems: true) as! [String] + queryCopy.urlTerms = NSArray(array:self.urlTerms, copyItems: true) as! [String] + for (key,value) in self.orderClauses { + queryCopy.orderClauses[key] = value + } + queryCopy.limit = self.limit + queryCopy.cursor = self.cursor + return queryCopy + } + + // MARK: - Building - + + /** + Constructs the string that should be appeneded to the end of the URL as a query. + + - parameter autoURLEncode: Automatically encode the constructed string. + + - returns: The constructed URL query sting. + */ + public func build(autoURLEncode: Bool = true) -> String { + return self.constructURLAppend(autoURLEncode) + } + + // MARK: - Builder Methods - + + /** + Contains. Query: where term contains 'val%'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func containsString(term: String, value: String) -> Self { return self.containsWord(term, value: value) } + + /** + Contains. Query: where term contains 'val%'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func containsWord(term: String, value: String) -> Self { return self.addRequirement(term + UsergridQuery.SPACE + UsergridQuery.CONTAINS + UsergridQuery.SPACE + ((value.isUuid()) ? UsergridQuery.EMPTY_STRING : UsergridQuery.APOSTROPHE) + value + ((value.isUuid()) ? UsergridQuery.EMPTY_STRING : UsergridQuery.APOSTROPHE)) } + + /** + Sort ascending. Query:. order by term asc. + + - parameter term: The term. + + - returns: `Self` + */ + public func ascending(term: String) -> Self { return self.asc(term) } + + /** + Sort ascending. Query:. order by term asc. + + - parameter term: The term. + + - returns: `Self` + */ + public func asc(term: String) -> Self { return self.sort(term, sortOrder: UsergridQuerySortOrder.Asc) } + + /** + Sort descending. Query: order by term desc + + - parameter term: The term. + + - returns: `Self` + */ + public func descending(term: String) -> Self { return self.desc(term) } + + /** + Sort descending. Query: order by term desc + + - parameter term: The term. + + - returns: `Self` + */ + public func desc(term: String) -> Self { return self.sort(term, sortOrder: UsergridQuerySortOrder.Desc) } + + /** + Filter (or Equal-to). Query: where term = 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func filter(term: String, value: AnyObject) -> Self { return self.eq(term, value: value) } + + /** + Equal-to. Query: where term = 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func equals(term: String, value: AnyObject) -> Self { return self.eq(term, value: value) } + + /** + Equal-to. Query: where term = 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func eq(term: String, value: AnyObject) -> Self { return self.addOperationRequirement(term, operation:.Equal, value: value) } + + /** + Greater-than. Query: where term > 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func greaterThan(term: String, value: AnyObject) -> Self { return self.gt(term, value: value) } + + /** + Greater-than. Query: where term > 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func gt(term: String, value: AnyObject) -> Self { return self.addOperationRequirement(term, operation:.GreaterThan, value: value) } + + /** + Greater-than-or-equal-to. Query: where term >= 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func greaterThanOrEqual(term: String, value: AnyObject) -> Self { return self.gte(term, value: value) } + + /** + Greater-than-or-equal-to. Query: where term >= 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func gte(term: String, value: AnyObject) -> Self { return self.addOperationRequirement(term, operation:.GreaterThanEqualTo, value: value) } + + /** + Less-than. Query: where term < 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func lessThan(term: String, value: AnyObject) -> Self { return self.lt(term, value: value) } + + /** + Less-than. Query: where term < 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func lt(term: String, value: AnyObject) -> Self { return self.addOperationRequirement(term, operation:.LessThan, value: value) } + + /** + Less-than-or-equal-to. Query: where term <= 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func lessThanOrEqual(term: String, value: AnyObject) -> Self { return self.lte(term, value: value) } + + /** + Less-than-or-equal-to. Query: where term <= 'value'. + + - parameter term: The term. + - parameter value: The value. + + - returns: `Self` + */ + public func lte(term: String, value: AnyObject) -> Self { return self.addOperationRequirement(term, operation:.LessThanEqualTo, value: value) } + + /** + Contains. Query: location within val of lat, long. + + - parameter distance: The distance from the latitude and longitude. + - parameter latitude: The latitude. + - parameter longitude: The longitude. + + - returns: `Self` + */ + public func locationWithin(distance: Float, latitude: Float, longitude: Float) -> Self { + return self.addRequirement(UsergridQuery.LOCATION + UsergridQuery.SPACE + UsergridQuery.WITHIN + UsergridQuery.SPACE + distance.description + UsergridQuery.SPACE + UsergridQuery.OF + UsergridQuery.SPACE + latitude.description + UsergridQuery.COMMA + longitude.description ) + } + + /** + Joining operation to combine conditional queries. + + - returns: `Self` + */ + public func or() -> Self { + if !self.requirementStrings.first!.isEmpty { + self.requirementStrings.insert(UsergridQuery.OR, atIndex: 0) + self.requirementStrings.insert(UsergridQuery.EMPTY_STRING, atIndex: 0) + } + return self + } + + /** + Not operation for conditional queries. + + - returns: `Self` + */ + public func not() -> Self { + if !self.requirementStrings.first!.isEmpty { + self.requirementStrings.insert(UsergridQuery.NOT, atIndex: 0) + self.requirementStrings.insert(UsergridQuery.EMPTY_STRING, atIndex: 0) + } + return self + } + + /** + Sort. Query: order by term `sortOrder` + + - parameter term: The term. + - parameter sortOrder: The order. + + - returns: `Self` + */ + public func sort(term: String, sortOrder: UsergridQuerySortOrder) -> Self { + self.orderClauses[term] = sortOrder + return self + } + + /** + Sets the collection name. + + - parameter collectionName: The new collection name. + + - returns: `Self` + */ + public func collection(collectionName: String) -> Self { + self.collectionName = collectionName + return self + } + + /** + Sets the limit on the query. Default limit is 10. + + - parameter limit: The limit. + + - returns: `Self` + */ + public func limit(limit: Int) -> Self { + self.limit = limit + return self + } + + /** + Adds a preconstructed query string as a requirement onto the query. + + - parameter value: The query string. + + - returns: `Self` + */ + public func ql(value: String) -> Self { + return self.addRequirement(value) + } + + /** + Sets the cursor of the query used internally by Usergrid's APIs. + + - parameter value: The cursor. + + - returns: `Self` + */ + public func cursor(value: String?) -> Self { + self.cursor = value + return self + } + + /** + Adds a URL term that will be added next to the query string when constructing the URL append. + + - parameter term: The term. + - parameter equalsValue: The value. + + - returns: `Self` + */ + public func urlTerm(term: String, equalsValue: String) -> Self { + if (term as NSString).isEqualToString(UsergridQuery.QL) { + self.ql(equalsValue) + } else { + self.urlTerms.append(term + UsergridQueryOperator.Equal.stringValue + equalsValue) + } + return self + } + + /** + Adds a string requirement to the query. + + - parameter term: The term. + - parameter operation: The operation. + - parameter stringValue: The string value. + + - returns: `Self` + */ + public func addOperationRequirement(term: String, operation: UsergridQueryOperator, stringValue: String) -> Self { + return self.addOperationRequirement(term,operation:operation,value:stringValue) + } + + /** + Adds a integer requirement to the query. + + - parameter term: The term. + - parameter operation: The operation. + - parameter intValue: The integer value. + + - returns: `Self` + */ + public func addOperationRequirement(term: String, operation: UsergridQueryOperator, intValue: Int) -> Self { + return self.addOperationRequirement(term,operation:operation,value:intValue) + } + + private func addRequirement(requirement: String) -> Self { + var requirementString: String = self.requirementStrings.removeAtIndex(0) + if !requirementString.isEmpty { + requirementString += UsergridQuery.SPACE + UsergridQuery.AND + UsergridQuery.SPACE + } + requirementString += requirement + self.requirementStrings.insert(requirementString, atIndex: 0) + return self + } + + private func addOperationRequirement(term: String, operation: UsergridQueryOperator, value: AnyObject) -> Self { + if value is String { + return self.addRequirement(term + UsergridQuery.SPACE + operation.stringValue + UsergridQuery.SPACE + ((value.description.isUuid()) ? UsergridQuery.EMPTY_STRING : UsergridQuery.APOSTROPHE) + value.description + ((value.description.isUuid()) ? UsergridQuery.EMPTY_STRING : UsergridQuery.APOSTROPHE) ) + } else { + return self.addRequirement(term + UsergridQuery.SPACE + operation.stringValue + UsergridQuery.SPACE + value.description) + } + } + + private func constructOrderByString() -> String { + var orderByString = UsergridQuery.EMPTY_STRING + if !self.orderClauses.isEmpty { + var combinedClausesArray: [String] = [] + for (key,value) in self.orderClauses { + combinedClausesArray.append(key + UsergridQuery.SPACE + value.stringValue) + } + for index in 0..<combinedClausesArray.count { + if index > 0 { + orderByString += UsergridQuery.COMMA + } + orderByString += combinedClausesArray[index] + } + if !orderByString.isEmpty { + orderByString = UsergridQuery.SPACE + UsergridQuery.ORDER_BY + UsergridQuery.SPACE + orderByString + } + } + return orderByString + } + + private func constructURLTermsString() -> String { + return (self.urlTerms as NSArray).componentsJoinedByString(UsergridQuery.AMPERSAND) + } + + private func constructRequirementString() -> String { + var requirementsString = UsergridQuery.EMPTY_STRING + var requirementStrings = self.requirementStrings + + // If the first requirement is empty lets remove it. + if let firstRequirement = requirementStrings.first where firstRequirement.isEmpty { + requirementStrings.removeFirst() + } + + // If the first requirement now is a conditional separator then we should remove it so its not placed at the end of the constructed string. + if let firstRequirement = requirementStrings.first where firstRequirement == UsergridQuery.OR || firstRequirement == UsergridQuery.NOT { + requirementStrings.removeFirst() + } + + requirementsString = (requirementStrings.reverse() as NSArray).componentsJoinedByString(UsergridQuery.SPACE) + return requirementsString + } + + private func constructURLAppend(autoURLEncode: Bool = true) -> String { + var urlAppend = UsergridQuery.EMPTY_STRING + if self.limit != UsergridQuery.LIMIT_DEFAULT { + urlAppend += "\(UsergridQuery.LIMIT)=\(self.limit.description)" + } + let urlTermsString = self.constructURLTermsString() + if !urlTermsString.isEmpty { + if !urlAppend.isEmpty { + urlAppend += UsergridQuery.AMPERSAND + } + urlAppend += urlTermsString + } + if let cursorString = self.cursor where !cursorString.isEmpty { + if !urlAppend.isEmpty { + urlAppend += UsergridQuery.AMPERSAND + } + urlAppend += "\(UsergridQuery.CURSOR)=\(cursorString)" + } + + var requirementsString = self.constructRequirementString() + let orderByString = self.constructOrderByString() + if !orderByString.isEmpty { + requirementsString += orderByString + } + if !requirementsString.isEmpty { + if autoURLEncode { + if let encodedRequirementsString = requirementsString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) { + requirementsString = encodedRequirementsString + } + } + if !urlAppend.isEmpty { + urlAppend += UsergridQuery.AMPERSAND + } + urlAppend += "\(UsergridQuery.QL)=\(requirementsString)" + } + + if !urlAppend.isEmpty { + urlAppend = "\(UsergridQuery.QUESTION_MARK)\(urlAppend)" + } + return urlAppend + } + + private(set) var collectionName: String? = nil + private(set) var cursor: String? = nil + private(set) var limit: Int = UsergridQuery.LIMIT_DEFAULT + + private(set) var requirementStrings: [String] = [UsergridQuery.EMPTY_STRING] + private(set) var orderClauses: [String:UsergridQuerySortOrder] = [:] + private(set) var urlTerms: [String] = [] + + private static let LIMIT_DEFAULT = 10 + private static let AMPERSAND = "&" + private static let AND = "and" + private static let APOSTROPHE = "'" + private static let COMMA = "," + private static let CONTAINS = "contains" + private static let CURSOR = "cursor" + private static let EMPTY_STRING = "" + private static let IN = "in" + private static let LIMIT = "limit" + private static let LOCATION = "location"; + private static let NOT = "not" + private static let OF = "of" + private static let OR = "or" + private static let ORDER_BY = "order by" + private static let QL = "ql" + private static let QUESTION_MARK = "?" + private static let SPACE = " " + private static let WITHIN = "within" + + internal static let ASC = "asc" + internal static let DESC = "desc" + internal static let EQUAL = "=" + internal static let GREATER_THAN = ">" + internal static let GREATER_THAN_EQUAL_TO = ">=" + internal static let LESS_THAN = "<" + internal static let LESS_THAN_EQUAL_TO = "<=" +} http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridRequest.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridRequest.swift b/sdks/swift/Source/UsergridRequest.swift new file mode 100644 index 0000000..dd1f561 --- /dev/null +++ b/sdks/swift/Source/UsergridRequest.swift @@ -0,0 +1,245 @@ +// +// UsergridRequest.swift +// UsergridSDK +// +// Created by Robert Walsh on 1/12/16. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +/** + The UsergridRequest class incapsulates the properties that all requests made by the SDK have in common. + + This class is also functions to create `NSURLRequest` objects based on the properties of the class. +*/ +public class UsergridRequest : NSObject { + + // MARK: - Instance Properties - + + /// The HTTP method. + public let method: UsergridHttpMethod + + /// The base URL. + public let baseUrl: String + + /// The paths to append to the base URL. + public let paths: [String]? + + /// The query to append to the URL. + public let query: UsergridQuery? + + /// The auth that will be used. + public let auth: UsergridAuth? + + /// The headers to add to the request. + public let headers: [String:String]? + + /// The JSON body that will be set on the request. Can be either a valid JSON object or NSData. + public let jsonBody: AnyObject? + + /// The query params that will be set on the request. + public let queryParams: [String:String]? + + // MARK: - Initialization - + + /** + The designated initializer for `UsergridRequest` objects. + + - parameter method: The HTTP method. + - parameter baseUrl: The base URL. + - parameter paths: The optional paths to append to the base URL. + - parameter query: The optional query to append to the URL. + - parameter auth: The optional `UsergridAuth` that will be used in the Authorization header. + - parameter headers: The optional headers. + - parameter jsonBody: The optional JSON body. Can be either a valid JSON object or NSData. + - parameter queryParams: The optional query params to be appended to the request url. + + - returns: A new instance of `UsergridRequest`. + */ + public init(method:UsergridHttpMethod, + baseUrl:String, + paths:[String]? = nil, + query:UsergridQuery? = nil, + auth:UsergridAuth? = nil, + headers:[String:String]? = nil, + jsonBody:AnyObject? = nil, + queryParams:[String:String]? = nil) { + self.method = method + self.baseUrl = baseUrl + self.paths = paths + self.auth = auth + self.headers = headers + self.query = query + self.queryParams = queryParams + if let body = jsonBody where (body is NSData || NSJSONSerialization.isValidJSONObject(body)) { + self.jsonBody = body + } else { + self.jsonBody = nil + } + } + + // MARK: - Instance Methods - + + /** + Constructs a `NSURLRequest` object with this objects instance properties. + + - returns: An initialized and configured `NSURLRequest` object. + */ + public func buildNSURLRequest() -> NSURLRequest { + let request = NSMutableURLRequest(URL: self.buildURL()) + request.HTTPMethod = self.method.stringValue + self.applyHeaders(request) + self.applyBody(request) + self.applyAuth(request) + return request + } + + private func buildURL() -> NSURL { + var constructedURLString = self.baseUrl + if let appendingPaths = self.paths { + for pathToAppend in appendingPaths { + if let encodedPath = pathToAppend.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLPathAllowedCharacterSet()) { + constructedURLString = "\(constructedURLString)\(UsergridRequest.FORWARD_SLASH)\(encodedPath)" + } + } + } + if let queryToAppend = self.query { + let appendFromQuery = queryToAppend.build() + if !appendFromQuery.isEmpty { + constructedURLString = "\(constructedURLString)\(UsergridRequest.FORWARD_SLASH)\(appendFromQuery)" + } + } + if let queryParams = self.queryParams { + if let components = NSURLComponents(string: constructedURLString) { + components.queryItems = components.queryItems ?? [] + for (key, value) in queryParams { + let q: NSURLQueryItem = NSURLQueryItem(name: key, value: value) + components.queryItems!.append(q) + } + constructedURLString = components.string! + } + } + return NSURL(string:constructedURLString)! + } + + private func applyHeaders(request:NSMutableURLRequest) { + if let httpHeaders = self.headers { + for (key,value) in httpHeaders { + request.setValue(value, forHTTPHeaderField: key) + } + } + } + + private func applyBody(request:NSMutableURLRequest) { + if let jsonBody = self.jsonBody, httpBody = UsergridRequest.jsonBodyToData(jsonBody) { + request.HTTPBody = httpBody + request.setValue(String(format: "%lu", httpBody.length), forHTTPHeaderField: UsergridRequest.CONTENT_LENGTH) + } + } + + private func applyAuth(request:NSMutableURLRequest) { + if let usergridAuth = self.auth { + if usergridAuth.isValid, let accessToken = usergridAuth.accessToken { + request.setValue("\(UsergridRequest.BEARER) \(accessToken)", forHTTPHeaderField: UsergridRequest.AUTHORIZATION) + } + } + } + + private static func jsonBodyToData(jsonBody:AnyObject) -> NSData? { + if let jsonBodyAsNSData = jsonBody as? NSData { + return jsonBodyAsNSData + } else { + var jsonBodyAsNSData: NSData? = nil + do { jsonBodyAsNSData = try NSJSONSerialization.dataWithJSONObject(jsonBody, options: NSJSONWritingOptions(rawValue: 0)) } + catch { print(error) } + return jsonBodyAsNSData + } + } + + private static let AUTHORIZATION = "Authorization" + private static let ACCESS_TOKEN = "access_token" + private static let APPLICATION_JSON = "application/json" + private static let BEARER = "Bearer" + private static let CONTENT_LENGTH = "Content-Length" + private static let CONTENT_TYPE = "Content-Type" + private static let FORWARD_SLASH = "/" + + static let JSON_CONTENT_TYPE_HEADER = [UsergridRequest.CONTENT_TYPE:UsergridRequest.APPLICATION_JSON] +} + +/** + The `UsergridRequest` sub class which is used for uploading assets. + */ +public class UsergridAssetUploadRequest: UsergridRequest { + + // MARK: - Instance Properties - + + /// The asset to use for uploading. + public let asset: UsergridAsset + + /// A constructed multipart http body for requests to upload. + public var multiPartHTTPBody: NSData { + let httpBodyString = UsergridAssetUploadRequest.MULTIPART_START + + "\(UsergridAssetUploadRequest.CONTENT_DISPOSITION):\(UsergridAssetUploadRequest.FORM_DATA); name=file; filename=\(self.asset.filename)\r\n" + + "\(UsergridRequest.CONTENT_TYPE): \(self.asset.contentType)\r\n\r\n" as NSString + + let httpBody = NSMutableData() + httpBody.appendData(httpBodyString.dataUsingEncoding(NSUTF8StringEncoding)!) + httpBody.appendData(self.asset.data) + httpBody.appendData(UsergridAssetUploadRequest.MULTIPART_END.dataUsingEncoding(NSUTF8StringEncoding)!) + + return httpBody + } + + // MARK: - Initialization - + + /** + The designated initializer for `UsergridAssetUploadRequest` objects. + + - parameter baseUrl: The base URL. + - parameter paths: The optional paths to append to the base URL. + - parameter auth: The optional `UsergridAuth` that will be used in the Authorization header. + - parameter asset: The asset to upload. + + - returns: A new instance of `UsergridRequest`. + */ + public init(baseUrl:String, + paths:[String]? = nil, + auth:UsergridAuth? = nil, + asset:UsergridAsset) { + self.asset = asset + super.init(method: .Put, baseUrl: baseUrl, paths: paths, auth: auth) + } + + private override func applyHeaders(request: NSMutableURLRequest) { + super.applyHeaders(request) + request.setValue(UsergridAssetUploadRequest.ASSET_UPLOAD_CONTENT_HEADER, forHTTPHeaderField: UsergridRequest.CONTENT_TYPE) + request.setValue(String(format: "%lu", self.multiPartHTTPBody.length), forHTTPHeaderField: UsergridRequest.CONTENT_LENGTH) + } + + private static let ASSET_UPLOAD_BOUNDARY = "usergrid-asset-upload-boundary" + private static let ASSET_UPLOAD_CONTENT_HEADER = "multipart/form-data; boundary=\(UsergridAssetUploadRequest.ASSET_UPLOAD_BOUNDARY)" + private static let CONTENT_DISPOSITION = "Content-Disposition" + private static let MULTIPART_START = "--\(UsergridAssetUploadRequest.ASSET_UPLOAD_BOUNDARY)\r\n" + private static let MULTIPART_END = "\r\n--\(UsergridAssetUploadRequest.ASSET_UPLOAD_BOUNDARY)--\r\n" as NSString + private static let FORM_DATA = "form-data" +} http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridRequestManager.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridRequestManager.swift b/sdks/swift/Source/UsergridRequestManager.swift new file mode 100644 index 0000000..ccd41e5 --- /dev/null +++ b/sdks/swift/Source/UsergridRequestManager.swift @@ -0,0 +1,156 @@ +// +// UsergridRequestManager.swift +// UsergridSDK +// +// Created by Robert Walsh on 9/22/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +final class UsergridRequestManager { + + unowned let client: UsergridClient + + let session: NSURLSession + + var sessionDelegate : UsergridSessionDelegate { + return session.delegate as! UsergridSessionDelegate + } + + init(client:UsergridClient) { + self.client = client + + let config = NSURLSessionConfiguration.defaultSessionConfiguration() + + #if os(tvOS) + config.HTTPAdditionalHeaders = ["User-Agent": "usergrid-tvOS/v\(UsergridSDKVersion)"] + #elseif os(iOS) + config.HTTPAdditionalHeaders = ["User-Agent": "usergrid-ios/v\(UsergridSDKVersion)"] + #elseif os(watchOS) + config.HTTPAdditionalHeaders = ["User-Agent": "usergrid-watchOS/v\(UsergridSDKVersion)"] + #elseif os(OSX) + config.HTTPAdditionalHeaders = ["User-Agent": "usergrid-osx/v\(UsergridSDKVersion)"] + #endif + + self.session = NSURLSession(configuration: config, + delegate: UsergridSessionDelegate(), + delegateQueue: NSOperationQueue.mainQueue()) + } + + deinit { + session.invalidateAndCancel() + } + + func performRequest(request:UsergridRequest, completion:UsergridResponseCompletion?) { + session.dataTaskWithRequest(request.buildNSURLRequest()) { [weak self] (data, response, error) -> Void in + completion?(response: UsergridResponse(client:self?.client, data: data, response: response as? NSHTTPURLResponse, error: error)) + }.resume() + } +} + + +// MARK: - Authentication - +extension UsergridRequestManager { + + static func getTokenAndExpiryFromResponseJSON(jsonDict:[String:AnyObject]) -> (String?,NSDate?) { + var token: String? = nil + var expiry: NSDate? = nil + if let accessToken = jsonDict["access_token"] as? String { + token = accessToken + } + if let expiresIn = jsonDict["expires_in"] as? Int { + let expiresInAdjusted = expiresIn - 5000 + expiry = NSDate(timeIntervalSinceNow: Double(expiresInAdjusted)) + } + return (token,expiry) + } + + func performUserAuthRequest(userAuth:UsergridUserAuth, request:UsergridRequest, completion:UsergridUserAuthCompletionBlock?) { + session.dataTaskWithRequest(request.buildNSURLRequest()) { (data, response, error) -> Void in + let dataAsJSON = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) + if let jsonDict = dataAsJSON as? [String:AnyObject] { + let tokenAndExpiry = UsergridRequestManager.getTokenAndExpiryFromResponseJSON(jsonDict) + userAuth.accessToken = tokenAndExpiry.0 + userAuth.expiry = tokenAndExpiry.1 + + var user: UsergridUser? + if let userDict = jsonDict[UsergridUser.USER_ENTITY_TYPE] as? [String:AnyObject] { + if let createdUser = UsergridEntity.entity(jsonDict: userDict) as? UsergridUser { + createdUser.auth = userAuth + user = createdUser + } + } + if let createdUser = user { + completion?(auth: userAuth, user:createdUser, error: nil) + } else { + let error = UsergridResponseError(jsonDictionary: jsonDict) ?? UsergridResponseError(errorName: "Auth Failed.", errorDescription: "Error Description: \(error?.localizedDescription).") + completion?(auth: userAuth, user:nil, error:error) + } + } else { + let error = UsergridResponseError(errorName: "Auth Failed.", errorDescription: "Error Description: \(error?.localizedDescription).") + completion?(auth: userAuth, user:nil, error: error) + } + }.resume() + } + + func performAppAuthRequest(appAuth: UsergridAppAuth, request: UsergridRequest, completion: UsergridAppAuthCompletionBlock?) { + session.dataTaskWithRequest(request.buildNSURLRequest()) { (data, response, error) -> Void in + let dataAsJSON = try! NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) + if let jsonDict = dataAsJSON as? [String:AnyObject] { + let tokenAndExpiry = UsergridRequestManager.getTokenAndExpiryFromResponseJSON(jsonDict) + appAuth.accessToken = tokenAndExpiry.0 + appAuth.expiry = tokenAndExpiry.1 + completion?(auth: appAuth, error: nil) + } else { + let error = UsergridResponseError(errorName: "Auth Failed.", errorDescription: "Error Description: \(error?.localizedDescription).") + completion?(auth: nil, error: error) + } + }.resume() + } +} + +// MARK: - Asset Management - +extension UsergridRequestManager { + + func performAssetDownload(contentType:String, usergridRequest:UsergridRequest, progress: UsergridAssetRequestProgress? = nil, completion:UsergridAssetDownloadCompletion? = nil) { + let downloadTask = session.downloadTaskWithRequest(usergridRequest.buildNSURLRequest()) + let requestWrapper = UsergridAssetRequestWrapper(session: self.session, sessionTask: downloadTask, progress: progress) { (request) -> Void in + if let assetData = request.responseData where assetData.length > 0 { + let asset = UsergridAsset(data: assetData, contentType: contentType) + completion?(asset: asset, error:nil) + } else { + completion?(asset: nil, error: "Downloading asset failed. No data was recieved.") + } + } + self.sessionDelegate.addRequestDelegate(requestWrapper.sessionTask, requestWrapper:requestWrapper) + requestWrapper.sessionTask.resume() + } + + func performAssetUpload(usergridRequest:UsergridAssetUploadRequest, progress:UsergridAssetRequestProgress? = nil, completion: UsergridAssetUploadCompletion? = nil) { + let uploadTask = session.uploadTaskWithRequest(usergridRequest.buildNSURLRequest(), fromData: usergridRequest.multiPartHTTPBody) + let requestWrapper = UsergridAssetRequestWrapper(session: self.session, sessionTask: uploadTask, progress: progress) { [weak self] (request) -> Void in + completion?(response: UsergridResponse(client: self?.client, data: request.responseData, response: request.response as? NSHTTPURLResponse, error: request.error),asset:usergridRequest.asset,error:nil) + } + self.sessionDelegate.addRequestDelegate(requestWrapper.sessionTask, requestWrapper:requestWrapper) + requestWrapper.sessionTask.resume() + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridResponse.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridResponse.swift b/sdks/swift/Source/UsergridResponse.swift new file mode 100644 index 0000000..012c82f --- /dev/null +++ b/sdks/swift/Source/UsergridResponse.swift @@ -0,0 +1,203 @@ +// +// UsergridResponse.swift +// UsergridSDK +// +// Created by Robert Walsh on 9/2/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +/// The completion block used in for most `UsergridClient` requests. +public typealias UsergridResponseCompletion = (response: UsergridResponse) -> Void + +/** +`UsergridResponse` is the core class that handles both successful and unsuccessful HTTP responses from Usergrid. + +If a request is successful, any entities returned in the response will be automatically parsed into `UsergridEntity` objects and pushed to the `entities` property. + +If a request fails, the `errorName` and `errorDescription` will contain information about the problem encountered. +*/ +public class UsergridResponse: NSObject { + + // MARK: - Instance Properties - + + /// The client that was responsible for the request. + public weak var client: UsergridClient? + + /// The raw response JSON. + internal(set) public var responseJSON: [String:AnyObject]? + + /// The query used on the request. + internal(set) public var query: UsergridQuery? + + /// The cursor from the response. + internal(set) public var cursor: String? + + /// The entities created from the response JSON. + internal(set) public var entities: [UsergridEntity]? + + /// The response headers. + internal(set) public var headers: [String:String]? + + /// The response status code. + internal(set) public var statusCode: Int? + + /// The error object containing error information if one occurred. + internal(set) public var error: UsergridResponseError? + + /// Returns true if the HTTP status code from the response is less than 400. + public var ok : Bool { + var isOk = false + if let statusCode = self.statusCode { + isOk = (statusCode < 400) + } + return isOk + } + + /// The count of `entities`. + public var count: Int { return self.entities?.count ?? 0 } + + /// The first entity in `entities`. + public var first: UsergridEntity? { return self.entities?.first } + + /// The last entity in `entities`. + public var last: UsergridEntity? { return self.entities?.last } + + /// The first entity in `entities`. + public var entity: UsergridEntity? { return self.first } + + /// The `UsergridUser` entity. + public var user: UsergridUser? { return self.entities?.first as? UsergridUser } + + /// An array of `UsergridUser` entities. + public var users: [UsergridUser]? { return self.entities as? [UsergridUser] } + + /// Does the response have a cursor. + public var hasNextPage: Bool { return self.cursor != nil } + + /// The string value. + public var stringValue : String? { + if let responseJSON = self.responseJSON { + return NSString(data: try! NSJSONSerialization.dataWithJSONObject(responseJSON, options: .PrettyPrinted), encoding: NSASCIIStringEncoding) as? String + } else { + return error?.description + } + } + + /// The description. + public override var description : String { + return "Response Description: \(stringValue)." + } + + /// The debug description. + public override var debugDescription : String { + return "Properties of Entity: \(stringValue)." + } + + // MARK: - Initialization - + + /** + Designated initializer for `UsergridResponse` objects that contain errors. + + These types of responses are usually created because request conditions are not met. + + - parameter client: The client responsible for the request. + - parameter errorName: The error name. + - parameter errorDescription: The error description. + + - returns: A new instance of `UsergridResponse`. + */ + public init(client: UsergridClient?, errorName: String, errorDescription: String) { + self.client = client + self.error = UsergridResponseError(errorName: errorName, errorDescription: errorDescription, exception: nil) + } + + /** + Designated initializer for `UsergridResponse` objects finished but still may contain errors. + + - parameter client: The client responsible for the request. + - parameter data: The response data. + - parameter response: The `NSHTTPURLResponse` object. + - parameter error: The `NSError` object. + - parameter query: The query when making the request. + + - returns: A new instance of `UsergridResponse`. + */ + public init(client:UsergridClient?, data:NSData?, response:NSHTTPURLResponse?, error:NSError?, query:UsergridQuery? = nil) { + self.client = client + self.statusCode = response?.statusCode + self.headers = response?.allHeaderFields as? [String:String] + + if let sessionError = error { + self.error = UsergridResponseError(errorName: sessionError.domain, errorDescription: sessionError.localizedDescription) + } + + if let responseQuery = query { + self.query = responseQuery.copy() as? UsergridQuery + } + + if let jsonData = data { + do { + let dataAsJSON = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers) + if let jsonDict = dataAsJSON as? [String:AnyObject] { + self.responseJSON = jsonDict + if let responseError = UsergridResponseError(jsonDictionary: jsonDict) { + self.error = responseError + } else { + if let entitiesJSONArray = jsonDict[UsergridResponse.ENTITIES] as? [[String:AnyObject]] where entitiesJSONArray.count > 0 { + self.entities = UsergridEntity.entities(jsonArray: entitiesJSONArray) + } + if let cursor = jsonDict[UsergridResponse.CURSOR] as? String where !cursor.isEmpty { + self.cursor = cursor + } + } + } + } catch { + print(error) + } + } + } + + // MARK: - Instance Methods - + + /** + Attempts to load the next page of `UsergridEntity` objects. + + This requires a `cursor` to be valid as well as a `path` key within the response JSON. + + - parameter completion: The completion block that is called once the request for the next page has finished. + */ + public func loadNextPage(completion: UsergridResponseCompletion) { + if self.hasNextPage, let type = (self.responseJSON?["path"] as? NSString)?.lastPathComponent { + if let query = self.query?.copy() as? UsergridQuery { + self.client?.GET(type, query: query.cursor(self.cursor), completion:completion) + } else { + self.client?.GET(type, query: UsergridQuery(type).cursor(self.cursor), completion:completion) + } + } else { + completion(response: UsergridResponse(client: self.client, errorName: "No next page.", errorDescription: "No next page was found.")) + } + } + + static let CURSOR = "cursor" + static let ENTITIES = "entities" +} http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridResponseError.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridResponseError.swift b/sdks/swift/Source/UsergridResponseError.swift new file mode 100644 index 0000000..eda8a30 --- /dev/null +++ b/sdks/swift/Source/UsergridResponseError.swift @@ -0,0 +1,90 @@ +// +// UsergridResponseError.swift +// UsergridSDK +// +// Created by Robert Walsh on 1/8/16. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +/// A standard error object that contains details about a request failure. +public class UsergridResponseError: NSObject { + + // MARK: - Instance Properties - + + /// The error's name. + public let errorName : String + + /// The error's description. + public let errorDescription: String + + /// The exception. + public var exception: String? + + /// The description. + public override var description : String { + return "Error Name: \(errorName). Error Description: \(errorDescription). Exception: \(exception)." + } + + /// The debug description. + public override var debugDescription : String { + return "Error Name: \(errorName). Error Description: \(errorDescription). Exception: \(exception)." + } + + // MARK: - Initialization - + + /** + Designated initializer for `UsergridResponseError`. + + - parameter errorName: The error's name. + - parameter errorDescription: The error's description. + - parameter exception: The exception. + + - returns: A new instance of `UsergridResponseError` + */ + public init(errorName:String, errorDescription:String, exception:String? = nil) { + self.errorName = errorName + self.errorDescription = errorDescription + self.exception = exception + } + + /** + Convenience initializer for `UsergridResponseError` that determines if the given `jsonDictionary` contains an error. + + - parameter jsonDictionary: The JSON dictionary that may contain error information. + + - returns: A new instance of `UsergridResponseError` if the JSON dictionary did indeed contain error information. + */ + public convenience init?(jsonDictionary:[String:AnyObject]) { + if let errorName = jsonDictionary[USERGRID_ERROR] as? String, + errorDescription = jsonDictionary[USERGRID_ERROR_DESCRIPTION] as? String { + self.init(errorName:errorName,errorDescription:errorDescription,exception:jsonDictionary[USERGRID_EXCEPTION] as? String) + } else { + self.init(errorName:"",errorDescription:"") + return nil + } + } +} + +let USERGRID_ERROR = "error" +let USERGRID_ERROR_DESCRIPTION = "error_description" +let USERGRID_EXCEPTION = "exception" \ No newline at end of file http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridSDK.h ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridSDK.h b/sdks/swift/Source/UsergridSDK.h new file mode 100644 index 0000000..be9da27 --- /dev/null +++ b/sdks/swift/Source/UsergridSDK.h @@ -0,0 +1,37 @@ +// +// UsergridSDK.h +// UsergridSDK +// +// Created by Robert Walsh on 9/30/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +#import <UIKit/UIKit.h> + +//! Project version number for UsergridSDK. +FOUNDATION_EXPORT double UsergridSDKVersionNumber; + +//! Project version string for UsergridSDK. +FOUNDATION_EXPORT const unsigned char UsergridSDKVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import <UsergridSDK/PublicHeader.h> + + http://git-wip-us.apache.org/repos/asf/usergrid/blob/7442c881/sdks/swift/Source/UsergridSessionDelegate.swift ---------------------------------------------------------------------- diff --git a/sdks/swift/Source/UsergridSessionDelegate.swift b/sdks/swift/Source/UsergridSessionDelegate.swift new file mode 100644 index 0000000..cb36fb7 --- /dev/null +++ b/sdks/swift/Source/UsergridSessionDelegate.swift @@ -0,0 +1,90 @@ +// +// UsergridSessionDelegate.swift +// UsergridSDK +// +// Created by Robert Walsh on 9/30/15. +// +/* + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. 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. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + * + */ + +import Foundation + +final class UsergridSessionDelegate: NSObject { + + private var requestDelegates: [Int:UsergridAssetRequestWrapper] = [:] + + func addRequestDelegate(task:NSURLSessionTask,requestWrapper:UsergridAssetRequestWrapper) { + requestDelegates[task.taskIdentifier] = requestWrapper + } + + func removeRequestDelegate(task:NSURLSessionTask) { + requestDelegates[task.taskIdentifier] = nil + } +} + +extension UsergridSessionDelegate : NSURLSessionTaskDelegate { + + func URLSession(session: NSURLSession, task: NSURLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { + if let progressBlock = requestDelegates[task.taskIdentifier]?.progress { + progressBlock(bytesFinished:totalBytesSent, bytesExpected: totalBytesExpectedToSend) + } + } + + func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) { + if let requestWrapper = requestDelegates[task.taskIdentifier] { + requestWrapper.error = error + requestWrapper.completion(requestWrapper: requestWrapper) + } + self.removeRequestDelegate(task) + } +} + +extension UsergridSessionDelegate : NSURLSessionDataDelegate { + + func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveResponse response: NSURLResponse, completionHandler: (NSURLSessionResponseDisposition) -> Void) { + if let requestWrapper = requestDelegates[dataTask.taskIdentifier] { + requestWrapper.response = response + } + completionHandler(NSURLSessionResponseDisposition.Allow) + } + + func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) { + if let requestWrapper = requestDelegates[dataTask.taskIdentifier] { + let mutableData = requestWrapper.responseData != nil ? NSMutableData(data: requestWrapper.responseData!) : NSMutableData() + mutableData.appendData(data) + requestWrapper.responseData = mutableData + } + } +} + +extension UsergridSessionDelegate : NSURLSessionDownloadDelegate { + + func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + if let progressBlock = requestDelegates[downloadTask.taskIdentifier]?.progress { + progressBlock(bytesFinished:totalBytesWritten, bytesExpected: totalBytesExpectedToWrite) + } + } + + func URLSession(session: NSURLSession, downloadTask: NSURLSessionDownloadTask, didFinishDownloadingToURL location: NSURL) { + if let requestWrapper = requestDelegates[downloadTask.taskIdentifier] { + requestWrapper.responseData = NSData(contentsOfURL: location)! + } + } +} \ No newline at end of file