/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
/**
* Defines {@link baja.SlotScheme}.
* @module baja/ord/SlotScheme
*/
define([
"bajaScript/comm",
"bajaScript/baja/ord/OrdScheme",
"bajaScript/baja/ord/OrdTarget",
"bajaScript/baja/comm/Callback" ], function (
baja,
OrdScheme,
OrdTarget,
Callback) {
"use strict";
var subclass = baja.subclass,
callSuper = baja.callSuper,
bajaDef = baja.def;
/**
* Slot ORD Scheme.
*
* This scheme resolves a `SlotPath` to a Niagara `Object`.
*
* @see baja.SlotPath
*
* @class
* @alias baja.SlotScheme
* @extends baja.OrdScheme
*/
var SlotScheme = function SlotScheme() {
callSuper(SlotScheme, this, arguments);
};
subclass(SlotScheme, OrdScheme); // TODO: Need to extend from SpaceScheme eventually
/**
* Default Slot ORD Scheme instance.
* @private
* @type {baja.SlotScheme}
*/
SlotScheme.DEFAULT = new SlotScheme();
// Empty fail function. It doesn't matter is the loads fail as the resolve without the
// network calls will result in the fail we want
var emptyFail = function (ignore) {};
//TODO: refactor
function slotSchemeResolve(scheme, target, query, cursor, options, netcall) {
var newTarget = new OrdTarget(target),
object = target.object;
newTarget.slotPath = query;
var slotPath = newTarget.slotPath, // SlotPath
space = null, // Space
container = null; // base container
netcall = bajaDef(netcall, true);
// TODO: May need to make more robust if other types of Slot Paths are added
var isVirtual = slotPath.constructor !== baja.SlotPath;
// TODO: Property Container support
if (object.getType().is("baja:VirtualGateway") && isVirtual) {
space = object.getVirtualSpace();
container = space.getRootComponent();
} else if (object.getType().is("baja:ComponentSpace")) {
space = object;
container = object.getRootComponent();
} else if (object.getType().isComponent()) {
space = object.getComponentSpace();
if (slotPath.isAbsolute()) {
container = space.getRootComponent();
} else {
container = object;
}
} else {
throw new Error("Slot Scheme Unsupported ORD base: " + object.getType());
}
// Hack for Virtual Space
if (isVirtual && slotPath.getBody() === "" && !object.getType().is("baja:VirtualComponent")) {
newTarget.object = space;
cursor.resolveNext(newTarget, options);
return;
}
// Avoid a network call if the Component Space doesn't support callbacks
if (netcall) {
if (space === null) {
netcall = false;
} else {
netcall = space.hasCallbacks();
}
}
var value = container,
nameAtDepth,
slot = null,
propertyPath = null,
depthRequestIndex = -1,
backupDepth = slotPath.getBackupDepth(),
k, i, j;
// first walk up using backup
for (k = 0; k < backupDepth; ++k) {
container = container.getType().isComponent() ? container.getParent() : null;
value = container;
if (value === null) {
throw new Error("Cannot walk backup depth: " + object);
}
}
// Walk the SlotPath
for (i = 0; i < slotPath.depth(); ++i) {
nameAtDepth = slotPath.nameAt(i);
if (isVirtual) {
nameAtDepth = baja.VirtualPath.toSlotPathName(nameAtDepth);
}
if (value.getType().isComplex()) {
slot = value.getSlot(nameAtDepth);
} else {
throw new Error("Unable to resolve ORD: " + slotPath);
}
if (slot === null) {
if (netcall) {
depthRequestIndex = i;
break;
}
throw new Error("Unresolved ORD - unable to resolve SlotPath");
}
if (!slot.isProperty()) {
if (i !== slotPath.depth() - 1) { // Actions and Topics must be at the final depth
throw new Error("Unresolved ORD - Actions/Topics must be at final depth: " + slotPath);
}
newTarget.container = container;
newTarget.slot = slot;
// Resolve the next part of the ORD and feed this target into it
cursor.resolveNext(newTarget, options);
return;
}
value = value.get(slot);
// If we have a Component without a Handle then it's probably a value from a frozen Property
// that's completely unloaded. In this case, we need to perform a load for this Component
if (value.getType().isComponent() && value.$handle === null && space !== null) {
if (netcall) {
depthRequestIndex = i;
break;
}
throw new Error("Unresolved ORD - unable to load handle for Component");
}
if (propertyPath === null && value.getType().is("baja:IPropertyContainer")) {
container = value;
} else {
if (propertyPath === null) {
propertyPath = [];
}
propertyPath.push(slot);
}
}
// Make a network call to resolve the SlotPath
var slotOrd,
batch;
if (netcall && depthRequestIndex > -1) {
batch = new baja.comm.Batch();
// Load ops on Slots that don't exist
var startIndex;
if (slotPath.isAbsolute()) {
slotOrd = "slot:/";
startIndex = 0;
} else {
slotOrd = "slot:";
startIndex = depthRequestIndex;
}
var slotPathInfo = [];
for (j = startIndex; j < slotPath.depth(); ++j) {
var slotName = slotPath.nameAt(j);
if (isVirtual) { slotName = baja.VirtualPath.toSlotPathName(slotName); }
if (j >= depthRequestIndex) {
slotPathInfo.push({ o: slotOrd, sn: slotName });
}
if (j > startIndex) { slotOrd += "/"; }
slotOrd += slotName;
}
var newOk = function () {
// Now the network calls have all been committed, resolve this
// Slot path without making any network calls
scheme.resolve(target, query, cursor, options, /*network call*/false);
};
var newFail = function (err) {
options.callback.fail(err);
};
// Attempt to load the missing Slot Path information
space.getCallbacks().loadSlotPath(slotPathInfo,
container,
new Callback(newOk, newFail, batch));
batch.commit();
return;
}
if (slot === null && slotPath.depth() > 0) {
throw new Error("Unable to resolve ORD: " + slotPath);
}
if (propertyPath === null) {
newTarget.object = container;
} else {
// If there was a Property Path then use the first Property in the Property Path as the Slot
slot = propertyPath[0];
newTarget.container = container;
newTarget.object = value;
newTarget.slot = slot;
newTarget.propertyPath = propertyPath;
}
// Resolve the next part of the ORD and feed this target into it
cursor.resolveNext(newTarget, options);
}
function slotSchemeResolveFull(scheme, target, query, cursor, options, netcall) {
var newTarget = new OrdTarget(target),
object = target.object;
newTarget.slotPath = query;
var slotPath = newTarget.slotPath, // SlotPath
space = null, // Space
container = null, // base container
isVirtual = slotPath.constructor !== baja.SlotPath;
netcall = bajaDef(netcall, true);
// TODO: Property Container support
if (object.getType().is("baja:VirtualGateway") && isVirtual) {
space = object.getVirtualSpace();
container = space.getRootComponent();
} else if (object.getType().is("baja:ComponentSpace")) {
space = object;
container = object.getRootComponent();
} else if (object.getType().isComponent()) {
space = object.getComponentSpace();
if (slotPath.isAbsolute()) {
container = space.getRootComponent();
} else {
container = object;
}
} else {
throw new Error("Slot Scheme Unsupported ORD base: " + object.getType());
}
// Hack for Virtual Space
if (isVirtual && slotPath.getBody() === "" && !object.getType().is("baja:VirtualComponent")) {
newTarget.object = space;
cursor.resolveNext(newTarget, options);
return;
}
// Avoid a network call if the Component Space doesn't support callbacks
if (netcall) {
if (space === null) {
netcall = false;
} else {
netcall = space.hasCallbacks();
}
}
var value = container,
nameAtDepth,
slot = null,
propertyPath = null,
batch = new baja.comm.Batch(),
depthRequestIndex = 0,
backupDepth = slotPath.getBackupDepth(),
k, i, j;
// first walk up using backup
for (k = 0; k < backupDepth; ++k) {
container = container.getType().isComponent() ? container.getParent() : null;
value = container;
if (value === null) {
throw new Error("Cannot walk backup depth: " + object);
}
}
// Attempt to load slots on the container if needed
if (container.getType().isComplex()) {
container.loadSlots({
"ok": baja.ok,
"fail": emptyFail,
"batch": batch
});
}
if (batch.isEmpty()) {
// Walk the SlotPath
for (i = 0; i < slotPath.depth(); ++i) {
nameAtDepth = slotPath.nameAt(i);
if (isVirtual) {
nameAtDepth = baja.VirtualPath.toSlotPathName(nameAtDepth);
}
if (value.getType().isComplex()) {
value.loadSlots({
"ok": baja.ok,
"fail": emptyFail,
"batch": batch
});
slot = value.getSlot(nameAtDepth);
} else {
throw new Error("Unable to resolve ORD: " + slotPath);
}
if (netcall && !batch.isEmpty()) {
depthRequestIndex = i;
break;
}
if (slot === null) {
throw new Error("Unresolved ORD - unable to resolve SlotPath");
}
if (!slot.isProperty()) {
if (i !== slotPath.depth() - 1) { // Actions and Topics must be at the final depth
throw new Error("Unresolved ORD - Actions/Topics must be at final depth: " + slotPath);
}
newTarget.container = container;
newTarget.slot = slot;
// Resolve the next part of the ORD and feed this target into it
cursor.resolveNext(newTarget, options);
return;
}
value = value.get(slot);
// If we have a Component without a Handle then it's probably a value from a frozen Property
// that's completely unloaded. In this case, we need to perform a load for this Component
if (value.getType().isComponent() && value.$handle === null && space !== null) {
if (netcall) {
depthRequestIndex = i;
break;
}
throw new Error("Unresolved ORD - unable to load handle for Component");
}
if (propertyPath === null && value.getType().is("baja:IPropertyContainer")) {
container = value;
} else {
if (propertyPath === null) {
propertyPath = [];
}
propertyPath.push(slot);
}
}
}
// Make a network call to resolve the SlotPath
var slotOrd;
if (!batch.isEmpty() && netcall) {
// Load ops on Slots that don't exist
slotOrd = "slot:";
if (slotPath.isAbsolute()) {
slotOrd += "/";
}
for (j = 0; j < slotPath.depth(); ++j) {
if (j > 0) {
slotOrd += "/";
}
slotOrd += isVirtual ? baja.VirtualPath.toSlotPathName(slotPath.nameAt(j)) : slotPath.nameAt(j);
if (j >= depthRequestIndex) {
space.getCallbacks().loadSlots(slotOrd, 0, new Callback(baja.ok, emptyFail, batch));
}
}
var newOk = function () {
// Now the network calls have all been committed, resolve this
// Slot path without making any network calls
scheme.resolve(target, query, cursor, options, false);
};
var newFail = function (err) {
options.callback.fail(err);
};
// Finally perform a poll so all of the sync ops can be processed from all the subsequent load ops
baja.comm.poll(new Callback(newOk, newFail, batch));
batch.commit();
return;
}
if (slot === null && slotPath.depth() > 0) {
throw new Error("Unable to resolve ORD: " + slotPath);
}
if (propertyPath === null) {
newTarget.object = container;
} else {
// If there was a Property Path then use the first Property in the Property Path as the Slot
slot = propertyPath[0];
newTarget.container = container;
newTarget.object = value;
newTarget.slot = slot;
newTarget.propertyPath = propertyPath;
}
// Resolve the next part of the ORD and feed this target into it
cursor.resolveNext(newTarget, options);
}
/**
* Called when an ORD is resolved.
*
* @private
*
* @see baja.OrdScheme#resolve
*
* @param {module:baja/ord/OrdTarget} target the current ORD Target.
* @param {baja.OrdQuery} query the ORD Query used in resolving the ORD.
* @param {module:baja/ord/OrdQueryListCursor} cursor the ORD Query List
* cursor used for helping to asynchronously resolve the ORD.
* @param {Object} options options used for resolving an ORD.
* @param {Boolean} [netcall] optional boolean to specify whether a network
* call should be attempted (used internally).
*/
SlotScheme.prototype.resolve = function (target, query, cursor, options, netcall) {
var that = this,
cb = options.callback;
function ok() {
try {
if (options.full) {
slotSchemeResolveFull(that, target, query, cursor, options, netcall);
} else {
slotSchemeResolve(that, target, query, cursor, options, netcall);
}
} catch (err) {
cb.fail(err);
}
}
// Load Slots on any Virtual Gateway before resolving the Slot Path. This will
// make sure any Virtual Spaces are fully loaded before we try to resolve and access them.
if (target.object && target.object.getType().is("baja:VirtualGateway")) {
target.object.loadSlots({
ok: ok,
fail: function (err) {
cb.fail(err);
}
});
} else {
ok();
}
};
/**
* Return an ORD Query for the scheme.
*
* @returns {baja.SlotPath}
*/
SlotScheme.prototype.parse = function (schemeName, body) {
return new baja.SlotPath(body);
};
return SlotScheme;
});