blob: 7babee4e1aefa6f1b134791e790f578bb1eed5e0 [file] [log] [blame]
//
// UsergridEntity.swift
// UsergridSDK
//
// Created by Robert Walsh on 7/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
import CoreLocation
/**
`UsergridEntity` is the base class that contains a single Usergrid entity.
`UsergridEntity` maintains a set of accessor properties for standard Usergrid schema properties (e.g. name, uuid), and supports helper methods for accessing any custom properties that might exist.
*/
open class UsergridEntity: NSObject, NSCoding {
static private var subclassMappings: [String:UsergridEntity.Type] = [UsergridUser.USER_ENTITY_TYPE:UsergridUser.self,UsergridDevice.DEVICE_ENTITY_TYPE:UsergridDevice.self]
// MARK: - Instance Properties -
/// The property dictionary that stores the properties values of the `UsergridEntity` object.
private var properties: [String : Any] {
didSet {
if let fileMetaData = properties.removeValue(forKey: UsergridFileMetaData.FILE_METADATA) as? [String:Any] {
self.fileMetaData = UsergridFileMetaData(fileMetaDataJSON: fileMetaData)
} else {
self.fileMetaData = nil
}
}
}
/// The `UsergridAsset` that contains the asset data.
internal(set) public var asset: UsergridAsset?
/// The `UsergridFileMetaData` of this `UsergridEntity`.
internal(set) public var fileMetaData : UsergridFileMetaData?
/// Property helper method for the `UsergridEntity` objects `UsergridEntityProperties.type`.
public var type: String { return self.getEntitySpecificProperty(.type) as! String }
/// Property helper method for the `UsergridEntity` objects `UsergridEntityProperties.uuid`.
public var uuid: String? { return self.getEntitySpecificProperty(.uuid) as? String }
/// Property helper method for the `UsergridEntity` objects `UsergridEntityProperties.name`.
public var name: String? { return self.getEntitySpecificProperty(.name) as? String }
/// Property helper method for the `UsergridEntity` objects `UsergridEntityProperties.created`.
public var created: Date? { return self.getEntitySpecificProperty(.created) as? Date }
/// Property helper method for the `UsergridEntity` objects `UsergridEntityProperties.modified`.
public var modified: Date? { return self.getEntitySpecificProperty(.modified) as? Date }
/// Property helper method for the `UsergridEntity` objects `UsergridEntityProperties.location`.
public var location: CLLocation? {
get { return self.getEntitySpecificProperty(.location) as? CLLocation }
set(newLocation) { self[UsergridEntityProperties.location.stringValue] = newLocation }
}
/// Property helper method to get the UUID or name of the `UsergridEntity`.
public var uuidOrName: String? { return self.uuid ?? self.name }
/// Tells you if this `UsergridEntity` has a type of `user`.
public var isUser: Bool { return self is UsergridUser || self.type == UsergridUser.USER_ENTITY_TYPE }
/// Tells you if there is an asset associated with this entity.
public var hasAsset: Bool { return self.asset != nil || (self.fileMetaData?.contentLength ?? 0) > 0 }
/// The JSON object value.
public var jsonObjectValue : [String:Any] { return self.properties }
/// The string value.
public var stringValue : String { return NSString(data: try! JSONSerialization.data(withJSONObject: self.jsonObjectValue, options: .prettyPrinted), encoding: String.Encoding.utf8.rawValue) as! String }
/// The description.
open override var description : String {
return "Properties of Entity: \(stringValue)."
}
/// The debug description.
open override var debugDescription : String {
return "Properties of Entity: \(stringValue)."
}
// MARK: - Initialization -
/**
Designated initializer for `UsergridEntity` objects
- parameter type: The type associated with the `UsergridEntity` object.
- parameter name: The optional name associated with the `UsergridEntity` object.
- parameter propertyDict: The optional property dictionary that the `UsergridEntity` object will start out with.
- returns: A new `UsergridEntity` object.
*/
required public init(type:String, name:String? = nil, propertyDict:[String:Any]? = nil) {
self.properties = propertyDict ?? [:]
super.init()
if self is UsergridUser {
self.properties[UsergridEntityProperties.type.stringValue] = UsergridUser.USER_ENTITY_TYPE
} else if self is UsergridDevice {
self.properties[UsergridEntityProperties.type.stringValue] = UsergridDevice.DEVICE_ENTITY_TYPE
} else {
self.properties[UsergridEntityProperties.type.stringValue] = type
}
if let entityName = name {
self.properties[UsergridEntityProperties.name.stringValue] = entityName
}
if let fileMetaData = self.properties.removeValue(forKey: UsergridFileMetaData.FILE_METADATA) as? [String:Any] {
self.fileMetaData = UsergridFileMetaData(fileMetaDataJSON: fileMetaData)
}
}
internal func copyInternalsFromEntity(_ entity:UsergridEntity) {
self.properties = entity.properties
}
/**
Used for custom mapping subclasses to a given `Usergrid` type.
- parameter type: The type of the `Usergrid` object.
- parameter toSubclass: The subclass `UsergridEntity.Type` to map it to.
*/
public static func mapCustomType(_ type:String,toSubclass:UsergridEntity.Type) {
UsergridEntity.subclassMappings[type] = toSubclass
}
/**
Class convenience constructor for creating `UsergridEntity` objects dynamically.
- parameter jsonDict: A valid JSON dictionary which must contain at the very least a value for the `type` key.
- returns: A `UsergridEntity` object provided that the `type` key within the dictionay exists. Otherwise nil.
*/
public class func entity(jsonDict: [String:Any]) -> UsergridEntity? {
guard let type = jsonDict[UsergridEntityProperties.type.stringValue] as? String
else {
return nil
}
let mapping = UsergridEntity.subclassMappings[type] ?? UsergridEntity.self
return mapping.init(type: type, propertyDict: jsonDict)
}
/**
Class convenience constructor for creating multiple `UsergridEntity` objects dynamically.
- parameter entitiesJSONArray: An array which contains dictionaries that are used to create the `UsergridEntity` objects.
- returns: An array of `UsergridEntity`.
*/
public class func entities(jsonArray entitiesJSONArray: [[String:Any]]) -> [UsergridEntity] {
var entityArray : [UsergridEntity] = []
for entityJSONDict in entitiesJSONArray {
if let entity = UsergridEntity.entity(jsonDict:entityJSONDict) {
entityArray.append(entity)
}
}
return entityArray
}
// MARK: - NSCoding -
/**
NSCoding protocol initializer.
- parameter aDecoder: The decoder.
- returns: A decoded `UsergridUser` object.
*/
required public init?(coder aDecoder: NSCoder) {
guard let properties = aDecoder.decodeObject(forKey: "properties") as? [String:Any]
else {
self.properties = [:]
super.init()
return nil
}
self.properties = properties
self.fileMetaData = aDecoder.decodeObject(forKey: "fileMetaData") as? UsergridFileMetaData
self.asset = aDecoder.decodeObject(forKey: "asset") as? UsergridAsset
super.init()
}
/**
NSCoding protocol encoder.
- parameter aCoder: The encoder.
*/
open func encode(with aCoder: NSCoder) {
aCoder.encode(self.properties, forKey: "properties")
aCoder.encode(self.fileMetaData, forKey: "fileMetaData")
aCoder.encode(self.asset, forKey: "asset")
}
// MARK: - Property Manipulation -
/**
Subscript for the `UsergridEntity` class.
- Example usage:
```
let propertyValue = usergridEntity["propertyName"]
usergridEntity["propertyName"] = propertyValue
```
*/
public subscript(propertyName: String) -> Any? {
get {
if let entityProperty = UsergridEntityProperties.fromString(propertyName) {
return self.getEntitySpecificProperty(entityProperty)
} else {
let propertyValue = self.properties[propertyName]
if propertyValue is NSNull { // Let's just return nil for properties that have been removed instead of NSNull
return nil
} else {
return propertyValue
}
}
}
set(propertyValue) {
if let value = propertyValue {
if let entityProperty = UsergridEntityProperties.fromString(propertyName) {
if entityProperty.isMutableForEntity(self) {
if entityProperty == .location {
if let location = value as? CLLocation {
properties[propertyName] = [ENTITY_LATITUDE:location.coordinate.latitude,
ENTITY_LONGITUDE:location.coordinate.longitude]
} else if let location = value as? CLLocationCoordinate2D {
properties[propertyName] = [ENTITY_LATITUDE:location.latitude,
ENTITY_LONGITUDE:location.longitude]
} else if let location = value as? [String:Double] {
if let lat = location[ENTITY_LATITUDE], let long = location[ENTITY_LONGITUDE] {
properties[propertyName] = [ENTITY_LATITUDE:lat,
ENTITY_LONGITUDE:long]
}
}
} else {
properties[propertyName] = value
}
}
} else {
properties[propertyName] = value
}
} else { // If the property value is nil we assume they wanted to remove the property.
// We set the value for this property to Null so that when a PUT is performed on the entity the property will actually be removed from the Entity on Usergrid
if let entityProperty = UsergridEntityProperties.fromString(propertyName){
if entityProperty.isMutableForEntity(self) {
properties[propertyName] = NSNull()
}
} else {
properties[propertyName] = NSNull()
}
}
}
}
/**
Updates a properties value for the given property name.
- parameter name: The name of the property.
- parameter value: The value to update to.
*/
public func putProperty(_ name:String,value:Any?) {
self[name] = value
}
/**
Updates a set of properties that are within the given properties dictionary.
- parameter properties: The property dictionary containing the properties names and values.
*/
public func putProperties(_ properties:[String:Any]) {
for (name,value) in properties {
self.putProperty(name, value: value)
}
}
/**
Removes the property for the given property name.
- parameter name: The name of the property.
*/
public func removeProperty(_ name:String) {
self[name] = nil
}
/**
Removes the properties with the names within the propertyNames array
- parameter propertyNames: An array of property names.
*/
public func removeProperties(_ propertyNames:[String]) {
for name in propertyNames {
self.removeProperty(name)
}
}
/**
Appends the given value to the end of the properties current value.
- parameter name: The name of the property.
- parameter value: The value or an array of values to append.
*/
public func append(_ name:String, value:Any) {
self.insertArray(name, values:value as? [Any] ?? [value], index: Int.max)
}
/**
Inserts the given value at the given index within the properties current value.
- parameter name: The name of the property.
- parameter index: The index to insert at.
- parameter value: The value or an array of values to insert.
*/
public func insert(_ name:String, value:Any, index:Int = 0) {
self.insertArray(name, values:value as? [Any] ?? [value], index: index)
}
/**
Inserts an array of property values at a given index within the properties current value.
- parameter name: The name of the property
- parameter index: The index to insert at.
- parameter values: The values to insert.
*/
private func insertArray(_ name:String,values:[Any], index:Int = 0) {
if let propertyValue = self[name] {
if let arrayValue = propertyValue as? [Any] {
var arrayOfValues = arrayValue
if index > arrayValue.count {
arrayOfValues.append(contentsOf: values)
} else {
arrayOfValues.insert(contentsOf: values, at: index)
}
self[name] = arrayOfValues
} else {
if index > 0 {
self[name] = [propertyValue] + values
} else {
self[name] = values + [propertyValue]
}
}
} else {
self[name] = values
}
}
/**
Removes the last value of the properties current value.
- parameter name: The name of the property.
*/
public func pop(_ name:String) {
if let arrayValue = self[name] as? [Any] , arrayValue.count > 0 {
var arrayOfValues = arrayValue
arrayOfValues.removeLast()
self[name] = arrayOfValues
}
}
/**
Removes the first value of the properties current value.
- parameter name: The name of the property.
*/
public func shift(_ name:String) {
if let arrayValue = self[name] as? [Any] , arrayValue.count > 0 {
var arrayOfValues = arrayValue
arrayOfValues.removeFirst()
self[name] = arrayOfValues
}
}
private func getEntitySpecificProperty(_ entityProperty: UsergridEntityProperties) -> Any? {
var propertyValue: Any? = nil
switch entityProperty {
case .uuid,.type,.name :
propertyValue = self.properties[entityProperty.stringValue]
case .created,.modified :
if let milliseconds = self.properties[entityProperty.stringValue] as? Int {
propertyValue = Date(milliseconds: milliseconds.description)
}
case .location :
if let locationDict = self.properties[entityProperty.stringValue] as? [String:Double], let lat = locationDict[ENTITY_LATITUDE], let long = locationDict[ENTITY_LONGITUDE] {
propertyValue = CLLocation(latitude: lat, longitude: long)
}
}
return propertyValue
}
// MARK: - CRUD Convenience Methods -
/**
Performs a GET on the `UsergridEntity` using the shared instance of `UsergridClient`.
- parameter completion: An optional completion block that, if successful, will contain the reloaded `UsergridEntity` object.
*/
public func reload(_ completion: UsergridResponseCompletion? = nil) {
self.reload(Usergrid.sharedInstance, completion: completion)
}
/**
Performs a GET on the `UsergridEntity`.
- parameter client: The client to use when reloading.
- parameter completion: An optional completion block that, if successful, will contain the reloaded `UsergridEntity` object.
*/
public func reload(_ client:UsergridClient, completion: UsergridResponseCompletion? = nil) {
guard let uuidOrName = self.uuidOrName
else {
completion?(UsergridResponse(client: client, errorName: "Entity cannot be reloaded.", errorDescription: "Entity has neither an UUID or name specified."))
return
}
client.GET(self.type, uuidOrName: uuidOrName) { response in
if let responseEntity = response.entity {
self.copyInternalsFromEntity(responseEntity)
}
completion?(response)
}
}
/**
Performs a PUT (or POST if no UUID is found) on the `UsergridEntity` using the shared instance of `UsergridClient`.
- parameter completion: An optional completion block that, if successful, will contain the updated/saved `UsergridEntity` object.
*/
public func save(_ completion: UsergridResponseCompletion? = nil) {
self.save(Usergrid.sharedInstance, completion: completion)
}
/**
Performs a PUT (or POST if no UUID is found) on the `UsergridEntity`.
- parameter client: The client to use when saving.
- parameter completion: An optional completion block that, if successful, will contain the updated/saved `UsergridEntity` object.
*/
public func save(_ client:UsergridClient, completion: UsergridResponseCompletion? = nil) {
if let _ = self.uuid { // If UUID exists we PUT otherwise POST
client.PUT(self) { response in
if let responseEntity = response.entity {
self.copyInternalsFromEntity(responseEntity)
}
completion?(response)
}
} else {
client.POST(self) { response in
if let responseEntity = response.entity {
self.copyInternalsFromEntity(responseEntity)
}
completion?(response)
}
}
}
/**
Performs a DELETE on the `UsergridEntity` using the shared instance of the `UsergridClient`.
- parameter completion: An optional completion block.
*/
public func remove(_ completion: UsergridResponseCompletion? = nil) {
self.remove(Usergrid.sharedInstance, completion: completion)
}
/**
Performs a DELETE on the `UsergridEntity`.
- parameter client: The client to use when removing.
- parameter completion: An optional completion block.
*/
public func remove(_ client:UsergridClient, completion: UsergridResponseCompletion? = nil) {
client.DELETE(self, completion: completion)
}
// MARK: - Asset Management -
/**
Uploads the given `UsergridAsset` and the data within it and creates an association between this `UsergridEntity` with the given `UsergridAsset` using the shared instance of `UsergridClient`.
- parameter asset: The `UsergridAsset` object to upload.
- parameter progress: An optional progress block to keep track of upload progress.
- parameter completion: An optional completion block.
*/
public func uploadAsset(_ asset:UsergridAsset, progress:UsergridAssetRequestProgress? = nil, completion:UsergridAssetUploadCompletion? = nil) {
self.uploadAsset(Usergrid.sharedInstance, asset: asset, progress: progress, completion: completion)
}
/**
Uploads the given `UsergridAsset` and the data within it and creates an association between this `UsergridEntity` with the given `UsergridAsset`.
- parameter client: The client to use when uploading.
- parameter asset: The `UsergridAsset` object to upload.
- parameter progress: An optional progress block to keep track of upload progress.
- parameter completion: An optional completion block.
*/
public func uploadAsset(_ client:UsergridClient, asset:UsergridAsset, progress:UsergridAssetRequestProgress? = nil, completion:UsergridAssetUploadCompletion? = nil) {
client.uploadAsset(self, asset: asset, progress:progress, completion:completion)
}
/**
Downloads the `UsergridAsset` that is associated with this `UsergridEntity` using the shared instance of `UsergridClient`.
- parameter contentType: The content type of the data to load.
- parameter progress: An optional progress block to keep track of download progress.
- parameter completion: An optional completion block.
*/
public func downloadAsset(_ contentType:String, progress:UsergridAssetRequestProgress? = nil, completion:UsergridAssetDownloadCompletion? = nil) {
self.downloadAsset(Usergrid.sharedInstance, contentType: contentType, progress: progress, completion: completion)
}
/**
Downloads the `UsergridAsset` that is associated with this `UsergridEntity`.
- parameter client: The client to use when uploading.
- parameter contentType: The content type of the data to load.
- parameter progress: An optional progress block to keep track of download progress.
- parameter completion: An optional completion block.
*/
public func downloadAsset(_ client:UsergridClient, contentType:String, progress:UsergridAssetRequestProgress? = nil, completion:UsergridAssetDownloadCompletion? = nil) {
client.downloadAsset(self, contentType: contentType, progress:progress, completion: completion)
}
// MARK: - Connection Management -
/**
Creates a relationship between this `UsergridEntity` and the given entity using the shared instance of `UsergridClient`.
- parameter relationship: The relationship type.
- parameter toEntity: The entity to connect.
- parameter completion: An optional completion block.
*/
public func connect(_ relationship:String, toEntity:UsergridEntity, completion: UsergridResponseCompletion? = nil) {
self.connect(Usergrid.sharedInstance, relationship: relationship, toEntity: toEntity, completion: completion)
}
/**
Creates a relationship between this `UsergridEntity` and the given entity.
- parameter client: The client to use when connecting.
- parameter relationship: The relationship type.
- parameter toEntity: The entity to connect.
- parameter completion: An optional completion block.
*/
public func connect(_ client:UsergridClient, relationship:String, toEntity:UsergridEntity, completion: UsergridResponseCompletion? = nil) {
client.connect(self, relationship: relationship, to: toEntity, completion: completion)
}
/**
Removes a relationship between this `UsergridEntity` and the given entity using the shared instance of `UsergridClient`.
- parameter relationship: The relationship type.
- parameter fromEntity: The entity to disconnect.
- parameter completion: An optional completion block.
*/
public func disconnect(_ relationship:String, fromEntity:UsergridEntity, completion: UsergridResponseCompletion? = nil) {
self.disconnect(Usergrid.sharedInstance, relationship: relationship, fromEntity: fromEntity, completion: completion)
}
/**
Removes a relationship between this `UsergridEntity` and the given entity.
- parameter client: The client to use when disconnecting.
- parameter relationship: The relationship type.
- parameter fromEntity: The entity to disconnect.
- parameter completion: An optional completion block.
*/
public func disconnect(_ client:UsergridClient, relationship:String, fromEntity:UsergridEntity, completion: UsergridResponseCompletion? = nil) {
client.disconnect(self, relationship: relationship, from: fromEntity, completion: completion)
}
/**
Gets the `UsergridEntity` objects, if any, which are connected via the relationship using the shared instance of `UsergridClient`.
- parameter direction: The direction of the connection.
- parameter relationship: The relationship type.
- parameter query: The optional query.
- parameter completion: An optional completion block.
*/
public func getConnections(_ direction:UsergridDirection, relationship:String, query:UsergridQuery? = nil, completion:UsergridResponseCompletion? = nil) {
self.getConnections(Usergrid.sharedInstance, direction: direction, relationship: relationship, query: query, completion: completion)
}
/**
Gets the `UsergridEntity` objects, if any, which are connected via the relationship.
- parameter client: The client to use when getting the connected `UsergridEntity` objects.
- parameter direction: The direction of the connection.
- parameter relationship: The relationship type.
- parameter query: The optional query.
- parameter completion: An optional completion block.
*/
public func getConnections(_ client:UsergridClient, direction:UsergridDirection, relationship:String, query:UsergridQuery? = nil, completion:UsergridResponseCompletion? = nil) {
client.getConnections(direction, entity: self, relationship: relationship, query:query, completion: completion)
}
// MARK: - Helper methods -
/**
Determines if the two `UsergridEntity` objects are equal. i.e. they have the same non nil uuidOrName.
- parameter entity: The entity to check.
- returns: If the two `UsergridEntity` objects are equal. i.e. they have the same non nil uuidOrName.
*/
public func isEqualToEntity(_ entity: UsergridEntity?) -> Bool {
guard let selfUUID = self.uuidOrName,
let entityUUID = entity?.uuidOrName
else {
return false
}
return selfUUID == entityUUID
}
}