Implement logger for workflows

Added Workflow Logger annotation and Log Store

Change-Id: I90b1e34f94083aceff3323660a708756096a0ef0
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 5819f11..1a77e25 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
@@ -56,6 +56,11 @@
     private static StaticDataModelInjector staticDataModelInjector = new StaticDataModelInjector();
 
     /**
+     * Workflow Logger injector.
+     */
+    private WorkflowLoggerInjector workflowLoggerInjector = new WorkflowLoggerInjector();
+
+    /**
      * Constructor of ImmutableListWorkflow.
      *
      * @param builder builder of ImmutableListWorkflow
@@ -118,6 +123,7 @@
                 continue;
 
             } else {
+                workflowLoggerInjector.inject(worklet, context);
                 // isNext is read only. It does not perform 'inhale'.
                 dataModelInjector.inject(worklet, context);
                 WorkletDescription workletDesc = getWorkletDesc(pc);
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogStore.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogStore.java
new file mode 100644
index 0000000..cd61da9
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogStore.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022-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.List;
+import java.util.Map;
+
+public interface WorkflowLogStore {
+
+    /**
+     * Adds log messages to store.
+     *
+     * @param contextName context name
+     * @param logMsg log message
+     * @param className class name
+     * @param level logging level
+     */
+    void addLog(String contextName, String logMsg, String className, String level);
+
+    /**
+     * Adds exception message and call stack to store.
+     *
+     * @param contextName context name
+     * @param logMsg log message
+     * @param className class name
+     * @param level logging level
+     * @param e Throwable object
+     */
+    void addException(String contextName, String logMsg, String className, String level, Throwable e);
+
+    /**
+     * Gets log messages from store.
+     *
+     * @param contextName context name
+     * @return log messages of given contextName
+     */
+    List<String> getLog(String contextName);
+
+
+    /**
+     * Get store as Java map.
+     *
+     * @return Store as Java hash map.
+     */
+    Map<String, List<String>> asJavaMap();
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogger.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogger.java
new file mode 100644
index 0000000..8622244
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLogger.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022-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.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+
+/*
+  Annotation for injecting logger on work-let execution context.
+ */
+public @interface WorkflowLogger {
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerFactory.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerFactory.java
new file mode 100644
index 0000000..06ca7a2
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerFactory.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2022-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 org.onlab.osgi.DefaultServiceDirectory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
+
+public class WorkflowLoggerFactory {
+    private static final String INFO = "INFO";
+    private static final String ERROR = "ERROR";
+    private static final String WARN = "WARN";
+
+    protected Logger log;
+    protected String contextName;
+    protected String className;
+    protected WorkflowLogStore logStore = DefaultServiceDirectory.getService(WorkflowLogStore.class);
+
+    public WorkflowLoggerFactory(String ctxtName, String cls) {
+        log = LoggerFactory.getLogger(cls);
+        contextName = ctxtName;
+        className = cls;
+    }
+
+    public void debug(String msg) {
+        log.debug(msg);
+    }
+
+    public void debug(String format, Object arg) {
+        log.debug(format, arg);
+    }
+
+    public void debug(String format, Object arg1, Object arg2) {
+        log.debug(format, arg1, arg2);
+    }
+
+    public void debug(String format, Object... arg) {
+        log.debug(format, arg);
+    }
+
+    public void debug(String format, Throwable e) {
+        log.debug(format, e);
+    }
+
+    public void info(String msg) {
+        logStore.addLog(contextName, msg, className, INFO);
+        log.info(msg);
+    }
+
+    public void info(String format, Object arg) {
+        String msg = MessageFormatter.format(format, arg).getMessage();
+        logStore.addLog(contextName, msg, className, INFO);
+        log.info(format, arg);
+    }
+
+    public void info(String format, Object arg1, Object arg2) {
+        String msg = MessageFormatter.format(format, arg1, arg2).getMessage();
+        logStore.addLog(contextName, msg, className, INFO);
+        log.info(format, arg1, arg2);
+    }
+
+    public void info(String format, Object... arg) {
+        String msg = MessageFormatter.arrayFormat(format, arg).getMessage();
+        logStore.addLog(contextName, msg, className, INFO);
+        log.info(format, arg);
+    }
+
+    public void info(String format, Throwable e) {
+        logStore.addException(contextName, format, className, INFO, e);
+        log.info(format, e);
+    }
+
+    public void error(String msg) {
+        logStore.addLog(contextName, msg, className, ERROR);
+        log.error(msg);
+    }
+
+    public void error(String format, Object arg) {
+        String msg = MessageFormatter.format(format, arg).getMessage();
+        logStore.addLog(contextName, msg, className, ERROR);
+        log.error(format, arg);
+    }
+
+    public void error(String format, Object arg1, Object arg2) {
+        String msg = MessageFormatter.format(format, arg1, arg2).getMessage();
+        logStore.addLog(contextName, msg, className, ERROR);
+        log.error(format, arg1, arg2);
+    }
+
+    public void error(String format, Object... arg) {
+        String msg = MessageFormatter.arrayFormat(format, arg).getMessage();
+        logStore.addLog(contextName, msg, className, ERROR);
+        log.error(format, arg);
+    }
+
+    public void error(String format, Throwable e) {
+        logStore.addException(contextName, format, className, ERROR, e);
+        log.error(format, e);
+    }
+
+    public void warn(String msg) {
+        logStore.addLog(contextName, msg, className, WARN);
+        log.warn(msg);
+    }
+
+    public void warn(String format, Object arg) {
+        String msg = MessageFormatter.format(format, arg).getMessage();
+        logStore.addLog(contextName, msg, className, WARN);
+        log.warn(format, arg);
+    }
+
+    public void warn(String format, Object arg1, Object arg2) {
+        String msg = MessageFormatter.format(format, arg1, arg2).getMessage();
+        logStore.addLog(contextName, msg, className, WARN);
+        log.warn(format, arg1, arg2);
+    }
+
+    public void warn(String format, Object... arg) {
+        String msg = MessageFormatter.arrayFormat(format, arg).getMessage();
+        logStore.addLog(contextName, msg, className, WARN);
+        log.warn(format, arg);
+    }
+
+    public void warn(String format, Throwable e) {
+        logStore.addException(contextName, format, className, WARN, e);
+        log.warn(format, e);
+    }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerInjector.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerInjector.java
new file mode 100644
index 0000000..5022f96
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowLoggerInjector.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2022-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.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Class for injecting workflow logger on the work-let execution context.
+ */
+public class WorkflowLoggerInjector {
+
+    /**
+     * Injects logger to work-let.
+     *
+     * @param worklet work-let to be injected
+     * @param context workflow context
+     * @throws WorkflowException workflow exception
+     */
+    public void inject(Worklet worklet, WorkflowContext context) throws WorkflowException {
+
+        handle(worklet, context, this::injectLogger);
+    }
+
+
+    private void handle(Worklet worklet, WorkflowContext context, WorkflowLoggerInjector.ReferenceFieldBehavior func)
+            throws WorkflowException {
+        Class<?> cl = worklet.getClass();
+        List<Field> fields = getInheritedFields(cl);
+
+        for (Field field : fields) {
+            Annotation[] annotations = field.getAnnotations();
+            if (Objects.isNull(annotations)) {
+                continue;
+            }
+            for (Annotation annotation : annotations) {
+                if (!(annotation instanceof WorkflowLogger)) {
+                    continue;
+                }
+
+                if (Modifier.isStatic(field.getModifiers())) {
+                    throw new WorkflowException("Static field(" + field + " ) cannot use @WorkflowLogger in " + cl);
+                }
+
+                WorkflowLogger reference = (WorkflowLogger) annotation;
+                func.apply(worklet, context, field, reference);
+            }
+        }
+    }
+
+    private static List<Field> getInheritedFields(Class<?> type) {
+        List<Field> fields = new ArrayList<>();
+
+        Class<?> cl = type;
+        while (cl != null && cl != Object.class) {
+            for (Field field : cl.getDeclaredFields()) {
+                if (!field.isSynthetic()) {
+                    fields.add(field);
+                }
+            }
+            cl = cl.getSuperclass();
+        }
+        return fields;
+    }
+
+    /**
+     * Functional interface for workflow logger annotated field behavior.
+     */
+    @FunctionalInterface
+    public interface ReferenceFieldBehavior {
+        void apply(Worklet worklet, WorkflowContext context, Field field, WorkflowLogger reference)
+                throws WorkflowException;
+    }
+
+    /**
+     * Injects logger on the filed of work-let.
+     *
+     * @param worklet work-let
+     * @param context workflow context
+     * @param field   the field of work-let
+     * @param reference   logger reference for the field
+     * @throws WorkflowException workflow exception
+     */
+    private void injectLogger(Worklet worklet, WorkflowContext context, Field field, WorkflowLogger reference)
+            throws WorkflowException {
+
+        Object obj = new WorkflowLoggerFactory(context.name(), worklet.getClass().getSimpleName());
+
+        try {
+            field.setAccessible(true);
+            field.set(worklet, obj);
+        } catch (IllegalAccessException e) {
+            throw new WorkflowException(e);
+        }
+    }
+
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/SimpleWorkflowLogStore.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/SimpleWorkflowLogStore.java
new file mode 100644
index 0000000..fdc1ab1
--- /dev/null
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/SimpleWorkflowLogStore.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2022-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.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.onosproject.workflow.api.WorkflowLogStore;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+@Component(immediate = true)
+@Service
+public class SimpleWorkflowLogStore implements WorkflowLogStore {
+    private static final int LOG_EXPIRATION_TIME = 60;
+    private static final String SQ_OPBRACKET = "[";
+    private static final String SQ_CLBRACKET = "]";
+    private static final String SPACE = " ";
+
+    private Cache<String, List<String>> workflowLogMap;
+
+    @Activate
+    public void activate() {
+        workflowLogMap = CacheBuilder.newBuilder()
+                .expireAfterWrite(LOG_EXPIRATION_TIME, TimeUnit.MINUTES)
+                .build();
+    }
+
+    @Deactivate
+    public void deactivate() {
+        workflowLogMap.invalidateAll();
+    }
+
+    @Override
+    public void addLog(String uuid, String logMsg, String className, String level) {
+        workflowLogMap.asMap().putIfAbsent(uuid, new ArrayList<>());
+        Objects.requireNonNull(workflowLogMap.getIfPresent(uuid)).add(0, formatLog(logMsg, level, className));
+    }
+
+    @Override
+    public void addException(String uuid, String logMsg, String className, String level, Throwable e) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        e.printStackTrace(pw);
+        String msg = logMsg + sw;
+        addLog(uuid, msg, className, level);
+    }
+
+    @Override
+    public List<String> getLog(String uuid) {
+        List<String> logs = workflowLogMap.getIfPresent(uuid);
+        if (Objects.isNull(logs)) {
+            return new ArrayList<>();
+        }
+        return logs;
+    }
+
+    @Override
+    public Map<String, List<String>> asJavaMap() {
+        return workflowLogMap.asMap();
+    }
+
+    private String formatLog(String msg, String level, String className) {
+        Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+        msg = SQ_OPBRACKET + timestamp + SQ_CLBRACKET + SPACE + SQ_OPBRACKET + level + SQ_CLBRACKET + SPACE +
+                SQ_OPBRACKET + className + SQ_CLBRACKET + SPACE + msg;
+        return msg;
+    }
+}
diff --git a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
index 55bf0e4..8b99bb8 100644
--- a/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
+++ b/apps/workflow/app/src/main/java/org/onosproject/workflow/impl/WorkFlowEngine.java
@@ -51,6 +51,7 @@
 import org.onosproject.workflow.api.WorkflowExecutionService;
 import org.onosproject.workflow.api.WorkletDescription;
 import org.onosproject.workflow.api.StaticDataModelInjector;
+import org.onosproject.workflow.api.WorkflowLoggerInjector;
 import org.onosproject.event.AbstractListenerManager;
 import org.onosproject.event.Event;
 import org.onosproject.net.intent.WorkPartitionService;
@@ -123,6 +124,7 @@
     private static final int DEFAULT_WORKFLOW_THREADS = 12;
     private static final int DEFAULT_EVENTTASK_THREADS = 12;
     private static final int MAX_REGISTER_EVENTMAP_WAITS = 10;
+    private static final String ERROR = "ERROR";
 
     private ScheduledExecutorService eventMapTriggerExecutor;
 
@@ -130,6 +132,7 @@
 
     private JsonDataModelInjector dataModelInjector = new JsonDataModelInjector();
     private StaticDataModelInjector staticDataModelInjector = new StaticDataModelInjector();
+    private WorkflowLoggerInjector workflowLoggerInjector = new WorkflowLoggerInjector();
 
     public static final String APPID = "org.onosproject.workflow";
     private ApplicationId appId;
@@ -190,6 +193,7 @@
                 log.info("{} worklet.process:{}", context.name(), initWorklet.tag());
                 log.trace("{} context: {}", context.name(), context);
 
+                workflowLoggerInjector.inject(initWorklet, context);
                 dataModelInjector.inject(initWorklet, context);
                 initWorklet.process(context);
                 dataModelInjector.inhale(initWorklet, context);
@@ -496,6 +500,7 @@
             log.info("{} worklet.isCompleted:{}", latestContext.name(), worklet.tag());
             log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
 
+            workflowLoggerInjector.inject(worklet, latestContext);
             dataModelInjector.inject(worklet, latestContext);
             boolean completed = worklet.isCompleted(latestContext, task.event());
             dataModelInjector.inhale(worklet, latestContext);
@@ -589,6 +594,7 @@
             log.info("{} worklet.timeout(for event):{}", latestContext.name(), worklet.tag());
             log.trace("{} task:{}, context: {}", latestContext.name(), task, latestContext);
 
+            workflowLoggerInjector.inject(worklet, latestContext);
             dataModelInjector.inject(worklet, latestContext);
 
             WorkletDescription workletDesc = workflow.getWorkletDesc(task.programCounter());
@@ -666,6 +672,7 @@
             log.info("{} worklet.timeout:{}", latestContext.name(), worklet.tag());
             log.trace("{} context: {}", latestContext.name(), latestContext);
 
+            workflowLoggerInjector.inject(worklet, latestContext);
             dataModelInjector.inject(worklet, latestContext);
 
             WorkletDescription workletDesc = workflow.getWorkletDesc(task.programCounter());
@@ -793,6 +800,7 @@
             log.trace("{} context: {}", latestContext.name(), latestContext);
 
 
+            workflowLoggerInjector.inject(worklet, latestContext);
             dataModelInjector.inject(worklet, latestContext);
 
             WorkletDescription workletDesc = workflow.getWorkletDesc(pc);