[ONOS-7732] Automating switch workflow - checking workflow definitition

Change-Id: I66b3bcd43377869b82be5bb7a446152857344355
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java
index ca0570f..b1acf7f 100644
--- a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkflowManager.java
@@ -21,8 +21,10 @@
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.JsonNodeType;
 import com.fasterxml.jackson.databind.node.MissingNode;
+import com.google.common.base.MoreObjects;
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.workflow.api.WorkflowDefinitionException;
 import org.onosproject.workflow.api.WorkflowService;
 import org.onosproject.workflow.api.WorkflowExecutionService;
 import org.onosproject.workflow.api.WorkplaceStore;
@@ -49,7 +51,9 @@
 import java.lang.reflect.Field;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.regex.Matcher;
@@ -95,6 +99,12 @@
     }
 
     @Override
+    public void register(Workflow workflow) throws WorkflowException {
+        checkWorkflow(workflow);
+        workflowStore.register(workflow);
+    }
+
+    @Override
     public void createWorkplace(WorkplaceDescription wpDesc) throws WorkflowException {
         log.info("createWorkplace: {}", wpDesc);
 
@@ -142,7 +152,7 @@
             throw new WorkflowException("Invalid Workflow");
         }
 
-        checkWorkflowSchema(workflow, worklowDescJson);
+        checkWorkflowDataModelSchema(workflow, worklowDescJson);
 
         Workflow wfCreationWf = workflowStore.get(URI.create(WorkplaceWorkflow.WF_CREATE_WORKFLOW));
         if (Objects.isNull(wfCreationWf)) {
@@ -154,13 +164,129 @@
     }
 
     /**
-     * Checks if the type of worklet is same as that of wfdesc Json.
+     * Checks the validity of workflow definition.
+     * @param workflow workflow to be checked
+     * @throws WorkflowException workflow exception
+     */
+    private void checkWorkflow(Workflow workflow) throws WorkflowException {
+
+        Map<String, WorkletDataModelFieldDesc> descMap = new HashMap<>();
+
+        List<String> errors = new ArrayList<>();
+
+        for (String workletType : workflow.getWorkletTypeList()) {
+
+            Worklet worklet = workflow.getWorkletInstance(workletType);
+            if (Worklet.Common.COMPLETED.equals(worklet) || Worklet.Common.INIT.equals(worklet)) {
+                continue;
+            }
+
+            Class cls = worklet.getClass();
+            for (Field field : cls.getDeclaredFields()) {
+
+                if (field.isSynthetic()) {
+                    continue;
+                }
+
+                for (Annotation annotation : field.getAnnotations()) {
+
+                    if (!(annotation instanceof JsonDataModel)) {
+                        continue;
+                    }
+
+                    JsonDataModel jsonDataModel = (JsonDataModel) annotation;
+                    Matcher matcher = Pattern.compile("(\\w+)").matcher(jsonDataModel.path());
+                    if (!matcher.find()) {
+                        throw new WorkflowException(
+                                "Invalid Json Data Model Path(" + jsonDataModel.path() + ") in " + worklet.tag());
+                    }
+                    String path = matcher.group(1);
+
+                    WorkletDataModelFieldDesc desc =
+                            new WorkletDataModelFieldDesc(workletType, path, field.getType(), jsonDataModel.optional());
+
+                    WorkletDataModelFieldDesc existing = descMap.get(path);
+
+                    if (Objects.isNull(existing)) {
+                        descMap.put(path, desc);
+                    } else {
+                        if (!desc.hasSameAttributes(existing)) {
+                            errors.add("" + desc + " is conflicted with " + existing + " in workflow " + workflow.id());
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!errors.isEmpty()) {
+            throw new WorkflowDefinitionException(workflow.id(), errors);
+        }
+    }
+
+    /**
+     * Description of worklet data model field.
+     */
+    private static class WorkletDataModelFieldDesc {
+
+        private final String workletType;
+
+        private final String path;
+
+        private final Class type;
+
+        private final boolean optional;
+
+        /**
+         * Constructor of worklet data model field description.
+         * @param workletType worklet type
+         * @param path path of data model
+         * @param type type of data model
+         * @param optional optional
+         */
+        public WorkletDataModelFieldDesc(String workletType, String path, Class type, boolean optional) {
+            this.workletType = workletType;
+            this.path = path;
+            this.type = type;
+            this.optional = optional;
+        }
+
+        /**
+         * Checks the attributes of worklet data model field.
+         * @param desc worklet data model description
+         * @return true means that this worklet data model field description has same attributes with desc
+         */
+        public boolean hasSameAttributes(WorkletDataModelFieldDesc desc) {
+
+            if (!Objects.equals(type, desc.type)) {
+                return false;
+            }
+
+            if (!Objects.equals(optional, desc.optional)) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(getClass())
+                    .add("worklet", workletType)
+                    .add("path", path)
+                    .add("type", type)
+                    .add("optional", optional)
+                    .toString();
+        }
+    }
+
+    /**
+     * Checks the schema of workflow data.
      *
      * @param workflow workflow
      * @param worklowDescJson jsonNode
      * @throws WorkflowException workflow exception
      */
-    private void checkWorkflowSchema(Workflow workflow, JsonNode worklowDescJson) throws WorkflowException {
+    private void checkWorkflowDataModelSchema(Workflow workflow, JsonNode worklowDescJson) throws WorkflowException {
 
         List<String> errors = new ArrayList<>();
 
@@ -186,23 +312,25 @@
 
                 for (Annotation annotation : field.getAnnotations()) {
 
-                    if (annotation instanceof JsonDataModel) {
-
-                        JsonDataModel jsonDataModel = (JsonDataModel) annotation;
-                        Matcher matcher = Pattern.compile("(\\w+)").matcher(jsonDataModel.path());
-                        if (!matcher.find()) {
-                            throw new WorkflowException(
-                                    "Invalid Json Data Model Path(" + jsonDataModel.path() + ") in " + worklet.tag());
-                        }
-                        String path = matcher.group(1);
-
-                        Optional<String> optError =
-                                getJsonNodeDataError(dataNode, worklet, field, path, jsonDataModel.optional());
-
-                        if (optError.isPresent()) {
-                            errors.add(optError.get());
-                        }
+                    if (!(annotation instanceof JsonDataModel)) {
+                        continue;
                     }
+
+                    JsonDataModel jsonDataModel = (JsonDataModel) annotation;
+                    Matcher matcher = Pattern.compile("(\\w+)").matcher(jsonDataModel.path());
+                    if (!matcher.find()) {
+                        throw new WorkflowException(
+                                "Invalid Json Data Model Path(" + jsonDataModel.path() + ") in " + worklet.tag());
+                    }
+                    String path = matcher.group(1);
+
+                    Optional<String> optError =
+                            getJsonNodeDataError(dataNode, worklet, field, path, jsonDataModel.optional());
+
+                    if (optError.isPresent()) {
+                        errors.add(optError.get());
+                    }
+
                 }
             }
         }