69098ec2daa96bdc8ca35bc318579b49e4d2196a
[osm/UI.git] / skyquake / plugins / composer / src / src / libraries / model / DescriptorModelMetaFactory.js
1 /**
2 * Created by onvelocity on 1/27/16.
3 *
4 * This class provides methods to get the metadata about descriptor models.
5 */
6
7 import _cloneDeep from 'lodash/cloneDeep'
8 import _isEmpty from 'lodash/isEmpty'
9 import _pick from 'lodash/pick'
10 import _get from 'lodash/get'
11 import _set from 'lodash/set'
12 import DescriptorModelMetaProperty from './DescriptorModelMetaProperty'
13 import CommonUtils from 'utils/utils';
14 const assign = Object.assign;
15
16 const exportInnerTypesMap = {
17 'constituent-vnfd': 'nsd.constituent-vnfd',
18 'vdu': 'vnfd.vdu'
19 };
20
21 function getPathForType(type) {
22 if (exportInnerTypesMap[type]) {
23 return exportInnerTypesMap[type];
24 }
25 return type;
26 }
27
28 const uiStateToSave = ['containerPositionMap'];
29
30 //////
31 // Data serialization will be done on a meta model basis. That is,
32 // given a schema and data, retrieve from the data only that which is
33 // defined by the schema.
34 //
35
36 // serialize data for a list of properties
37 function serializeAll(properties, data) {
38 if (data) {
39 return properties.reduce((obj, p) => {
40 return Object.assign(obj, p.serialize(data));
41 }, {});
42 }
43 return null;
44 }
45
46 function serialize_container(data) {
47 data = data[this.name];
48 if (_isEmpty(data)) {
49 return null;
50 }
51 let obj = {};
52 obj[this.name] = serializeAll(this.properties, data);
53 return obj;
54 }
55
56 function serialize_list(data) {
57 data = data[this.name];
58 if (data) {
59 if (!Array.isArray(data)) {
60 return serializeAll(this.properties, data);
61 } else if (data.length) {
62 let list = data.reduce((c, d) => {
63 let obj = serializeAll(this.properties, d);
64 if (!_isEmpty(obj)) {
65 c.push(obj);
66 }
67 return c;
68 }, []);
69 if (!_isEmpty(list)){
70 let obj = {};
71 obj[this.name] = list;
72 return obj;
73 }
74 }
75 }
76 return null;
77 }
78
79 function serialize_leaf(data) {
80 let value = data[this.name];
81 if (value === null || typeof value === 'undefined' || value === '') {
82 return null;
83 }
84 let obj = {};
85 if (this['data-type'] === 'empty') {
86 value = ''; // empty string does get sent as value
87 }
88 obj[this.name] = value;
89 return obj;
90 }
91
92 function serialize_leaf_empty(data) {
93 let value = data[this.name];
94 if (value) {
95 let obj = {};
96 obj[this.name] = "";
97 return obj;
98 }
99 return null;
100 }
101
102 function serialize_leaf_list(data) {
103 data = data[this.name];
104 if (data) {
105 commaSeparatedValues = data.reduce((d, v) => {
106 let leaf = Serializer.leaf.call(this, d);
107 let value = leaf & leaf[this.name];
108 if (value && value.length) {
109 if (v.length) {
110 v += ', ';
111 }
112 v += value;
113 }
114 }, "");
115 if (commaSeparatedValues.length) {
116 let obj = {};
117 obj[this.name] = commaSeparatedValues;
118 return obj;
119 }
120 }
121 return null;
122 }
123
124 function serialize_choice(data) {
125 let keys = Object.keys(data);
126 if (keys) {
127 const chosen = this.properties.find(
128 c => c.type === 'case' && c.properties && c.properties.some(p => keys.indexOf(p.name) > -1));
129 return chosen && serializeAll(chosen.properties, data);
130 }
131 return null;
132 }
133
134 function serialize_case(data) {
135 return Serializer.container.call(this, data);
136 }
137
138 // special ui data handler for leaf of type string named 'meta'
139 function serialize_meta(data) {
140 let uiState = data['uiState'];
141 let meta = uiState && _pick(uiState, uiStateToSave);
142 // if there is no uiState to save perhaps this was not a ui state property
143 return _isEmpty(meta) ? null : {meta: JSON.stringify(meta)};
144 }
145
146 function serialize_unsupported(data) {
147 console.error('unsupported property', property);
148 return null;
149 }
150
151 function getSerializer(property) {
152 switch (property.name) {
153 case 'rw-nsd:meta':
154 case 'rw-vnfd:meta':
155 return serialize_meta.bind(property);
156 }
157 switch (property.type) {
158 case 'list':
159 return serialize_list.bind(property);
160 case 'container':
161 return serialize_container.bind(property);
162 case 'choice':
163 return serialize_choice.bind(property);
164 case 'case':
165 return serialize_case.bind(property);
166 case 'leaf_list':
167 return serialize_leaf_list.bind(property);
168 case 'leaf':
169 switch (property['data-type']){
170 case 'empty':
171 return serialize_leaf_empty.bind(property);
172 }
173 return serialize_leaf.bind(property);
174 }
175 return serialize_unsupported.bind(property);
176 }
177
178 let modelMetaByPropertyNameMap = [];
179
180 let cachedDescriptorModelMetaRequest = null;
181
182 export default {
183 init() {
184 if (!cachedDescriptorModelMetaRequest) {
185 cachedDescriptorModelMetaRequest = new Promise(function (resolve, reject) {
186 CommonUtils.getDescriptorModelMeta().then(function (data) {
187 let DescriptorModelMetaJSON = data;
188 modelMetaByPropertyNameMap = Object.keys(DescriptorModelMetaJSON).reduce((map, key) => {
189 function mapProperties(parentMap, parentObj) {
190 // let's beef up the meta info with a helper (more to come?)
191 parentObj.serialize = getSerializer(parentObj);
192 parentMap[':meta'] = parentObj;
193 const properties = parentObj && parentObj.properties ? parentObj.properties : [];
194 properties.forEach(p => {
195 parentMap[p.name] = mapProperties({}, assign(p, {
196 ':qualified-type': parentObj[':qualified-type'] + '.' + p.name
197 }));
198 return map;
199 }, parentMap);
200 return parentMap;
201 }
202 map[key] = mapProperties({}, assign(DescriptorModelMetaJSON[key], {
203 ':qualified-type': key
204 }));
205 return map;
206 }, {});
207
208 (() => {
209 // initialize the UI centric properties that CONFD could care less about
210 _set(modelMetaByPropertyNameMap, 'nsd.meta.:meta.preserve-line-breaks', true);
211 _set(modelMetaByPropertyNameMap, 'vnfd.meta.:meta.preserve-line-breaks', true);
212 _set(modelMetaByPropertyNameMap, 'vnfd.vdu.cloud-init.:meta.preserve-line-breaks', true);
213 _set(modelMetaByPropertyNameMap, 'nsd.constituent-vnfd.vnf-configuration.config-template.:meta.preserve-line-breaks', true);
214 })();
215 resolve();
216 }, function (error) {
217 cachedDescriptorModelMetaRequest = null;
218 })
219 })
220 }
221
222 return cachedDescriptorModelMetaRequest;
223 },
224 /**
225 * Create a new instance of the indicated property and, if relevent, use the given
226 * unique name for the instance's key (see generateItemUniqueName)
227 *
228 * @param {Object | string} typeOrPath a property definition object or a path to a property
229 * @param [{string}] uniqueName optional
230 * @returns
231 */
232 createModelInstanceForType(typeOrPath, uniqueName) {
233 const modelMeta = this.getModelMetaForType(typeOrPath);
234 return DescriptorModelMetaProperty.createModelInstance(modelMeta, uniqueName);
235 },
236 getModelMetaForType(typeOrPath, filterProperties = () => true) {
237 // resolve paths like 'nsd' or 'vnfd.vdu' or 'nsd.constituent-vnfd'
238 const found = _get(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
239 if (found) {
240 const uiState = _cloneDeep(found[':meta']);
241 uiState.properties = uiState.properties.filter(filterProperties);
242 return uiState;
243 }
244 console.warn('no model uiState found for type', typeOrPath);
245 },
246 getModelFieldNamesForType(typeOrPath) {
247 // resolve paths like 'nsd' or 'vnfd.vdu' or 'nsd.constituent-vnfd'
248 const found = _get(modelMetaByPropertyNameMap, getPathForType(typeOrPath));
249 if (found) {
250 let result = [];
251 found[':meta'].properties.map((p) => {
252 // if(false) {
253 if (p.type == 'choice') {
254 result.push(p.name)
255 return p.properties.map(function (q) {
256 result.push(q.properties[0].name);
257 })
258
259 } else {
260 return result.push(p.name);
261 }
262 })
263 return result;
264 }
265 console.warn('no model uiState found for type', typeOrPath);
266 },
267 /**
268 * For a list with a single valued key that is of type string, generate a unique name
269 * for a new entry to be added to the indicated list. This name will use the provided
270 * prefix (or the list's name) followed by a number. The number will be based on the
271 * current length of the array but will insure there is no collision with an existing
272 * name.
273 *
274 * @param {Array} list the list model data
275 * @param {prooerty} property the schema definition of the list
276 * @param [{any} prefix] the perferred prefix for the name. If not provide property.name
277 * will be used.
278 * @returns {string}
279 */
280 generateItemUniqueName(list, property, prefix) {
281 if (property.type !== 'list' ||
282 property.key.length !== 1 ||
283 property.properties.find(prop => prop.name === property.key[0])['data-type'] !== 'string') {
284 // only support list with a single key of type string
285 return null;
286 }
287 if (!prefix) {
288 prefix = property.name;
289 }
290 let key = property.key[0];
291 let suffix = list ? list.length + 1 : 1
292 let keyValue = prefix + '-' + suffix;
293
294 function makeUniqueName() {
295 if (list) {
296 for (let i = 0; i < list.length; i = ++i) {
297 if (list[i][key] === keyValue) {
298 keyValue = keyValue + '-' + (i + 1);
299 makeUniqueName(); // not worried about recursing too deep (chances ??)
300 break;
301 }
302 }
303 }
304 }
305 makeUniqueName();
306 return keyValue;
307 }
308
309 }