blob: c5305c182a786a621d81b0a3b435fec988e6017f [file] [log] [blame]
//
// PredictionIOSDK.swift
// PredictionIOSDK
//
// Created by Minh Tu Le on 2/23/15.
// Copyright (c) 2015 PredictionIO. All rights reserved.
//
import Foundation
import Alamofire
// MARK: - Base Client
/**
Base client that manages network connections with the server.
*/
public class BaseClient {
let baseURL: String
private let networkManager: Manager
// MARK: Constructors
/**
:param: baseURL The base URL
:param: timeout The request timeout
*/
public init(baseURL: String, timeout: NSTimeInterval) {
self.baseURL = baseURL
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = timeout
configuration.URLCache = nil
networkManager = Manager(configuration: configuration)
}
}
// MARK: - Event
/**
An `Event` class that represents a PredictionIO's event JSON dictionary in
its REST API.
*/
public struct Event {
// MARK: Constants
/// Reversed set event name.
public static let SetEvent = "$set"
/// Reversed unset event name.
public static let UnsetEvent = "$unset"
/// Reversed delete event name.
public static let DeleteEvent = "$delete"
/// Predefined user entity type.
public static let UserEntityType = "user"
/// Predefined item entity type.
public static let ItemEntityType = "item"
// MARK: Properties
/// The event name e.g. "sign-up", "rate", "view".
///
/// **Note:** All event names starting with "$" and "pio_" are reversed
/// and shouldn't be used as your custom event (e.g. "$set").
public var event: String
/// The entity type. It is the namespace of the `entityID` and analogous
/// to the table name of a relational database. The `entityID` must be
/// unique within the same `entityType`.
///
/// **Note:** All entityType names starting with "$" and "pio_" are reversed
/// and shouldn't be used.
public var entityType: String
/// The entity ID. `entityType-entityID` becomes the unique identifier
/// of the entity.
public var entityID: String
/// The target entity type.
///
/// **Note:** All targetEntityType names starting with "$" and "pio_" are reversed
/// and shouldn't be used.
public var targetEntityType: String?
/// The target entity ID.
public var targetEntityID: String?
/// The event properties.
///
/// **Note:** All properties names starting with "$" and "pio_" are reversed
/// and shouldn't be used.
public var properties: [String: AnyObject]?
/// The time of the event.
public var eventTime: NSDate
// MARK: Constructors
/**
:param: event The event name
:param: entityType The entity type
:param: entityID The entity ID
:param: targetEntityType The target entity type
:param: targetEntityID The target entity ID
:param: properties The event properties
:param: eventTime The event time
*/
public init(event: String, entityType: String, entityID: String, targetEntityType: String? = nil, targetEntityID: String? = nil, properties: [String: AnyObject]? = nil, eventTime: NSDate = NSDate()) {
self.event = event
self.entityType = entityType
self.entityID = entityID
self.targetEntityType = targetEntityType
self.targetEntityID = targetEntityID
self.properties = properties
self.eventTime = eventTime
}
// MARK: Helpers
func toDictionary() -> [String: AnyObject] {
var dict: [String: AnyObject] = [
"event": event,
"entityType": entityType,
"entityId": entityID,
"eventTime": Event.dateTimeFormatter.stringFromDate(eventTime)
]
// Target entity type and ID must be specified together.
if targetEntityType != nil && targetEntityID != nil {
dict["targetEntityType"] = targetEntityType!
dict["targetEntityId"] = targetEntityID!
}
if properties != nil {
dict["properties"] = properties!
}
return dict
}
private static let dateTimeFormatter: NSDateFormatter = {
let dateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
dateFormatter.timeZone = NSTimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return dateFormatter
}()
}
// MARK: - Event Client
/**
Client for sending data to PredictionIO Event Server.
*/
public class EventClient : BaseClient {
/// The access key for your application
let accessKey: String
private var URLForCreatingEvent: String {
return "\(baseURL)/events.json?accessKey=\(accessKey)"
}
private var URLForCreatingBatchEvents: String {
return "\(baseURL)/batch/events.json?accessKey=\(accessKey)"
}
private func URLForGettingEvent(eventID: String) -> String {
return "\(baseURL)/events/\(eventID).json?accessKey=\(accessKey)"
}
// MARK: Constructors
/**
:param: accessKey The access key for your application
:param: baseURL The base URL. Default to be http://localhost:7070.
:param: timeout The request timeout. Default to be 5s.
*/
public init(accessKey: String, baseURL: String = "http://localhost:7070", timeout: NSTimeInterval = 5) {
self.accessKey = accessKey
super.init(baseURL: baseURL, timeout: timeout)
}
// MARK: Methods
/**
Create an event in Event Server.
:param: event An `Event` instance that captures the event.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func createEvent(event: Event, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
assert((event.event == Event.UnsetEvent && event.properties?.isEmpty == true) == false, "Properties cannot be empty for $unset event")
networkManager.request(.POST, URLForCreatingEvent, parameters: event.toDictionary(), encoding: .JSON)
.responseJSON { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
/**
Create events with a batch request.
*/
public func createBatchEvents(events: [Event], completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let eventDicts = events.map { event in event.toDictionary() }
var error: NSError?
let request = NSMutableURLRequest(URL: NSURL(string: URLForCreatingBatchEvents)!)
request.HTTPMethod = Method.POST.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.HTTPBody = NSJSONSerialization.dataWithJSONObject(eventDicts, options: NSJSONWritingOptions.allZeros, error: &error)
networkManager.request(request)
.responseJSON { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
// MARK: For development and debugging purpose only.
/**
Get an event from Event Server.
:param: eventID The event ID
:param: completionHandler The callback to be executed when the request has finished.
*/
public func getEvent(eventID: String, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
if let escapedEventID = eventID.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
networkManager.request(.GET, URLForGettingEvent(escapedEventID))
.responseJSON { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
}
/**
Delete an event from Event Server.
:param: eventID The event ID
:param: completionHandler The callback to be executed when the request has finished.
*/
public func deleteEvent(eventID: String, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
if let escapedEventID = eventID.stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
networkManager.request(.DELETE, URLForGettingEvent(escapedEventID))
.responseJSON { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
}
}
// MARK: - Convenience Methods
/*
For User entities
*/
extension EventClient {
/**
Sets properties of a user.
:param: userID The user ID.
:param: properties The properties to be set.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func setUser(userID: String, properties: [String: AnyObject], eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let userEvent = Event(
event: Event.SetEvent,
entityType: Event.UserEntityType,
entityID: userID,
eventTime: eventTime,
properties: properties
)
createEvent(userEvent) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
/**
Unsets properties of a user.
:param: userID The user ID.
:param: properties The properties to be unset.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func unsetUser(userID: String, properties: [String: AnyObject], eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let userEvent = Event(
event: Event.UnsetEvent,
entityType: Event.UserEntityType,
entityID: userID,
eventTime: eventTime,
properties: properties
)
createEvent(userEvent) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
/**
Deletes a user.
:param: userID The user ID.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func deleteUser(userID: String, eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let userEvent = Event(
event: Event.DeleteEvent,
entityType: Event.UserEntityType,
entityID: userID,
eventTime: eventTime
)
createEvent(userEvent) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
}
/**
For Item entities
*/
extension EventClient {
/**
Sets properties of an item.
:param: itemID The item ID.
:param: properties The properties to be set.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func setItem(itemID: String, properties: [String: AnyObject], eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let itemEvent = Event(
event: Event.SetEvent,
entityType: Event.ItemEntityType,
entityID: itemID,
eventTime: eventTime,
properties: properties
)
createEvent(itemEvent) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
/**
Unsets properties of an item.
:param: itemID The item ID.
:param: properties The properties to be unset.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func unsetItem(itemID: String, properties: [String: AnyObject], eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let itemEvent = Event(
event: Event.UnsetEvent,
entityType: Event.ItemEntityType,
entityID: itemID,
eventTime: eventTime,
properties: properties
)
createEvent(itemEvent) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
/**
Deletes an item.
:param: itemID The item ID.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func deleteItem(itemID: String, eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let itemEvent = Event(
event: Event.DeleteEvent,
entityType: Event.ItemEntityType,
entityID: itemID,
eventTime: eventTime
)
createEvent(itemEvent) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
}
/**
For User action on Item
*/
extension EventClient {
/**
Creates a user-to-item action.
:param: action The event name.
:param: userID The userID
:param: itemID The item ID.
:param: properties The properties of the event.
:param: eventTime The event time.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func recordAction(action: String, byUserID userID: String, itemID: String, properties: [String: AnyObject] = [:], eventTime: NSDate = NSDate(), completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
let event = Event(
event: action,
entityType: Event.UserEntityType,
entityID: userID,
eventTime: eventTime,
targetEntityType: Event.ItemEntityType,
targetEntityID: itemID,
properties: properties
)
createEvent(event) { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
}
// MARK: - Engine Client
/**
Client for retrieving prediction results from an PredictionIO Engine instance.
*/
public class EngineClient : BaseClient {
private var URLForQuerying: String {
return "\(baseURL)/queries.json"
}
// MARK: Constructors
/**
:param: baseURL Base URL. Default to be http://localhost:8000.
:param: timeout Request timeout. Default to be 5s.
*/
public override init(baseURL: String = "http://localhost:8000", timeout: NSTimeInterval = 5) {
super.init(baseURL: baseURL, timeout: timeout)
}
// MARK: Methods
/**
Sends a query to the prediction engine.
:param: query The query dictionary.
:param: completionHandler The callback to be executed when the request has finished.
*/
public func sendQuery(query: [String: AnyObject], completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) {
networkManager.request(.POST, URLForQuerying, parameters: query, encoding: .JSON)
.responseJSON { (request, response, JSON, error) -> Void in
completionHandler(request, response, JSON, error)
}
}
}