workflow app fixes

Change-Id: Ia3b06b7947671e4e38ca37eb832e6a969ed1e6af
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
index e948a7a..02a7836 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ImmutableListWorkflow.java
@@ -55,6 +55,7 @@
 
     /**
      * Constructor of ImmutableListWorkflow.
+     *
      * @param builder builder of ImmutableListWorkflow
      */
     private ImmutableListWorkflow(Builder builder) {
@@ -148,7 +149,7 @@
 
         WorkflowStore store;
         try {
-             store = DefaultServiceDirectory.getService(WorkflowStore.class);
+            store = DefaultServiceDirectory.getService(WorkflowStore.class);
         } catch (ServiceNotFoundException e) {
             throw new WorkflowException(e);
         }
@@ -179,8 +180,14 @@
         return ImmutableSet.copyOf(attributes);
     }
 
+    @Override
+    public List<String> getWorkletTypeList() {
+        return ImmutableList.copyOf(workletTypeList);
+    }
+
     /**
      * Gets index of class in worklet type list.
+     *
      * @param aClass class to get index
      * @return index of class in worklet type list
      */
@@ -195,6 +202,7 @@
 
     /**
      * Checks whether class is allowed class or not.
+     *
      * @param clazz class to check
      * @return Check result
      */
@@ -245,6 +253,7 @@
 
     /**
      * Gets a instance of builder.
+     *
      * @return instance of builder
      */
     public static Builder builder() {
@@ -263,6 +272,7 @@
 
         /**
          * Sets id of immutable list workflow.
+         *
          * @param uri id of immutable list workflow
          * @return builder
          */
@@ -274,6 +284,7 @@
 
         /**
          * Sets init worklet class name of immutable list workflow.
+         *
          * @param workletClassName class name of worklet
          * @return builder
          */
@@ -284,6 +295,7 @@
 
         /**
          * Chains worklet class name of immutable list workflow.
+         *
          * @param workletClassName class name of worklet
          * @return builder
          */
@@ -294,6 +306,7 @@
 
         /**
          * Adds workflow attribute.
+         *
          * @param attribute workflow attribute to be added
          * @return builder
          */
@@ -304,6 +317,7 @@
 
         /**
          * Builds ImmutableListWorkflow.
+         *
          * @return instance of ImmutableListWorkflow
          */
         public ImmutableListWorkflow build() {
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java
index 96e623f..848a706 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Workflow.java
@@ -16,6 +16,7 @@
 package org.onosproject.workflow.api;
 
 import java.net.URI;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -84,4 +85,10 @@
      * @return attributes
      */
     Set<WorkflowAttribute> attributes();
+
+    /**
+     * Returns worklet type list.
+     * @return worklet type
+     */
+    List<String> getWorkletTypeList();
 }
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java
new file mode 100644
index 0000000..9227092
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowDataModelException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.workflow.api;
+
+
+import java.util.Map;
+
+/**
+ * Workflow DataModel exception class.
+ */
+public class WorkflowDataModelException extends WorkflowException {
+
+    private String workflowName;
+    private Map<String, Map<String, String>> errorListMap;
+
+
+    /**
+     * Constructor for Workflow DataModel Exception.
+     *
+     * @param msg exception message
+     */
+    public WorkflowDataModelException(String msg) {
+
+        super(msg);
+    }
+
+    /**
+     * Constructor for Workflow DataModel Exception.
+     *
+     * @param msg          exception message
+     * @param workflowName workflow name
+     * @param errorListMap throwable to deliver
+     */
+    public WorkflowDataModelException(String msg, String workflowName, Map<String, Map<String, String>> errorListMap) {
+        super(msg);
+        this.workflowName = workflowName;
+        this.errorListMap = errorListMap;
+    }
+
+    @Override
+    public String toString() {
+        return "WorkflowDataModelException{" +
+                "workflowName='" + workflowName + '\'' +
+                ", errorListMap=" + errorListMap.toString() +
+                '}';
+    }
+
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java
index 941ba70..381b2d5 100644
--- a/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/cli/WorkFlowTestCommand.java
@@ -37,15 +37,16 @@
 public class WorkFlowTestCommand extends AbstractShellCommand {
 
     static final String INVOKE_SAMPLE = "invoke-sample";
+    static final String EXCEPTION_SAMPLE = "exception-sample";
 
     @Argument(index = 0, name = "test-name",
-            description = "Test name (" + INVOKE_SAMPLE + ")",
+            description = "Test name (" + INVOKE_SAMPLE + " | " + EXCEPTION_SAMPLE + ")",
             required = true)
     @Completion(WorkFlowTestCompleter.class)
     private String testName = null;
 
     @Argument(index = 1, name = "arg1",
-            description = "number of test for " + INVOKE_SAMPLE,
+            description = "number of test for (" + INVOKE_SAMPLE + " | " + EXCEPTION_SAMPLE + ")",
             required = false)
     private String arg1 = null;
 
@@ -76,6 +77,26 @@
 
                 invokeSampleTest(num);
                 break;
+
+            case EXCEPTION_SAMPLE:
+                if (Objects.isNull(arg1)) {
+                    error("arg1 is required for test " + EXCEPTION_SAMPLE);
+                    return;
+                }
+                int count;
+                try {
+                    count = Integer.parseInt(arg1);
+                } catch (NumberFormatException e) {
+                    error("arg1 should be an integer value");
+                    return;
+                } catch (Exception e) {
+                    error(e.getMessage() + ", trace: " + Arrays.asList(e.getStackTrace()));
+                    return;
+                }
+
+                invokeExceptionTest(count);
+                break;
+
             default:
                 print("Unsupported test-name: " + testName);
         }
@@ -83,6 +104,7 @@
 
     /**
      * Workflow invoke test_name.
+     *
      * @param num the arg1 of workflow to test_name
      */
     private void invokeSampleTest(int num) {
@@ -95,8 +117,22 @@
     }
 
     /**
+     * Workflow datatype exception test.
+     *
+     * @param num the number of workflow to test
+     */
+    private void invokeExceptionTest(int num) {
+        for (int i = 0; i <= num; i++) {
+            String wpName = "test-" + i;
+            invoke("sample.workflow-3", wpName);
+        }
+    }
+
+
+    /**
      * Invokes workflow.
-     * @param workflowId workflow id
+     *
+     * @param workflowId    workflow id
      * @param workplaceName workplace name
      */
     private void invoke(String workflowId, String workplaceName) {
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 5866932..8eb7c0c 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
@@ -15,22 +15,27 @@
  */
 package org.onosproject.workflow.impl;
 
+
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.JsonNodeType;
 import org.onosproject.net.config.NetworkConfigRegistry;
 import org.onosproject.net.config.NetworkConfigService;
-import org.onosproject.workflow.api.DefaultWorkplace;
-import org.onosproject.workflow.api.JsonDataModelTree;
-import org.onosproject.workflow.api.Workflow;
-import org.onosproject.workflow.api.WorkflowContext;
-import org.onosproject.workflow.api.WorkflowDescription;
-import org.onosproject.workflow.api.WorkflowException;
 import org.onosproject.workflow.api.WorkflowService;
 import org.onosproject.workflow.api.WorkflowExecutionService;
-import org.onosproject.workflow.api.WorkflowStore;
-import org.onosproject.workflow.api.Workplace;
-import org.onosproject.workflow.api.WorkplaceDescription;
 import org.onosproject.workflow.api.WorkplaceStore;
+import org.onosproject.workflow.api.WorkflowStore;
+import org.onosproject.workflow.api.WorkplaceDescription;
+import org.onosproject.workflow.api.WorkflowException;
+import org.onosproject.workflow.api.DefaultWorkplace;
+import org.onosproject.workflow.api.JsonDataModelTree;
+import org.onosproject.workflow.api.WorkflowDescription;
+import org.onosproject.workflow.api.Workplace;
+import org.onosproject.workflow.api.WorkflowDataModelException;
+import org.onosproject.workflow.api.Workflow;
+import org.onosproject.workflow.api.Worklet;
+import org.onosproject.workflow.api.WorkflowContext;
+import org.onosproject.workflow.api.JsonDataModel;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -38,8 +43,15 @@
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.slf4j.Logger;
 
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
 import java.net.URI;
+import java.util.Map;
+import java.util.HashMap;
 import java.util.Objects;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -118,21 +130,121 @@
     @Override
     public void invokeWorkflow(JsonNode worklowDescJson) throws WorkflowException {
         log.info("invokeWorkflow: {}", worklowDescJson);
-
         Workplace workplace = workplaceStore.getWorkplace(Workplace.SYSTEM_WORKPLACE);
         if (Objects.isNull(workplace)) {
             throw new WorkflowException("Invalid system workplace");
         }
 
-        Workflow workflow = workflowStore.get(URI.create(WorkplaceWorkflow.WF_CREATE_WORKFLOW));
+        Workflow workflow = workflowStore.get(URI.create(worklowDescJson.get("id").asText()));
         if (Objects.isNull(workflow)) {
+            throw new WorkflowException("Invalid Workflow");
+        }
+
+        if (!checkWorkflowSchema(workflow, worklowDescJson)) {
+            throw new WorkflowException("Invalid Workflow " + worklowDescJson.get("id").asText());
+        }
+
+        Workflow wfCreationWf = workflowStore.get(URI.create(WorkplaceWorkflow.WF_CREATE_WORKFLOW));
+        if (Objects.isNull(wfCreationWf)) {
             throw new WorkflowException("Invalid workflow " + WorkplaceWorkflow.WF_CREATE_WORKFLOW);
         }
 
-        WorkflowContext context = workflow.buildSystemContext(workplace, new JsonDataModelTree(worklowDescJson));
+        WorkflowContext context = wfCreationWf.buildSystemContext(workplace, new JsonDataModelTree(worklowDescJson));
         workflowExecutionService.execInitWorklet(context);
     }
 
+    /**
+     * Checks if the type of worklet is same as that of wfdesc Json.
+     *
+     * @param workflow workflow
+     * @param jsonNode jsonNode
+     * @throws WorkflowException workflow exception
+     */
+
+    private boolean checkWorkflowSchema(Workflow workflow, JsonNode jsonNode) throws WorkflowException {
+
+        Map<String, Map<String, String>> workletDataTypeMap = new HashMap<>();
+        for (String workletType : workflow.getWorkletTypeList()) {
+            Map<String, String> jsonDataModelMap = new HashMap<>();
+            if (Objects.equals(workletType, Worklet.Common.INIT.tag())
+                    || (Objects.equals(workletType, Worklet.Common.COMPLETED.tag()))) {
+                continue;
+            }
+            Worklet worklet = workflow.getWorkletInstance(workletType);
+            Class cls = worklet.getClass();
+            for (Field field : cls.getDeclaredFields()) {
+                if (field.isSynthetic()) {
+                    continue;
+                }
+                Annotation[] annotations = field.getAnnotations();
+                for (Annotation annotation : annotations) {
+                    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");
+                        }
+                        String path = matcher.group(1);
+                        if (checkJsonNodeDataType(jsonNode, field, path)) {
+                            jsonDataModelMap.put(path, field.getType().getName());
+                        }
+                    }
+                }
+            }
+            if (!jsonDataModelMap.isEmpty()) {
+                workletDataTypeMap.put(worklet.tag(), jsonDataModelMap);
+            }
+
+        }
+        if (!workletDataTypeMap.isEmpty()) {
+            throw new WorkflowDataModelException("invalid workflow ", workflow.id().toString(), workletDataTypeMap);
+        }
+        return true;
+    }
+
+
+    private boolean checkJsonNodeDataType(JsonNode jsonNode, Field field, String path) throws WorkflowException {
+        if (!Objects.nonNull(jsonNode.get("data")) && !Objects.nonNull(jsonNode.get("data").get(path))) {
+            throw new WorkflowException("Invalid Json");
+        }
+        JsonNodeType jsonNodeType = jsonNode.get("data").get(path).getNodeType();
+        if (jsonNodeType != null) {
+            switch (jsonNodeType) {
+                case NUMBER:
+                    if (!(field.getType().isAssignableFrom(Integer.class))) {
+                        return true;
+                    }
+                    break;
+                case STRING:
+                    if (!(field.getType().isAssignableFrom(String.class))) {
+                        return true;
+                    }
+                    break;
+                case OBJECT:
+                    if (!(field.getType().isAssignableFrom(Objects.class))) {
+                        return true;
+                    }
+                    break;
+                case BOOLEAN:
+                    if (!(field.getType().isAssignableFrom(Boolean.class))) {
+                        return true;
+                    }
+                    break;
+                case ARRAY:
+                    if (!(field.getType().isAssignableFrom(Arrays.class))) {
+                        return true;
+                    }
+                    break;
+                default:
+                    return true;
+            }
+        } else {
+            return false;
+
+        }
+        return false;
+    }
+
     @Override
     public void terminateWorkflow(WorkflowDescription wfDesc) throws WorkflowException {
         log.info("terminateWorkflow: {}", wfDesc);
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java
index 252c17d..2a9b9f6 100644
--- a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/example/SampleWorkflow.java
@@ -116,6 +116,15 @@
                 .chain(SampleWorklet5.class.getName())
                 .build();
         workflowStore.register(workflow);
+
+        // registering new workflow definition
+        uri = URI.create("sample.workflow-3");
+        workflow = ImmutableListWorkflow.builder()
+                .id(uri)
+                .chain(SampleWorklet6.class.getName())
+                .build();
+        workflowStore.register(workflow);
+
     }
 
     /**
@@ -308,4 +317,29 @@
         }
     }
 
+    /**
+     * Class for sample worklet-6 to test workflow datamodel exception.
+     */
+    public static class SampleWorklet6 extends AbsSampleWorklet {
+
+        @JsonDataModel(path = MODEL_COUNT)
+        String str;
+
+        @Override
+        public void process(WorkflowContext context) throws WorkflowException {
+            ObjectNode node = getDataModel(context);
+            node.put("work6", "done");
+            log.info("workflow-process {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+            sleep(10);
+            context.completed();
+        }
+
+        @Override
+        public boolean isNext(WorkflowContext context) throws WorkflowException {
+            ObjectNode node = allocOrGetModel(context);
+            log.info("workflow-isNext {}-{}", context.workplaceName(), this.getClass().getSimpleName());
+            sleep(10);
+            return !node.has("work6");
+        }
+    }
 }