| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228 |
1x
1x
1x
1x
1x
12x
12x
1x
1x
12x
12x
12x
1x
161x
1x
161x
1x
13x
13x
2x
11x
11x
11x
11x
11x
11x
11x
11x
11x
11x
11x
11x
11x
11x
1x
118x
118x
117x
117x
117x
1x
1x
118x
118x
| /*
* Context Manager
*
* Copyright 2014-2017 Raising the Floor - International
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* The research leading to these results has received funding from the European Union's
* Seventh Framework Programme (FP7/2007-2013)
* under grant agreement no. 289016.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
"use strict";
var fluid = require("infusion"),
gpii = fluid.registerNamespace("gpii");
fluid.defaults("gpii.contextManager.app", {
gradeNames: "kettle.app",
components: {
contextManager: {
type: "gpii.contextManager"
}
},
requestHandlers: {
environmentChanged: {
route: "/environmentChanged",
method: "put",
type: "gpii.contextManager.environmentChanged.handler"
}
}
});
fluid.defaults("gpii.contextManager.environmentChanged.handler", {
gradeNames: ["kettle.request.http"],
invokers: {
handleRequest: {
funcName: "gpii.contextManager.environmentChanged.handleRequest",
args: ["{flowManager}.contextManager", "{request}.req.body", "{request}.events.onSuccess"]
}
}
});
/**
* Request handler for URL /environmentChanged. Forwards the content to environmentChanged
* function and fires the onSuccess event.
*
* @param that {Component} The context manager component
* @param body {Object} the body of the request. It is expected to contain only key-value pairs
* of context variables, and optionally a 'timestamp' key (and associated value) which
* will not be considered part of the current context
* @param onSuccess {Event} onSuccess event - will be fired with empty content
**/
gpii.contextManager.environmentChanged.handleRequest = function (that, body, onSuccess) {
gpii.contextManager.updateCurrentEnvironment(that, body);
onSuccess.fire();
};
fluid.defaults("gpii.contextManager", {
gradeNames: "fluid.modelComponent",
members: {
temporalIntervalID: null
},
invokers: {
updateActiveContextName: {
funcName: "gpii.contextManager.updateActiveContextName",
args: ["{that}", "{arguments}.0", "{arguments}.1"] // fullPayload, forceContextName
},
contextChanged: {
funcName: "gpii.contextManager.contextChanged",
args: ["{that}", "{lifecycleManager}", "{arguments}.0"] // forceContextName
},
environmentChanged: "gpii.contextManager.updateCurrentEnvironment({that}, {arguments}.0)" // body
},
modelListeners: {
currentEnvironment: {
func: "{that}.contextChanged",
args: []
}
},
listeners: {
onCreate: "gpii.contextManager.startTemporalEnvironmentReporter",
onDestroy: "gpii.contextManager.stopTemporalEnvironmentReporter"
}
});
/**
* Responsible for weeding out any information from the request body that is not strictly
* environmental variables and then notifying the applier.
*
* @param that {Component} The context manager component
* @param body {Object} the body of the request. It is expected to contain only key-value pairs
* of environmental variables, and optionally a 'timestamp' key (and associated value) which
* will not be considered part of the current context
**/
gpii.contextManager.updateCurrentEnvironment = function (that, body) {
var newEnvironment = fluid.censorKeys(body, ["timestamp"]);
fluid.log("contextManager: Updating environment with: " + JSON.stringify(newEnvironment, null, 4));
that.applier.change("currentEnvironment", newEnvironment);
};
/**
* Function to start the "Temporal reporter". Will report timerelated contexts every 20 seconds.
* It reports both the timestamp and timeOfDay context variables via manual calls to the
* gpii.contextManager.environmentChanged function
*
* TODO: The timezone here is retrieved based on the system time - the geographical location
* of the user is not taken into account (unless the OS does so by default) - see GPII-1105
*
* @param {Component} The context manager object
**/
gpii.contextManager.startTemporalEnvironmentReporter = function (that) {
that.temporalIntervalID = setInterval(function () {
var date = new Date();
gpii.contextManager.updateCurrentEnvironment(that, {
// ms since UTC epoch, eg: 1421066816897
"http://registry.gpii.net/common/environment/timestamp": date.getTime(),
// time of day, eg: "18:30"
"http://registry.gpii.net/common/environment/timeOfDay": date.getHours() + ":" + date.getMinutes()
});
}, 20000); // report time every 20 seconds
};
/**
* Triggered on destruction of the context manager component. Stops the temporal reporters
* time reports
*
* @param that {Component} The context manager object
**/
gpii.contextManager.stopTemporalEnvironmentReporter = function (that) {
clearInterval(that.temporalIntervalID);
};
/**
* This function is listening to the changeApplier of the contextManager ("currentEnvironment" path)
*
* On changes to the currentEnvironment part of the model it will:
* 1) parse the context against the currently logged in user's conditions
* 2) if the new calculated active context is different from the currently applied context,
* the LifecycleManager's update functionality will be called and the applied context and
* configuration will be stored in the active session of the lifecycle manager.
*
* @param that {Object} The context manager object
* @param lifecycleManager {Component} The lifecycleManager component from which the current
* session will be retrieved
* @param forceContextName {String} [optional] A context name to be unconditionally selected
* @return {Promise} If the context change causes an actual change in the system (i.e. change of
* active context and changes to the applied configuration) a promise of an update to the system
* configuration is returned. If the call to contextChanged does not result in a different
* active context or no user is logged in, undefined will be returned.
**/
gpii.contextManager.contextChanged = function (that, lifecycleManager, forceContextName) {
// find logged in users
var activeSessions = lifecycleManager.getActiveSessionTokens();
// if noone is logged in, do nothing
if (activeSessions.length === 0) {
return;
}
var activeSession = lifecycleManager.getSession(activeSessions);
var fullPayload = fluid.extend(true, {}, activeSession.model);
var oldActiveContext = fullPayload.activeContextName;
// find and update context:
that.updateActiveContextName(fullPayload, forceContextName);
Iif (oldActiveContext === fullPayload.activeContextName) {
fluid.log("contextManager: Same context as before (" + oldActiveContext + ") so doing nothing");
return;
}
fluid.log("contextManager: New active contexts: " + fullPayload.activeContextName);
// Update the context name immediately so we don't produce a model update from the lifecycleManager with a
// partially applied set of solutions from the new context, confusing, e.g., the PCPChannel
// This is required because of https://issues.fluidproject.org/browse/FLUID-6208
activeSession.applier.change("activeContextName", fullPayload.activeContextName);
lifecycleManager.addLifecycleInstructionsToPayload(fullPayload);
var response = lifecycleManager.update(fullPayload);
response.then(function () {
// Delete these this to avoid the 'lifecycleInstructions' block being attached
// to the sessionState in the next line. LifecycleInstructions is only used in the
// "finalPayload" as instructions on how to configure the system. once configured,
// the applied settings are stored in 'activeConfiguration'
delete fullPayload.lifecycleInstructions;
// TODO: This is pretty rubbish, why isn't the natural action of lifecycleManager.update good enough here?
activeSession.applier.change("", fullPayload); // TODO: this will need to be a "MERGE" in time
fluid.log("contextManager: Successfully updated configuration triggered by context changes");
}, function () {
fluid.log(fluid.logLevel.ERROR, "contextManager: Failed to apply newly evaluated conditions");
});
return response;
};
/**
* Function to add the current context to the full payload which is output from the matchmaking process.
*
* @param that {Component} - gpii.contextManager component
* @param fullPayload {Object} - the full structure output from the matchmaking process. NOTE: This object will be modified
* @param forceContextName {String} [optional] - A context name to be selected, overriding environment conditions
* @return {Object} - the modified fullPayload, now including activeContextName, activeConfiguration
*/
gpii.contextManager.updateActiveContextName = function (that, fullPayload, forceContextName) {
var matchMakerOutput = fullPayload.matchMakerOutput;
var newActiveContextName;
if (forceContextName === undefined) {
Iif (fullPayload.forcedContext) { // Don't compute conditions if context has been forced
newActiveContextName = fullPayload.activeContextName;
} else {
var activeContexts = gpii.contextManager.utils.findActiveContexts(fluid.get(that.model, "currentEnvironment"), matchMakerOutput);
// save highest priority context as the applied one
newActiveContextName = activeContexts[0];
}
} else {
newActiveContextName = forceContextName;
fullPayload.forcedContext = true;
}
fullPayload.activeContextName = newActiveContextName;
return fullPayload;
};
|