Skip to content

Commit

Permalink
Update FHIR submodule to use new FHIROpenServer as base class
Browse files Browse the repository at this point in the history
  • Loading branch information
p2 committed Dec 1, 2015
1 parent 3dc5171 commit f25736c
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 301 deletions.
3 changes: 1 addition & 2 deletions Classes/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,7 @@ public class Client

/** Stops any request currently in progress. */
public func abort() {
server.abortAuthorization()
server.abortSession()
server.abort()
}

/** Resets state and authorization data. */
Expand Down
317 changes: 32 additions & 285 deletions Classes/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,11 @@ import Foundation
This implementation manages its own NSURLSession, either with an optional delegate provided via `sessionDelegate` or simply the shared
session. Subclasses can change this behavior by overriding `createDefaultSession` or any of the other request-related methods.
*/
public class Server: FHIRServer
public class Server: FHIROpenServer
{
/// The service URL as a string, as specified during initalization to be used as `aud` parameter.
final let aud: String

/// The server's base URL.
public final let baseURL: NSURL

/// An optional name of the server; will be read from conformance statement unless manually assigned.
public final var name: String?

Expand All @@ -48,13 +45,6 @@ public class Server: FHIRServer
}
}

/// The operations the server supports, as specified in the conformance statement.
var operations: [String: OperationDefinition]?
var conformanceOperations: [ConformanceRestOperation]?

/// The active URL session.
var session: NSURLSession?

var mustAbortAuthorization = false

