inceptum
Version:
hipages take on the foundational library for enterprise-grade apps written in NodeJS
434 lines • 18.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const LogManager_1 = require("../../log/LogManager");
const PromiseUtil_1 = require("../../util/PromiseUtil");
const IoCException_1 = require("../IoCException");
const Lifecycle_1 = require("../Lifecycle");
const SingletonDefinition_1 = require("./SingletonDefinition");
class BaseSingletonState extends Lifecycle_1.LifecycleState {
}
BaseSingletonState.INSTANTIATING = new BaseSingletonState('INSTANTIATING', 100);
BaseSingletonState.INSTANTIATED = new BaseSingletonState('INSTANTIATED', 200);
BaseSingletonState.PROPERTIES_SET = new BaseSingletonState('PROPERTIES_SET', 400);
exports.BaseSingletonState = BaseSingletonState;
var ParamType;
(function (ParamType) {
ParamType[ParamType["Value"] = 0] = "Value";
ParamType[ParamType["Reference"] = 1] = "Reference";
ParamType[ParamType["Type"] = 2] = "Type";
ParamType[ParamType["TypeArray"] = 3] = "TypeArray";
ParamType[ParamType["Config"] = 4] = "Config";
ParamType[ParamType["Group"] = 5] = "Group";
ParamType[ParamType["DefinitionGroup"] = 6] = "DefinitionGroup";
})(ParamType = exports.ParamType || (exports.ParamType = {}));
class ParamDefinition {
static withValue(val) {
const resp = new ParamDefinition(ParamType.Value);
resp.val = val;
return resp;
}
static withConfigKey(key, defaultValue) {
const resp = new ParamDefinition(ParamType.Config);
resp.key = key;
resp.defaultValue = defaultValue;
return resp;
}
static withGroupName(groupName) {
const resp = new ParamDefinition(ParamType.Group);
resp.group = groupName;
return resp;
}
static withDefinitionGroupName(groupName) {
const resp = new ParamDefinition(ParamType.DefinitionGroup);
resp.group = groupName;
return resp;
}
static withRefName(refName) {
const resp = new ParamDefinition(ParamType.Reference);
resp.refName = refName;
return resp;
}
static withClassName(className) {
const resp = new ParamDefinition(ParamType.Type);
resp.className = className;
return resp;
}
static withClassNameArr(className) {
const resp = new ParamDefinition(ParamType.TypeArray);
resp.className = className;
return resp;
}
constructor(type) {
this.type = type;
}
}
exports.ParamDefinition = ParamDefinition;
class CallDefinition {
constructor(paramName, paramDefinition) {
this.args = [];
this.paramName = paramName;
this.args.push(paramDefinition);
}
}
exports.CallDefinition = CallDefinition;
class ConfigurableSingletonDefinition extends SingletonDefinition_1.SingletonDefinition {
constructor(clazz, name, logger) {
super(clazz, name, logger);
this.constructorArgDefinitions = [];
this.propertiesToSetDefinitions = [];
this.startFunctionName = null;
this.shutdownFunctionName = null;
}
getConstructorArgDefinitions() {
return this.constructorArgDefinitions;
}
getPropertiesToSetDefinitions() {
return this.propertiesToSetDefinitions;
}
getStartFunctionName() {
return this.startFunctionName;
}
getShutdownFunctionName() {
return this.shutdownFunctionName;
}
constructorParamByValue(value) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.constructorArgDefinitions.push(ParamDefinition.withValue(value));
return this;
}
constructorParamByConfig(key, defaultValue) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.constructorArgDefinitions.push(ParamDefinition.withConfigKey(key, defaultValue));
return this;
}
constructorParamByRef(name) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.constructorArgDefinitions.push(ParamDefinition.withRefName(name));
return this;
}
constructorParamByType(clazz) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.constructorArgDefinitions.push(ParamDefinition.withClassName(clazz));
return this;
}
constructorParamByTypeArray(clazz) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.constructorArgDefinitions.push(ParamDefinition.withClassNameArr(clazz));
return this;
}
setPropertyByValue(paramName, value) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withValue(value)));
return this;
}
setPropertyByConfig(paramName, key, defaultValue) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withConfigKey(key, defaultValue)));
return this;
}
setPropertyByGroup(paramName, groupName) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withGroupName(groupName)));
return this;
}
setPropertyByDefinitionGroup(paramName, groupName) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withDefinitionGroupName(groupName)));
return this;
}
setPropertyByRef(paramName, name) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withRefName(name)));
return this;
}
setPropertyByType(paramName, className) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withClassName((typeof className === 'function' && className.name) ?
className.name : className)));
return this;
}
setPropertyByTypeArray(paramName, className) {
this.assertState(BaseSingletonState.NOT_STARTED);
this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withClassNameArr((typeof className === 'function' && className.name) ?
className.name : className)));
return this;
}
startFunction(startFunctionName) {
this.assertState(BaseSingletonState.NOT_STARTED);
if (Object.prototype.hasOwnProperty.call(this.clazz.prototype, startFunctionName)) {
this.startFunctionName = startFunctionName;
}
else {
throw new IoCException_1.IoCException(`Can't find function named ${startFunctionName} on class ${this.clazz.name}`);
}
return this;
}
stopFunction(shutdownFunctionName) {
this.assertState(BaseSingletonState.NOT_STARTED);
if (Object.prototype.hasOwnProperty.call(this.clazz.prototype, shutdownFunctionName)) {
this.shutdownFunctionName = shutdownFunctionName;
}
else {
throw new IoCException_1.IoCException(`Can't find function named ${shutdownFunctionName} on class ${this.clazz.name}`);
}
return this;
}
copyInternalProperties(copyTo) {
super.copyInternalProperties(copyTo);
copyTo.constructorArgDefinitions = this.constructorArgDefinitions.slice(0);
copyTo.propertiesToSetDefinitions = this.propertiesToSetDefinitions.slice(0);
copyTo.startFunctionName = this.startFunctionName;
copyTo.shutdownFunctionName = this.shutdownFunctionName;
}
}
exports.ConfigurableSingletonDefinition = ConfigurableSingletonDefinition;
class BaseSingletonDefinition extends ConfigurableSingletonDefinition {
// private static getMaxStateInstance(def, trace, postLoad: Set<ObjectDefinition<any>>) {
// // console.log(`In ${this.getName()} getting max instance of ${def.getName()} - ${def.status} - ${BaseSingletonState.STARTED} / trace: [${trace}]`);
// if ((trace.indexOf(def.getName()) >= 0) || (def.status < BaseSingletonState.STARTED)) {
// // Circular reference, let's try to go for one that is just instantiated and finalise init afterwards
// postLoad.add(def);
// // console.log(`Getting ${def.getName()} as Instantiated`);
// return def.getInstanceAtState(BaseSingletonState.INSTANTIATED, trace.concat([def.getName()]), postLoad);
// }
// // console.log(`Getting ${def.getName()} as ready`);
// return def.getInstanceWithTrace(trace.concat([def.getName()]), postLoad);
// }
constructor(clazz, name, logger) {
super(clazz, name, logger || LogManager_1.LogManager.getLogger(__filename));
}
// ************************************
// Get instance methods
// ************************************
getInstance() {
const self = this;
if (this.instancePromise) {
return this.instancePromise;
}
try {
this.checkConstructorCircularDependency();
// No circular dependencies in the constructor
this.instancePromise = this.instantiate();
// At this point our promise will at least instantiate the object.
const wiredInstancePromise = this.instancePromise
.then(() => self.setAllProperties())
.then(() => self.lcStart())
.then(() => {
// Once we've set the properties
self.instancePromise = wiredInstancePromise;
return self.instance;
});
// We should check now if there are circular dependencies on properties
if (!this.hasPropertiesCircularDependency()) {
// As we have no Circular Dependencies in setting the properties we can make our dependants wait for our complete initialisation
this.instancePromise = wiredInstancePromise;
}
return this.instancePromise;
}
catch (e) {
this.instancePromise = Promise.reject(e);
return this.instancePromise;
}
}
// ************************************
// Lifecycle methods
// ************************************
hasPropertiesCircularDependency(trace = []) {
if (trace.indexOf(this.name) >= 0) {
trace.push(this.name);
this.logger.debug(`Circular dependency detected on property injection: ${trace.join(' -> ')}`);
return true;
}
const newTrace = trace.concat([this.name]);
const toCheck = []
.concat(this.getAllPropertiesObjectDefinitions())
.concat(this.getAllConstructorObjectDefinitions());
return toCheck.some((element) => {
if (element instanceof BaseSingletonDefinition) {
if (element.hasPropertiesCircularDependency(newTrace)) {
return true;
}
}
return false;
});
}
checkConstructorCircularDependency(trace = []) {
if (trace.indexOf(this.name) >= 0) {
trace.push(this.name);
throw new Error(`Circular dependency detected: ${trace.join(' -> ')}`);
}
const newTrace = trace.concat([this.name]);
this.getAllConstructorObjectDefinitions().forEach((element) => {
if (element instanceof BaseSingletonDefinition) {
element.checkConstructorCircularDependency(newTrace);
}
});
}
getAllConstructorObjectDefinitions() {
return this.getConstructorArgDefinitions().reduce((prev, current) => {
const defs = this.resolveParamObjectDefinition(current);
if (defs && defs.length > 0) {
return prev.concat(defs);
}
return prev;
}, []);
}
getAllPropertiesObjectDefinitions() {
return this.getPropertiesToSetDefinitions().reduce((prev, current) => {
const allDefs = current.args.reduce((prev2, currentParamDef) => {
const defs = this.resolveParamObjectDefinition(currentParamDef);
if (defs && defs.length > 0) {
return prev2.concat(defs);
}
return prev2;
}, []);
if (allDefs && allDefs.length > 0) {
return prev.concat(allDefs);
}
return prev;
}, []);
}
getAllPropertyObjectDefinitions() {
return this.getPropertiesToSetDefinitions().reduce((prev, current) => {
const argu = current.args;
if (!argu || argu.length <= 0) {
return prev;
}
const defs = [];
argu.forEach((c) => {
const od = this.resolveParamObjectDefinition(c);
if (od) {
defs.push(od);
}
});
if (defs && defs.length > 0) {
return prev.concat(defs);
}
return prev;
}, []);
}
resolveParamObjectDefinition(paramDefinition) {
if (paramDefinition.objectDefinitions) {
return paramDefinition.objectDefinitions; // It's already resolved. Move on
}
switch (paramDefinition.type) {
case ParamType.Value:
case ParamType.Config:
paramDefinition.objectDefinitions = [];
break;
case ParamType.Reference:
paramDefinition.objectDefinitions = [this.context.getDefinitionByName(paramDefinition.refName)];
break;
case ParamType.Type:
paramDefinition.objectDefinitions = [this.context.getDefinitionByType(paramDefinition.className)];
break;
case ParamType.TypeArray:
paramDefinition.objectDefinitions = this.context.getDefinitionsByType(paramDefinition.className);
break;
case ParamType.Group:
case ParamType.DefinitionGroup:
paramDefinition.objectDefinitions = this.context.getGroupObjectNames(paramDefinition.group).map((refName) => this.context.getDefinitionByName(refName));
break;
default:
throw new IoCException_1.IoCException(`Unknown argument type ${paramDefinition.type} on bean ${this.name}`);
}
return paramDefinition.objectDefinitions;
}
instantiate() {
if (!this.context) {
return Promise.reject(new IoCException_1.IoCException(`ObjectDefinition ${this.getName()} hasn't been added to a context, Can't instantiate`));
}
this.setStatus(BaseSingletonState.INSTANTIATING);
return PromiseUtil_1.PromiseUtil.map(this.getConstructorArgDefinitions(), (argDefinition) => this.getParamDefinitionValue(argDefinition))
.then((constructorArgs) => {
this.instance = Reflect.construct(this.clazz, constructorArgs);
this.setStatus(BaseSingletonState.INSTANTIATED);
return this.instance;
});
}
setAllProperties() {
return PromiseUtil_1.PromiseUtil.map(this.getPropertiesToSetDefinitions(), (propertyToSet) => this.getSetPropertyPromise(propertyToSet));
}
getSetPropertyPromise(propertyToSet) {
return PromiseUtil_1.PromiseUtil.map(propertyToSet.args, (argDefinition) => this.getParamDefinitionValue(argDefinition))
.then((propertyParams) => {
this.instance[propertyToSet.paramName] = propertyParams[0]; // TODO: Make use of setters if available.
});
}
getParamDefinitionValue(paramDefinition) {
switch (paramDefinition.type) {
case ParamType.Value:
return Promise.resolve(paramDefinition.val);
case ParamType.Config:
const configValue = this.context.getConfig(paramDefinition.key);
return Promise.resolve(configValue !== undefined ? configValue : paramDefinition.defaultValue);
case ParamType.DefinitionGroup:
return Promise.resolve(paramDefinition.objectDefinitions);
default:
const prom = PromiseUtil_1.PromiseUtil.map(paramDefinition.objectDefinitions, (od) => od.getInstance());
switch (paramDefinition.type) {
case ParamType.Reference:
case ParamType.Type:
return prom.then((constArgs) => constArgs[0]);
}
return prom;
}
}
doStart() {
if (this.getStartFunctionName()) {
const resp = this.clazz.prototype[this.getStartFunctionName()].call(this.instance);
if (resp && resp.then) {
return resp;
}
return Promise.resolve();
}
return Promise.resolve();
}
doStop() {
if (this.getShutdownFunctionName()) {
const resp = this.clazz.prototype[this.getShutdownFunctionName()].call(this.instance);
if (resp && resp.then) {
return resp;
}
return Promise.resolve();
}
return Promise.resolve();
}
// protected resolveArgs(constructorArgs): Promise<any[]> {
// return PromiseUtil.map(constructorArgs, (arg) => {
// switch (arg.type) {
// case ParamType.Value:
// return arg.val;
// case ParamType.Config:
// return this.context.getConfig(arg.key);
// case ParamType.Reference:
// return BaseSingletonDefinition.getMaxStateInstance(
// this.context.getDefinitionByName(arg.refName),
// trace,
// postLoad);
// case ParamType.Type:
// return BaseSingletonDefinition.getMaxStateInstance(
// this.context.getDefinitionByType(arg.className),
// trace,
// postLoad);
// case ParamType.TypeArray:
// return Promise.all(
// this.context.getDefinitionsByType(arg.className).map(
// (d) => BaseSingletonDefinition.getMaxStateInstance(d, trace, postLoad)));
// default:
// return Promise.reject(new IoCException(`Unknown argument type ${arg.type} on bean ${this.name}`));
// }
// });
// }
getCopyInstance() {
return new BaseSingletonDefinition(this.clazz, this.name, this.logger);
}
}
exports.BaseSingletonDefinition = BaseSingletonDefinition;
class BaseSingletonDefinitionTestUtil extends BaseSingletonDefinition {
exposeGetAllConstructorObjectDefinitions() {
return this.getAllConstructorObjectDefinitions();
}
}
exports.BaseSingletonDefinitionTestUtil = BaseSingletonDefinitionTestUtil;
//# sourceMappingURL=BaseSingletonDefinition.js.map