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