/**
* @copyright 2015 Tridium, Inc. All Rights Reserved.
* @author Gareth Johnson
*/
define([
'bajaScript/sys',
'bajaPromises' ], function (
baja,
Promise) {
"use strict";
/**
* API Status: **Private**
*
* Utilities for ords.
*
* @exports baja/ord/ordUtil
* @private
*/
var exports = {};
/**
*
* @param {baja.OrdQueryList} list
* @param {Number} index
* @return {boolean}
*/
exports.trimToStart = function (list, index) {
if (index > 0) {
var i;
for (i = 0; i < index; ++i) {
list.remove(0);
}
return true;
}
return false;
};
/**
* This function will return a promise if its able to reuse the `existingOrdTarget` based on the provided parameters.
* Any subscriber passed to the resolveParams will use subscription if the component is already available.
* If this function is unable to reuse the OrdTarget, then undefined will be returned to indicate that the caller needs to resolve the ord themselves.
* Quick resolve will return undefined for one of the following conditions:
* - the resolveParams.existingOrdTarget is missing.
* - the resolveParams.existingOrdTarget.getOrd() is different than the data ord.
* - the result of resolveParams.existingOrdTarget.getComponent() is not yet subscribed.
* - a resolveParams.cursor is requested.
*
* @see module:baja/ord/Ord#resolve
* @param {*|String|baja.Ord} data Specifies some data used to resolve.
* @param {Object} [resolveParams] An Object Literal used for ORD resolution.
* @param {module:baja/ord/OrdTarget} [resolveParams.existingOrdTarget] Use this parameter for an existing OrdTarget that might match the data ord.
* @param {Function} [resolveParams.ok] (Deprecated: use Promise) the ok function called once the ORD has been successfully resolved.
* The OrdTarget is passed to this function when invoked.
* @param {baja.Subscriber} [resolveParams.subscriber] if defined the `Component` is subscribed using this `Subscriber`.
* @param {Object} [resolveParams.cursor] if defined, this specifies parameters for
* iterating through a Cursor.
* @return {Promise.<module:baja/ord/OrdTarget>|undefined}
*/
exports.quickResolve = function (data, resolveParams) {
resolveParams = resolveParams || {};
var existingOrdTarget = resolveParams.existingOrdTarget;
var cursor = resolveParams.cursor;
var ok = resolveParams.ok;
if (cursor) {
return;
}
var dataString = String(data);
if (existingOrdTarget !== undefined) {
if (dataString === String(existingOrdTarget.getOrd())) {
var subscribePromise;
var subscriber = resolveParams.subscriber;
var comp = existingOrdTarget.getComponent();
//if the previous ordTarget's component is not subscribed, then get a fresh copy
if (comp && !comp.isSubscribed()) {
return;
}
if (subscriber && comp) {
subscribePromise = subscriber.subscribe(comp);
}
return Promise.resolve(subscribePromise)
.then(function () {
return typeof ok === "function" && ok(existingOrdTarget);
})
.then(function () {
return existingOrdTarget;
});
}
}
};
/**
* Resolves ORDs while also calculating ORDs that is optimized for
* re-resolution in the future. Use this when resolving ORDs that you *know*
* may be re-resolved again, especially if the original ORDs may include
* expensive operations like BQL queries.
*
* The optimized ORD will be available as an `optimizedOrd` facet on each
* resolved target.
*
* @param {object} params
* @param {Array.<baja.Ord>} params.ords ords to resolve
* @param {object} [cx] context to be used for optimizing the ords
* @returns {Promise.<baja.BatchResolve>}
* @example
* return ordUtil.resolveOptimized({
* ords: [ station:|slot:/|bql:select * from baja:Component|single:' ],
* base: baja.localhost
* })
* .then(function (br) {
* var ordTarget = br.getTarget(0);
* var optimizedOrd = ordTarget.getFacets().get('optimizedOrd');
* // sock it away for re-resolution in the future
* });
*/
exports.resolveOptimized = function (params, cx) {
var ords = params.ords.map(baja.Ord.make);
var br = new baja.BatchResolve(ords);
return br.resolve({ subscriber: params.subscriber })
.catch(function (ignore) {})
.then(function () {
for (var i = 0, len = br.size(); i < len; ++i) {
if (br.isResolved(i)) {
var ordTarget = br.getTarget(i);
ordTarget.$facets = baja.Facets.make(ordTarget.getFacets(), {
optimizedOrd: exports.optimizeForReresolution(ords[i], ordTarget, cx)
});
}
}
return br;
});
};
/**
* @param {baja.Ord} ord
* @param {module:baja/ord/OrdTarget} ordTarget
* @param {object} cx
* @returns {baja.Ord} a substitute ord optimized for reresolution. If the
* input ord does not contain any substitutable schemes, the input ord will be
* returned directly.
*/
exports.optimizeForReresolution = function (ord, ordTarget, cx) {
var subScheme = exports.findSubstitutableOrdScheme(ord);
if (subScheme) {
return subScheme.convertToSubstituteOrd(ord, ordTarget, cx);
} else {
return ord;
}
};
/**
* @param {baja.Ord} ord
* @returns {baja.OrdScheme|null} the first substitutable ord scheme found in
* the ord
* @since Niagara 4.10
*/
exports.findSubstitutableOrdScheme = function (ord) {
var queryList = ord.parse();
for (var i = 0, len = queryList.size(); i < len; i++) {
var scheme = queryList.get(i).getScheme();
if (isSubstitutableScheme(scheme)) { return scheme; }
}
return null;
};
/**
* Provides the default substitute ORD conversion which is valid if the given
* OrdTarget result resolves to a mounted {@link baja.Component}, or a slot
* under mounted Component. Otherwise the original ORD is returned.
*
* @param {baja.Ord} ord The original ORD that contains a
* {@link baja.OrdQuery} using an OrdScheme that is this
* `baja:ISubstitutableOrdScheme`'s type. This method should not be called if
* no `baja:ISubstitutableOrdScheme` is present (use
* `findSubstitutableOrdScheme` first).
* @param {module:baja/ord/OrdTarget} ordTarget The OrdTarget that the
* original ORD resolved to which can be useful for computing a more efficient
* substitute ORD that resolves to this same result.
* @param {object} cx Additional Context information (for possible future
* use).
* @return {baja.Ord} A more efficient substitute ORD that can be used on
* subsequent ORD resolutions instead of the original ORD to resolve to the
* same result, or the original ORD if a better substitute ORD can't be found.
* @since Niagara 4.10
*/
exports.convertToSubstituteComponentOrd = function (ord, ordTarget, cx) {
/*
* devs: when making changes to this logic, please ensure the
* convertToSubstituteComponentOrd method in `BISubstitutableOrdScheme`
* receives corresponding logic changes
*/
try {
var component = ordTarget && ordTarget.getComponent();
if (!component) { return ord; }
// The preferred substitute ORD is either the absolute (handle) ORD for
// normal station components or the nav (virtual) ORD for virtuals
var substituteOrd =
baja.hasType(component, 'baja:VirtualComponent') ?
component.getNavOrd() : getAbsoluteOrd(component);
if (!substituteOrd) {
// Conversion can't happen if the component is not mounted in a
// component space, so just return the original ORD in that case
return ord;
}
// Check to see if the ORD resolves to a non-BComponent slot under a
// component, and if so, append that slot's path to the preferred
// substitute ORD for the parent component
var propertyPath = ordTarget.propertyPath;
if (!propertyPath) {
// Check for an action or topic slot
var slot = ordTarget.slot;
if (slot) { propertyPath = [ slot ]; }
}
if (propertyPath && propertyPath.length) {
substituteOrd = baja.Ord.make({
base: substituteOrd,
child: 'slot:' + propertyPath.join('/')
});
}
// If there was a view query at the end, also append that to the
// preferred substitute ORD
var viewQuery = getViewQuery(ord);
if (viewQuery) {
substituteOrd = baja.Ord.make({ base: substituteOrd, child: viewQuery });
}
// Almost done, before returning the preferred substitute ORD, we may need
// to relativize it to the host or session. So let's interrogate the
// original ORD to see whether it was already relativized to host/session,
// and use that input to decide whether to also relativize the substitute
// result
var originalNormalizedOrd = ord.normalize();
var originalOrdStr = String(originalNormalizedOrd);
// not implementing relativizeToHost here because BajaScript only operates with one host
// If relativizing the original ORD to session has no effect, then it must
// have already been relativized to the session
if (String(originalNormalizedOrd.relativizeToSession()) === originalOrdStr) {
substituteOrd = substituteOrd.relativizeToSession();
}
return substituteOrd;
} catch (e) {
// It seems unlikely that exceptions will occur in this method, but just
// in case, let's log it and return the original ORD
baja.error(e);
}
// If unconvertable, return the original ORD
return ord;
};
/**
* Normalize and split the ORD into two parts,
* an ORD without a ViewQuery and the ViewQuery
* @param {baja.Ord|String} ord
* @returns {{ord: baja.Ord, viewQuery: baja.ViewQuery}}
* @since Niagara 4.11
*/
exports.getOrdViewQuerySplit = function (ord) {
ord = baja.Ord.make(ord);
ord = ord.normalize();
var queryList = ord.parse();
var last = queryList.get(queryList.size() - 1);
if (last && last.getSchemeName() === 'view') {
queryList.remove(queryList.size() - 1);
return {
ord: baja.Ord.make(queryList.toString()),
viewQuery: last
};
}
return { ord: ord };
};
/**
* Append ViewQuery with the provided OrdTarget.
* Note that the ViewQuery will also be appended to the `optimizedOrd` facet.
*
* @param {module:baja/ord/OrdTarget} ordTarget
* @param {baja.ViewQuery} [viewQuery]
* @since Niagara 4.11
*/
exports.appendViewQueryToOrdTarget = function (ordTarget, viewQuery) {
if (!viewQuery || !ordTarget.ord) {
return;
}
var viewQueryStr = "|view:" + viewQuery.getBody();
ordTarget.ord = baja.Ord.make(ordTarget.ord + viewQueryStr);
var facets = ordTarget.$facets;
if (facets) {
var optimizedOrd = facets.get('optimizedOrd');
if (optimizedOrd) {
optimizedOrd = baja.Ord.make(optimizedOrd + viewQueryStr);
ordTarget.$facets = baja.Facets.make(facets, baja.Facets.make([ 'optimizedOrd' ], [ optimizedOrd ]));
}
}
};
function getAbsoluteOrd(component) {
return baja.Ord.make({
base: component.getComponentSpace().getAbsoluteOrd(),
child: getOrdInSpace(component)
});
}
function getHandleOrd(component) {
return baja.Ord.make('h:' + component.getHandle());
}
function getOrdInSpace(component) {
return getHandleOrd(component);
}
function isSubstitutableScheme(scheme) {
return baja.hasType(scheme, 'baja:ISubstitutableOrdScheme') &&
typeof scheme.convertToSubstituteOrd === 'function';
}
function getViewQuery(ord) {
var queryList = ord.parse();
var last = queryList.get(queryList.size() - 1);
if (last && last.getSchemeName() === 'view') { return last; }
}
return exports;
});