-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathBaseMCWSPersistenceProvider.js
More file actions
249 lines (213 loc) · 8.35 KB
/
BaseMCWSPersistenceProvider.js
File metadata and controls
249 lines (213 loc) · 8.35 KB
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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
import mcws from '../services/mcws/mcws.js';
import { createModelFromNamespaceDefinitionWithPersisted, interpolateUsername } from './utils.js';
const USERNAME_FROM_PATH_REGEX = new RegExp('([^/]*)$');
/**
* An object defining a MCWS namespace. Provides a unique identifier for a
* MCWS namespace and all the information necessary to access it.
*
* @typedef {Object} NamespaceDefinition
* @property {Boolean} containsNamespaces true if this namespace has
* sub-namespaces.
* @property {string} id an persistence identifier for the namespace root.
* Should include a unique persistence space prefix.
* @property {string} key the machine-readable persistence space identifier
* for this namespace.
* @property {string} name the human readable name of the namespace.
* @property {string} url the url to the MCWS namespace.
* @property {NamespaceTemplate} childTemplate template for contained
* namespaces, required if `containsNamespaces` is true.
*/
/**
* An object defining a user root namespace. Allows fetching of child
* namespaces and defines format for creating new user namespaces.
*
* @typedef {Object} NamespaceTemplate
* @param {String} id id template string for child namespaces.
* @param {String} key key template string for child namespaces.
* @param {String} name name template string for child namespaces.
* @param {String} url url template string for child namespaces.
*/
/**
* Provides persistence for objects (e.g. domainObject models)
* utilizing MCWS namespaces.
*
* @param { module:openmct } openmct
* @param { Array.<module:openmct.ObjectAPI~Identifier> } roots
*/
export default class BaseMCWSPersistenceProvider {
constructor(openmct, roots, allowedNamespaceKeys, invalidNamespaceKeys) {
this.openmct = openmct;
this.roots = roots;
this.allowedNamespaceKeys = allowedNamespaceKeys;
this.invalidNamespaceKeys = invalidNamespaceKeys;
}
// Abstract method for get, to be implemented by subclasses
async get(identifier, abortSignal) {
throw new Error('Method not implemented');
}
/**
* Return any namespace utilized by persistence. This includes all root
* namespaces and any namespaces they contain.
*
* @returns {Promise.<NamespaceDefinition[]>} persistenceNamespaces
*/
async getPersistenceNamespaces() {
// Return cached result if available
if (this.persistenceNamespaces) {
return this.persistenceNamespaces;
}
// If initialization is in progress, wait for it
if (!this.persistenceNamespacesPromise) {
this.persistenceNamespacesPromise = (async () => {
const rootNamespaces = await this.getRootNamespaces();
const allContainedNamespaces = await this.getAllContainedNamespaces(rootNamespaces);
this.persistenceNamespaces = [...rootNamespaces, ...allContainedNamespaces];
delete this.persistenceNamespacesPromise;
return this.persistenceNamespaces;
})();
}
return this.persistenceNamespacesPromise;
}
/**
* Return all namespaces contained in a given array of namespaces.
*
* @private
* @param {NamespaceDefinition[]} namespaceDefinitions
* @returns {Promise.<NamespaceDefinition[]>}
* containedNamespaceDefinitions an array of all contained namespaces.
*/
async getAllContainedNamespaces(namespaceDefinitions) {
const containingNamespaces = namespaceDefinitions.filter((definition) => {
return definition.containsNamespaces === true;
});
const containedNamespaces = await Promise.all(
containingNamespaces.map(this.getContainedNamespaces.bind(this))
);
return containedNamespaces.flat();
}
/**
* Returns namespace definitions for all namespaces contained in a given
* namespace. Additionally, creates a contained namespace for the current
* user if one does not already exist.
*
* @param {NamespaceDefinition} namespaceDefinition.
* @returns {NamespaceDefinition[]} containedNamespaces.
*/
async getContainedNamespaces(namespaceDefinition) {
if (!namespaceDefinition?.containsNamespaces) {
return [];
}
const namespaceTemplate = structuredClone(namespaceDefinition.childTemplate);
namespaceTemplate.location = namespaceDefinition.id;
const user = await this.openmct.user.getCurrentUser();
const containedNamespaces = await this.getNamespacesFromMCWS(namespaceDefinition);
const userNamespace = interpolateUsername(namespaceTemplate, user.id, user.name);
const existingUserNamespace = containedNamespaces.find(
(namespace) => namespace.url === userNamespace.url
);
if (existingUserNamespace) {
containedNamespaces.splice(containedNamespaces.indexOf(existingUserNamespace), 1);
containedNamespaces.unshift(userNamespace);
return containedNamespaces;
}
containedNamespaces.unshift(userNamespace);
await this.createIfMissing(userNamespace, user.id);
return containedNamespaces;
}
/**
* Read a namespace from MCWS and translate contained namespace objects into
* namespace definitions.
*
* @private
* @param {NamespaceDefinition} namespaceDefinition namespace to read
* @returns {Promise.<NamespaceDefinition[]>} containedNamespaceDefinitions
*/
async getNamespacesFromMCWS(namespaceDefinition) {
const namespaceContents = await mcws.namespace(namespaceDefinition.url).read();
const namespaces = namespaceContents.filter((item) => item.object === 'namespace');
const templateObject = namespaceDefinition.childTemplate;
const userNamespaces = namespaces.map((namespace) => {
const username = USERNAME_FROM_PATH_REGEX.exec(namespace.subject)[1];
const userNamespaceDefinition = interpolateUsername(templateObject, username);
userNamespaceDefinition.location = namespaceDefinition.id;
return userNamespaceDefinition;
});
return userNamespaces;
}
/**
* Get namespace definitions by taking defined roots and substituting user
* fields. Creates namespaces for definitions that are missing, and returns
* a promise for an array of namespace definitions.
*
* @returns {Promise.<NamespaceDefinition[]>}
*/
async getRootNamespaces() {
const user = await this.openmct.user.getCurrentUser();
let rootNamespaces = await Promise.all(
this.roots.map((rootNamespace) => this.createIfMissing(rootNamespace, user.id))
);
rootNamespaces = rootNamespaces.filter(Boolean);
return this.filterNamespacesByPath(rootNamespaces);
}
/**
* Check if a namespace exists, and if it does not exist, create it.
* Returns a promise that is resolved with the namespaceDefinition.
* If there is an error accessing or creating the namespace, the promise is
* resolved with `undefined`.
*
* @private
* @param {NamespaceDefinition} namespaceDefinition
* @param {string} userId the user ID
* @returns {Promise.<NamespaceDefinition>|Promise.<undefined>}
*/
async createIfMissing(namespaceDefinition, userId) {
const namespace = mcws.namespace(namespaceDefinition.url);
try {
await namespace.read();
return namespaceDefinition;
} catch (readError) {
if (readError.status === 404) {
try {
await namespace.create();
if (!namespaceDefinition.id.endsWith('container')) {
const model = createModelFromNamespaceDefinitionWithPersisted(
userId,
namespaceDefinition,
[]
);
await this.create(model);
}
return namespaceDefinition;
} catch (e) {
console.error('Error creating namespace:', e);
return;
}
} else {
throw readError;
}
}
}
/**
* Filters a list of namespaces, returning only the namespaces that are
* valid for a given path.
*
* @private
* @param {NamespaceDefinition[]} namespaceDefinitions
* @returns {NamespaceDefinition[]} validNamespaces
*/
filterNamespacesByPath(namespaceDefinitions) {
const FILTER_CRITERIA = {
'/mcws/clients/vista-ammos': 'ammos',
'/mcws/clients/vista-msl': 'msl',
'/mcws/clients/vista-smap': 'smap'
};
Object.entries(FILTER_CRITERIA).forEach(([path, includeTerm]) => {
if (window.location.pathname.startsWith(path)) {
namespaceDefinitions = namespaceDefinitions.filter((definition) =>
definition.key.startsWith(includeTerm)
);
}
});
return namespaceDefinitions;
}
}