/// An optional NSURLSessionDelegate.
Expand All @@ -71,15 +61,10 @@ public class Server: FHIRServer
/**
Main initializer. Makes sure the base URL ends with a "/" to facilitate URL generation later on.
*/
public init(baseURL base: NSURL, auth: OAuth2JSON? = nil) {
public required init(baseURL base: NSURL, auth: OAuth2JSON? = nil) {
aud = base.absoluteString
if let last = base.absoluteString.characters.last where last != "/" {
baseURL = base.URLByAppendingPathComponent("/")
}
else {
baseURL = base
}
authSettings = auth
super.init(baseURL: base, auth: auth)
didSetAuthSettings()
}

Expand Down Expand Up @@ -110,73 +95,24 @@ public class Server: FHIRServer

// MARK: - Server Conformance

/// The server's conformance statement. Must be implicitly fetched using `getConformance()`
public internal(set) var conformance: Conformance? {
didSet {
if nil == name && nil != conformance?.name {
name = conformance!.name
}

// look at ConformanceRest entries for security and operation information
if let rests = conformance?.rest {
var best: ConformanceRest?
for rest in rests {
if nil == best {
best = rest
}
else if "client" == rest.mode {
best = rest
break
}
}

// use the "best" matching rest entry to extract the information we want
if let rest = best {
if let security = rest.security {
auth = Auth.fromConformanceSecurity(security, server: self, settings: authSettings)
}

// if we have not yet initialized an Auth object we'll use one for "no auth"
if nil == auth {
auth = Auth(type: .None, server: self, settings: authSettings)
logIfDebug("Server seems to be open, proceeding with none-type auth")
}

if let operations = rest.operation {
conformanceOperations = operations
}
}
}
public override func didSetConformance(conformance: Conformance) {
if nil == name && nil != conformance.name {
name = conformance.name
}
super.didSetConformance(conformance)
}

/**
Executes a `read` action against the server's "metadata" path, as returned from `conformancePath()`, which should return the Conformance
statement.
*/
final func getConformance(callback: (error: FHIRError?) -> ()) {
if nil != conformance {
callback(error: nil)
return
}
public override func didFindConformanceRestStatement(rest: ConformanceRest) {
super.didFindConformanceRestStatement(rest)

// not yet fetched, fetch it
Conformance.readFrom(conformancePath(), server: self) { resource, error in
if let conf = resource as? Conformance {
self.conformance = conf
callback(error: nil)
}
else {
callback(error: error ?? FHIRError.Error("Conformance.readFrom() did not return a Conformance instance but \(resource)"))
}
// initialize Auth; if we can't find a suitable Auth we'll use one for "no auth"
if let security = rest.security {
auth = Auth.fromConformanceSecurity(security, server: self, settings: authSettings)
}
if nil == auth {
auth = Auth(type: .None, server: self, settings: authSettings)
logIfDebug("Server seems to be open, proceeding with none-type auth")
}
}

/** Return the relative path to the Conformance statement. This should be "metadata", we're also adding "_summary=true" to only request
the summary, not the entire statement.
*/
public func conformancePath() -> String {
return "metadata?_summary=true"
}


Expand Down Expand Up @@ -251,11 +187,24 @@ public class Server: FHIRServer
}
}

public func abort() {
abortAuthorization()
abortSession()
}

func abortAuthorization() {
logIfDebug("Aborting authorization")
mustAbortAuthorization = true
if nil != auth {
auth!.abort()
}
}

/**
Resets authorization state - including deletion of any known access and refresh tokens.
*/
func reset() {
abortSession()
abort()
auth?.reset()
}

Expand Down Expand Up @@ -302,209 +251,7 @@ public class Server: FHIRServer
public func register(dynreg: OAuth2DynReg, callback: ((json: OAuth2JSON?, error: ErrorType?) -> Void)) {
registerIfNeeded(dynreg, onlyIfNeeded: false, callback: callback)
}


// MARK: - Requests

/**
The server can return the appropriate request handler for the type and resource combination.

Request handlers are responsible for constructing an NSURLRequest that correctly performs the desired REST interaction.

- parameter type: The type of the request (GET, PUT, POST or DELETE)
- parameter resource: The resource to be involved in the request, if any
- returns: An appropriate `FHIRServerRequestHandler`, for example a _FHIRServerJSONRequestHandler_ if sending and receiving JSON
*/
public func handlerForRequestOfType(type: FHIRRequestType, resource: FHIRResource?) -> FHIRServerRequestHandler? {
return FHIRServerJSONRequestHandler(type, resource: resource)
}

/**
This method simply creates an absolute URL from the receiver's `baseURL` and the given path.

A chance for subclasses to mess with URL generation if needed.
*/
public func absoluteURLForPath(path: String, handler: FHIRServerRequestHandler) -> NSURL? {
return NSURL(string: path, relativeToURL: baseURL)
}

/**
This method should first execute `handlerForRequestOfType()` to obtain an appropriate request handler, then execute the prepared
request against the server.

- parameter type: The type of the request (GET, PUT, POST or DELETE)
- parameter path: The relative path on the server to be interacting against
- parameter resource: The resource to be involved in the request, if any
- parameter callback: A callback, likely called asynchronously, returning a response instance
*/
public func performRequestOfType(type: FHIRRequestType, path: String, resource: FHIRResource?, callback: ((response: FHIRServerResponse) -> Void)) {
if let handler = handlerForRequestOfType(type, resource: resource) {
performRequestAgainst(path, handler: handler, callback: callback)
}
else {
let res = FHIRServerRequestHandler.noneAvailableForType(type)
callback(response: res)
}
}

/**
Method to execute a given request with a given request/response handler.

- parameter path: The path, relative to the server's base; may include URL query and URL fragment (!)
- parameter handler: The RequestHandler that prepares the request and processes the response
- parameter callback: The callback to execute; NOT guaranteed to be performed on the main thread!
*/
public func performRequestAgainst<R: FHIRServerRequestHandler>(path: String, handler: R, callback: ((response: FHIRServerResponse) -> Void)) {
if let url = absoluteURLForPath(path, handler: handler) {
let request = auth?.signedRequest(url) ?? NSMutableURLRequest(URL: url)
do {
try handler.prepareRequest(request)
self.performPreparedRequest(request, handler: handler, callback: callback)
}
catch let error {
let err = (error as NSError).localizedDescription ?? "if only I knew why (\(__FILE__):\(__LINE__))"
callback(response: handler.notSent("Failed to prepare request against \(url): \(err)"))
}
}
else {
let res = handler.notSent("Failed to parse path «\(path)» relative to server base URL")
callback(response: res)
}
}

/**
Method to execute an already prepared request and use the given request/response handler.

This implementation uses the instance's NSURLSession to execute data tasks with the requests. Subclasses can override to supply
different NSURLSessions based on the request, if so desired.

- parameter request: The URL request to perform
- parameter handler: The RequestHandler that prepares the request and processes the response
- parameter callback: The callback to execute; NOT guaranteed to be performed on the main thread!
*/
public func performPreparedRequest<R: FHIRServerRequestHandler>(request: NSMutableURLRequest, handler: R, callback: ((response: FHIRServerResponse) -> Void)) {
performPreparedRequest(request, withSession: URLSession(), handler: handler, callback: callback)
}

/**
Method to execute an already prepared request with a given session and use the given request/response handler.

- parameter request: The URL request to perform
- parameter withSession: The NSURLSession instance to use
- parameter handler: The RequestHandler that prepares the request and processes the response
- parameter callback: The callback to execute; NOT guaranteed to be performed on the main thread!
*/
public func performPreparedRequest<R: FHIRServerRequestHandler>(request: NSMutableURLRequest, withSession session: NSURLSession, handler: R, callback: ((response: FHIRServerResponse) -> Void)) {
let task = session.dataTaskWithRequest(request) { data, response, error in
let res = handler.response(response: response, data: data, error: error)
logIfDebug("Server responded with status \(res.status)")
//let str = NSString(data: data!, encoding: NSUTF8StringEncoding)
//logIfDebug("\(str)")
callback(response: res)
}

logIfDebug("Performing \(handler.type.rawValue) request against \(request.URL!)")
task.resume()
}


// MARK: - Operations

func conformanceOperation(name: String) -> ConformanceRestOperation? {
if let defs = conformanceOperations {
for def in defs {
if name == def.name {
return def
}
}
}
return nil
}

/**
Retrieve the operation definition with the given name, either from cache or load the resource.

Once an OperationDefinition has been retrieved, it is cached into the instance's `operations` dictionary. Must be used after the
conformance statement has been fetched, i.e. after using `ready` or `getConformance`.
*/
public func operation(name: String, callback: (OperationDefinition? -> Void)) {
if let op = operations?[name] {
callback(op)
}
else if let def = conformanceOperation(name) {
def.definition?.resolve(OperationDefinition.self) { optop in
if let op = optop {
if nil != self.operations {
self.operations![name] = op
}
else {
self.operations = [name: op]
}
}
callback(optop)
}
}
else {
callback(nil)
}
}

/**
Performs the given Operation.

`Resource` has extensions to facilitate working with operations, be sure to take a look.

- parameter operation: The operation instance to perform
- parameter callback: The callback to call when the request ends (success or failure)
*/
public func performOperation(operation: FHIROperation, callback: ((response: FHIRServerResponse) -> Void)) {
self.operation(operation.name) { definition in
if let def = definition {
do {
try operation.validateWith(def)
try operation.perform(self, callback: callback)
}
catch let error {
callback(response: FHIRServerJSONResponse(error: error))
}
}
else {
callback(response: FHIRServerJSONResponse(error: FHIRError.OperationNotSupported(operation.name)))
}
}
}


// MARK: - Session Management

final public func URLSession() -> NSURLSession {
if nil == session {
session = createDefaultSession()
}
return session!
}

/** Create the server's default session. Override in subclasses to customize NSURLSession behavior. */
public func createDefaultSession() -> NSURLSession {
if let delegate = sessionDelegate {
return NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: delegate, delegateQueue: nil)
}
return NSURLSession.sharedSession()
}

func abortAuthorization() {
logIfDebug("Aborting authorization")
mustAbortAuthorization = true
if nil != auth {
auth!.abort()
}
}

func abortSession() {
if nil != session {
session!.invalidateAndCancel()
session = nil
}
}
}

public typealias FHIRBaseServer = Server

Loading

0 comments on commit f25736c

Please sign in to comment.