blob: d76aa52289a56db5626997c0e69dd1abc21c1f38 [file] [log] [blame]
jaegonkime0f45b52018-10-09 20:23:26 +09001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.workflow.api;
17
18import com.fasterxml.jackson.databind.JsonNode;
19import com.fasterxml.jackson.databind.node.ArrayNode;
20import com.fasterxml.jackson.databind.node.BooleanNode;
21import com.fasterxml.jackson.databind.node.IntNode;
22import com.fasterxml.jackson.databind.node.MissingNode;
23import com.fasterxml.jackson.databind.node.ObjectNode;
24import com.fasterxml.jackson.databind.node.TextNode;
25import org.slf4j.Logger;
26import org.slf4j.LoggerFactory;
27
28import java.lang.annotation.Annotation;
29import java.lang.reflect.Field;
30import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34import java.util.Objects;
35
36/**
37 * Class for injecting json data model on the work-let execution context.
38 */
39public class JsonDataModelInjector {
40
41 private static final Logger log = LoggerFactory.getLogger(JsonDataModelInjector.class);
42
43 /**
44 * Injects data model to work-let.
45 * @param worklet work-let to be injected
46 * @param context workflow context
47 * @throws WorkflowException workflow exception
48 */
49 public void inject(Worklet worklet, WorkflowContext context) throws WorkflowException {
50
51 handle(worklet, context, this::injectModel);
52 }
53
54 /**
55 * Inhales data model from work-let.
56 * @param worklet work-let to be inhaled
57 * @param context workflow context
58 * @throws WorkflowException workflow exception
59 */
60 public void inhale(Worklet worklet, WorkflowContext context) throws WorkflowException {
61
62 handle(worklet, context, this::inhaleModel);
63 }
64
65 private void handle(Worklet worklet, WorkflowContext context, DataModelFieldBehavior func)
66 throws WorkflowException {
67 Class cl = worklet.getClass();
68 List<Field> fields = getInheritedFields(cl);
69 if (Objects.isNull(fields)) {
70 log.error("Invalid fields on {}", cl);
71 return;
72 }
73
74 for (Field field: fields) {
75 Annotation[] annotations = field.getAnnotations();
76 if (Objects.isNull(annotations)) {
77 continue;
78 }
79 for (Annotation annotation: annotations) {
80 if (!(annotation instanceof JsonDataModel)) {
81 continue;
82 }
83 JsonDataModel model = (JsonDataModel) annotation;
84 func.apply(worklet, context, field, model);
85 }
86 }
87 }
88
89 private static List<Field> getInheritedFields(Class<?> type) {
90 List<Field> fields = new ArrayList<Field>();
91
92 Class<?> cl = type;
93 while (cl != null && cl != Object.class) {
94 for (Field field : cl.getDeclaredFields()) {
95 if (!field.isSynthetic()) {
96 fields.add(field);
97 }
98 }
99 cl = cl.getSuperclass();
100 }
101 return fields;
102 }
103
104 /**
105 * Functional interface for json data model annotated field behavior.
106 */
107 @FunctionalInterface
108 public interface DataModelFieldBehavior {
109 void apply(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
110 throws WorkflowException;
111 }
112
113 private static Map<Class, DataModelFieldBehavior> injectTypeMap = new HashMap<>();
114 static {
115 injectTypeMap.put(String.class, JsonDataModelInjector::injectText);
116 injectTypeMap.put(Integer.class, JsonDataModelInjector::injectInteger);
117 injectTypeMap.put(Boolean.class, JsonDataModelInjector::injectBoolean);
118 injectTypeMap.put(JsonNode.class, JsonDataModelInjector::injectJsonNode);
119 injectTypeMap.put(ArrayNode.class, JsonDataModelInjector::injectArrayNode);
120 injectTypeMap.put(ObjectNode.class, JsonDataModelInjector::injectObjectNode);
121 }
122
123 /**
124 * Injects data model on the filed of work-let.
125 * @param worklet work-let
126 * @param context workflow context
127 * @param field the field of work-let
128 * @param model data model for the field
129 * @throws WorkflowException workflow exception
130 */
131 private void injectModel(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
132 throws WorkflowException {
133
134 DataModelFieldBehavior behavior = injectTypeMap.get(model.type());
135 if (Objects.isNull(behavior)) {
136 throw new WorkflowException("Not supported type(" + model.type() + ")");
137 }
138 behavior.apply(worklet, context, field, model);
139 }
140
141 /**
142 * Injects text data model on the filed of work-let.
143 * @param worklet work-let
144 * @param context workflow context
145 * @param field the field of work-let
146 * @param model text data model for the field
147 * @throws WorkflowException workflow exception
148 */
149 private static void injectText(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
150 throws WorkflowException {
151
152 String text = ((JsonDataModelTree) context.data()).textAt(model.path());
153 if (Objects.isNull(text)) {
154 if (model.optional()) {
155 return;
156 }
157 throw new WorkflowException("Invalid text data model on (" + model.path() + ")");
158 }
159
160 if (!(Objects.equals(field.getType(), String.class))) {
161 throw new WorkflowException("Target field (" + field + ") is not String");
162 }
163
164 try {
165 field.setAccessible(true);
166 field.set(worklet, text);
167 } catch (IllegalAccessException e) {
168 throw new WorkflowException(e);
169 }
170 }
171
172 /**
173 * Injects integer data model on the filed of work-let.
174 * @param worklet work-let
175 * @param context workflow context
176 * @param field the field of work-let
177 * @param model integer data model for the field
178 * @throws WorkflowException workflow exception
179 */
180 private static void injectInteger(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
181 throws WorkflowException {
182
183 Integer number = ((JsonDataModelTree) context.data()).intAt(model.path());
184 if (Objects.isNull(number)) {
185 if (model.optional()) {
186 return;
187 }
188 throw new WorkflowException("Invalid number data model on (" + model.path() + ")");
189 }
190
191 if (!(Objects.equals(field.getType(), Integer.class))) {
192 throw new WorkflowException("Target field (" + field + ") is not Integer");
193 }
194
195 try {
196 field.setAccessible(true);
197 field.set(worklet, number);
198 } catch (IllegalAccessException e) {
199 throw new WorkflowException(e);
200 }
201 }
202
203 /**
204 * Injects boolean data model on the filed of work-let.
205 * @param worklet work-let
206 * @param context workflow context
207 * @param field the field of work-let
208 * @param model boolean data model for the field
209 * @throws WorkflowException workflow exception
210 */
211 private static void injectBoolean(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
212 throws WorkflowException {
213
214 Boolean bool = ((JsonDataModelTree) context.data()).booleanAt(model.path());
215 if (Objects.isNull(bool)) {
216 if (model.optional()) {
217 return;
218 }
219 throw new WorkflowException("Invalid boolean data model on (" + model.path() + ")");
220 }
221
222 if (!(Objects.equals(field.getType(), Boolean.class))) {
223 throw new WorkflowException("Target field (" + field + ") is not Boolean");
224 }
225
226 try {
227 field.setAccessible(true);
228 field.set(worklet, bool);
229 } catch (IllegalAccessException e) {
230 throw new WorkflowException(e);
231 }
232 }
233
234 /**
235 * Injects json node data model on the filed of work-let.
236 * @param worklet work-let
237 * @param context workflow context
238 * @param field the field of work-let
239 * @param model json node data model for the field
240 * @throws WorkflowException workflow exception
241 */
242 private static void injectJsonNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
243 throws WorkflowException {
244
245 JsonNode jsonNode = ((JsonDataModelTree) context.data()).nodeAt(model.path());
246 if (Objects.isNull(jsonNode)) {
247 if (model.optional()) {
248 return;
249 }
250 throw new WorkflowException("Invalid json node data model on (" + model.path() + ")");
251 }
252
253 if (!(Objects.equals(field.getType(), JsonNode.class))) {
254 throw new WorkflowException("Target field (" + field + ") is not JsonNode");
255 }
256
257 try {
258 field.setAccessible(true);
259 field.set(worklet, jsonNode);
260 } catch (IllegalAccessException e) {
261 throw new WorkflowException(e);
262 }
263 }
264
265 /**
266 * Injects json array node data model on the filed of work-let.
267 * @param worklet work-let
268 * @param context workflow context
269 * @param field the field of work-let
270 * @param model json array node data model for the field
271 * @throws WorkflowException workflow exception
272 */
273 private static void injectArrayNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
274 throws WorkflowException {
275
276 ArrayNode arrayNode = ((JsonDataModelTree) context.data()).arrayAt(model.path());
277 if (Objects.isNull(arrayNode)) {
278 if (model.optional()) {
279 return;
280 }
281 throw new WorkflowException("Invalid array node data model on (" + model.path() + ")");
282 }
283
284 if (!(Objects.equals(field.getType(), ArrayNode.class))) {
285 throw new WorkflowException("Target field (" + field + ") is not ArrayNode");
286 }
287
288 try {
289 field.setAccessible(true);
290 field.set(worklet, arrayNode);
291 } catch (IllegalAccessException e) {
292 throw new WorkflowException(e);
293 }
294 }
295
296 /**
297 * Injects json object node data model on the filed of work-let.
298 * @param worklet work-let
299 * @param context workflow context
300 * @param field the field of work-let
301 * @param model json object node data model for the field
302 * @throws WorkflowException workflow exception
303 */
304 private static void injectObjectNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
305 throws WorkflowException {
306
307 ObjectNode objNode = ((JsonDataModelTree) context.data()).objectAt(model.path());
308 if (Objects.isNull(objNode)) {
309 if (model.optional()) {
310 return;
311 }
312 throw new WorkflowException("Invalid object node data model on (" + model.path() + ")");
313 }
314
315 if (!(Objects.equals(field.getType(), ObjectNode.class))) {
316 throw new WorkflowException("Target field (" + field + ") is not ObjectNode");
317 }
318
319 try {
320 field.setAccessible(true);
321 field.set(worklet, objNode);
322 } catch (IllegalAccessException e) {
323 throw new WorkflowException(e);
324 }
325 }
326
327 private static Map<Class, DataModelFieldBehavior> inhaleTypeMap = new HashMap<>();
328 static {
329 inhaleTypeMap.put(String.class, JsonDataModelInjector::inhaleText);
330 inhaleTypeMap.put(Integer.class, JsonDataModelInjector::inhaleInteger);
331 inhaleTypeMap.put(Boolean.class, JsonDataModelInjector::inhaleBoolean);
332 inhaleTypeMap.put(JsonNode.class, JsonDataModelInjector::inhaleJsonNode);
333 inhaleTypeMap.put(ArrayNode.class, JsonDataModelInjector::inhaleArrayNode);
334 inhaleTypeMap.put(ObjectNode.class, JsonDataModelInjector::inhaleObjectNode);
335 }
336
337 /**
338 * Inhales data model on the filed of work-let.
339 * @param worklet work-let
340 * @param context workflow context
341 * @param field the field of work-let
342 * @param model data model for the field
343 * @throws WorkflowException workflow exception
344 */
345 private void inhaleModel(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
346 throws WorkflowException {
347
348 DataModelFieldBehavior behavior = inhaleTypeMap.get(model.type());
349 if (Objects.isNull(behavior)) {
350 throw new WorkflowException("Not supported type(" + model.type() + ")");
351 }
352 behavior.apply(worklet, context, field, model);
353 }
354
355 /**
356 * Inhales text data model on the filed of work-let.
357 * @param worklet work-let
358 * @param context workflow context
359 * @param field the field of work-let
360 * @param model text data model for the field
361 * @throws WorkflowException workflow exception
362 */
363 private static void inhaleText(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
364 throws WorkflowException {
365
366 if (!(Objects.equals(field.getType(), String.class))) {
367 throw new WorkflowException("Target field (" + field + ") is not String");
368 }
369
370 String text;
371 try {
372 field.setAccessible(true);
373 text = (String) field.get(worklet);
374 } catch (IllegalAccessException e) {
375 throw new WorkflowException(e);
376 }
377
378 if (Objects.isNull(text)) {
379 return;
380 }
381
382 JsonDataModelTree tree = (JsonDataModelTree) context.data();
383 JsonNode jsonNode = tree.nodeAt(model.path());
384
385 if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
386 tree.setAt(model.path(), text);
387 } else if (!(jsonNode instanceof TextNode)) {
388 throw new WorkflowException("Invalid text data model on (" + model.path() + ")");
389 } else {
390 tree.remove(model.path());
391 tree.setAt(model.path(), text);
392 }
393 }
394
395 /**
396 * Inhales integer data model on the filed of work-let.
397 * @param worklet work-let
398 * @param context workflow context
399 * @param field the field of work-let
400 * @param model integer data model for the field
401 * @throws WorkflowException workflow exception
402 */
403 private static void inhaleInteger(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
404 throws WorkflowException {
405
406 if (!(Objects.equals(field.getType(), Integer.class))) {
407 throw new WorkflowException("Target field (" + field + ") is not Integer");
408 }
409
410 Integer number;
411 try {
412 field.setAccessible(true);
413 number = (Integer) field.get(worklet);
414 } catch (IllegalAccessException e) {
415 throw new WorkflowException(e);
416 }
417
418 if (Objects.isNull(number)) {
419 return;
420 }
421
422 JsonDataModelTree tree = (JsonDataModelTree) context.data();
423 JsonNode jsonNode = tree.nodeAt(model.path());
424
425 if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
426 tree.setAt(model.path(), number);
427 } else if (!(jsonNode instanceof IntNode)) {
428 throw new WorkflowException("Invalid integer data model on (" + model.path() + ")");
429 } else {
430 tree.remove(model.path());
431 tree.setAt(model.path(), number);
432 }
433 }
434
435 /**
436 * Inhales boolean data model on the filed of work-let.
437 * @param worklet work-let
438 * @param context workflow context
439 * @param field the field of work-let
440 * @param model boolean data model for the field
441 * @throws WorkflowException workflow exception
442 */
443 private static void inhaleBoolean(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
444 throws WorkflowException {
445
446 if (!(Objects.equals(field.getType(), Boolean.class))) {
447 throw new WorkflowException("Target field (" + field + ") is not Boolean");
448 }
449
450 Boolean bool;
451 try {
452 field.setAccessible(true);
453 bool = (Boolean) field.get(worklet);
454 } catch (IllegalAccessException e) {
455 throw new WorkflowException(e);
456 }
457
458 if (Objects.isNull(bool)) {
459 return;
460 }
461
462 JsonDataModelTree tree = (JsonDataModelTree) context.data();
463 JsonNode jsonNode = tree.nodeAt(model.path());
464
465 if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
466 tree.setAt(model.path(), bool);
467 } else if (!(jsonNode instanceof BooleanNode)) {
468 throw new WorkflowException("Invalid boolean data model on (" + model.path() + ")");
469 } else {
470 tree.remove(model.path());
471 tree.setAt(model.path(), bool);
472 }
473 }
474
475 /**
476 * Inhales json node data model on the filed of work-let.
477 * @param worklet work-let
478 * @param context workflow context
479 * @param field the field of work-let
480 * @param model json node data model for the field
481 * @throws WorkflowException workflow exception
482 */
483 private static void inhaleJsonNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
484 throws WorkflowException {
485
486 if (!(Objects.equals(field.getType(), JsonNode.class))) {
487 throw new WorkflowException("Target field (" + field + ") is not JsonNode");
488 }
489
490 JsonNode tgtJsonNode;
491 try {
492 field.setAccessible(true);
493 tgtJsonNode = (JsonNode) field.get(worklet);
494 } catch (IllegalAccessException e) {
495 throw new WorkflowException(e);
496 }
497
498 if (Objects.isNull(tgtJsonNode)) {
499 return;
500 }
501
502 JsonDataModelTree tree = (JsonDataModelTree) context.data();
503 JsonNode jsonNode = tree.nodeAt(model.path());
504
505 if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
506 tree.attach(model.path(), new JsonDataModelTree(tgtJsonNode));
507 } else if (!(jsonNode instanceof JsonNode)) {
508 throw new WorkflowException("Invalid json node data model on (" + model.path() + ")");
509 } else {
510 // do nothing
511 }
512 }
513
514 /**
515 * Inhales json array node data model on the filed of work-let.
516 * @param worklet work-let
517 * @param context workflow context
518 * @param field the field of work-let
519 * @param model json array node data model for the field
520 * @throws WorkflowException workflow exception
521 */
522 private static void inhaleArrayNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
523 throws WorkflowException {
524 if (!(Objects.equals(field.getType(), ArrayNode.class))) {
525 throw new WorkflowException("Target field (" + field + ") is not ArrayNode");
526 }
527
528 ArrayNode tgtArrayNode;
529 try {
530 field.setAccessible(true);
531 tgtArrayNode = (ArrayNode) field.get(worklet);
532 } catch (IllegalAccessException e) {
533 throw new WorkflowException(e);
534 }
535
536 if (Objects.isNull(tgtArrayNode)) {
537 return;
538 }
539
540 JsonDataModelTree tree = (JsonDataModelTree) context.data();
541 JsonNode jsonNode = tree.nodeAt(model.path());
542
543 if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
544 tree.attach(model.path(), new JsonDataModelTree(tgtArrayNode));
545 } else if (!(jsonNode instanceof ArrayNode)) {
546 throw new WorkflowException("Invalid array node data model on (" + model.path() + ")");
547 } else {
548 // do nothing
549 }
550 }
551
552 /**
553 * Inhales json object node data model on the filed of work-let.
554 * @param worklet work-let
555 * @param context workflow context
556 * @param field the field of work-let
557 * @param model json object node data model for the field
558 * @throws WorkflowException workflow exception
559 */
560 private static void inhaleObjectNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
561 throws WorkflowException {
562 if (!(Objects.equals(field.getType(), ObjectNode.class))) {
563 throw new WorkflowException("Target field (" + field + ") is not ObjectNode");
564 }
565
566 ObjectNode tgtObjNode;
567 try {
568 field.setAccessible(true);
569 tgtObjNode = (ObjectNode) field.get(worklet);
570 } catch (IllegalAccessException e) {
571 throw new WorkflowException(e);
572 }
573
574 if (Objects.isNull(tgtObjNode)) {
575 return;
576 }
577
578 JsonDataModelTree tree = (JsonDataModelTree) context.data();
579 JsonNode jsonNode = tree.nodeAt(model.path());
580
581 if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
582 tree.attach(model.path(), new JsonDataModelTree(tgtObjNode));
583 } else if (!(jsonNode instanceof ObjectNode)) {
584 throw new WorkflowException("Invalid object node data model on (" + model.path() + ")");
585 } else {
586 // do nothing
587 }
588 }
589}