[ONOS-7732] Automating switch workflow: annotation based data model injection, applying programming counter, and small fixes
Change-Id: I4092d9c2695bcc8c4e8e01d54c442d3fac284eb6
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java
index 8a5f2b8..5fb8943 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/AbstractWorkflow.java
@@ -42,11 +42,19 @@
@Override
public WorkflowContext buildContext(Workplace workplace, DataModelTree data) throws WorkflowException {
- return new DefaultWorkflowContext(id, workplace.name(), data);
+ return DefaultWorkflowContext.builder()
+ .workflowId(id)
+ .workplaceName(workplace.name())
+ .data(data)
+ .build();
}
@Override
public WorkflowContext buildSystemContext(Workplace workplace, DataModelTree data) throws WorkflowException {
- return new SystemWorkflowContext(id, workplace.name(), data);
+ return SystemWorkflowContext.systemBuilder()
+ .workflowId(id)
+ .workplaceName(workplace.name())
+ .data(data)
+ .build();
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/CheckCondition.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/CheckCondition.java
new file mode 100644
index 0000000..7419583
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/CheckCondition.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018-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;
+
+
+/**
+ * Static convenience class that help a whether it was invoked correctly(whether its preconditions have been met).
+ * Methods of this class generally accept a boolean expression which is expected to be true.
+ * When false (or null) is passed instead, the Preconditions method throws an workflow exception.
+ */
+public final class CheckCondition {
+
+ /**
+ * Private class of check condition.
+ */
+ private CheckCondition() {
+ }
+
+ /**
+ * Checks the condition, and if it is false, it raises workflow exception with exception message.
+ * @param condition condition to check. A boolean expression is located on here.
+ * @param exceptionMessage exception message for workflow exception
+ * @throws WorkflowException workflow exception
+ */
+ public static void check(boolean condition, String exceptionMessage) throws WorkflowException {
+ if (!condition) {
+ throw new WorkflowException(exceptionMessage);
+ }
+ }
+
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java
index c26b3be..6d0f0fb 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ContextEventMapStore.java
@@ -32,11 +32,11 @@
* @param eventType the class name of event
* @param eventHint event hint string value of the event
* @param contextName workflow context name
- * @param workletType the class name of worklet
+ * @param programCounterString the program counter of workflow
* @throws WorkflowException workflow exception
*/
void registerEventMap(String eventType, String eventHint,
- String contextName, String workletType) throws WorkflowException;
+ String contextName, String programCounterString) throws WorkflowException;
/**
* Unregisters workflow context event mapping.
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
index 3236164..f7ada8d 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DataModelTree.java
@@ -59,5 +59,12 @@
* @throws WorkflowException workflow exception
*/
DataModelTree alloc(String path, Nodetype leaftype) throws WorkflowException;
+
+ /**
+ * Remove node on the path.
+ * @param path data model tree path
+ * @throws WorkflowException workflow exception
+ */
+ void remove(String path) throws WorkflowException;
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java
index 4a4715e..6f9d3c3 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultRpcDescription.java
@@ -20,6 +20,8 @@
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.MoreObjects;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Class of workflow RPC description.
*/
@@ -169,8 +171,12 @@
/**
* Builds workplace RPC description from builder.
* @return instance of workflow RPC description
+ * @throws WorkflowException workflow exception
*/
- public DefaultRpcDescription build() {
+ public DefaultRpcDescription build() throws WorkflowException {
+ check(op != null, "op is invalid");
+ check(params != null, "params is invalid");
+ check(id != null, "id is invalid");
return new DefaultRpcDescription(this);
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java
index e16376f..05c564a 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowContext.java
@@ -16,10 +16,14 @@
package org.onosproject.workflow.api;
import com.google.common.base.MoreObjects;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceNotFoundException;
import org.onosproject.event.Event;
import java.net.URI;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Default implementation of WorkflowContext.
*/
@@ -41,9 +45,9 @@
private WorkflowState state;
/**
- * Current worklet of the workflow.
+ * Current program counter of the workflow.
*/
- private String current;
+ private ProgramCounter current;
/**
* Cause of workflow exception.
@@ -87,16 +91,14 @@
/**
* Constructor of DefaultWorkflowContext.
- * @param workflowId ID of workflow
- * @param workplaceName name of workplace
- * @param data data model tree
+ * @param builder default workflow context builder
*/
- public DefaultWorkflowContext(URI workflowId, String workplaceName, DataModelTree data) {
- super(data);
- this.workflowId = workflowId;
- this.workplaceName = workplaceName;
+ protected DefaultWorkflowContext(Builder builder) {
+ super(builder.data);
+ this.workflowId = builder.workflowId;
+ this.workplaceName = builder.workplaceName;
this.state = WorkflowState.IDLE;
- this.current = Worklet.Common.INIT.name();
+ this.current = ProgramCounter.INIT_PC;
}
/**
@@ -140,13 +142,13 @@
}
@Override
- public String current() {
+ public ProgramCounter current() {
return this.current;
}
@Override
- public void setCurrent(Worklet worklet) {
- this.current = worklet.tag();
+ public void setCurrent(ProgramCounter pc) {
+ this.current = pc;
}
@Override
@@ -228,6 +230,16 @@
return workplaceStore;
}
+ public <T> T getService(Class<T> serviceClass) throws WorkflowException {
+ T service;
+ try {
+ service = DefaultServiceDirectory.getService(serviceClass);
+ } catch (ServiceNotFoundException e) {
+ throw new WorkflowException(e);
+ }
+ return service;
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
@@ -239,4 +251,75 @@
.add("cause", cause())
.toString();
}
+
+ /**
+ * Gets builder instance.
+ * @return builder instance
+ */
+ public static final Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for default workflow context.
+ */
+ public static class Builder {
+
+ /**
+ * ID of workflow.
+ */
+ private URI workflowId;
+
+ /**
+ * Workplace name of the workflow.
+ */
+ private String workplaceName;
+
+ /**
+ * Data model tree.
+ */
+ private DataModelTree data;
+
+ /**
+ * Sets workflow id.
+ * @param workflowId workflow id
+ * @return builder
+ */
+ public Builder workflowId(URI workflowId) {
+ this.workflowId = workflowId;
+ return this;
+ }
+
+ /**
+ * Sets workplace name.
+ * @param workplaceName workplace name
+ * @return builder
+ */
+ public Builder workplaceName(String workplaceName) {
+ this.workplaceName = workplaceName;
+ return this;
+ }
+
+ /**
+ * Sets data model tree.
+ * @param data data model tree
+ * @return builder
+ */
+ public Builder data(DataModelTree data) {
+ this.data = data;
+ return this;
+ }
+
+ /**
+ * Builds default workflow context.
+ * @return instance of default workflow context
+ * @throws WorkflowException workflow exception
+ */
+ public DefaultWorkflowContext build() throws WorkflowException {
+ check(data != null, "Invalid data model tree");
+ check(workflowId != null, "Invalid workflowId");
+ check(workplaceName != null, "Invalid workplaceName");
+ return new DefaultWorkflowContext(this);
+ }
+ }
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java
index c9ded60..13a8f46 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkflowDescription.java
@@ -24,6 +24,8 @@
import java.net.URI;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Class for default workflow description.
@@ -201,8 +203,12 @@
/**
* Builds workflow description from builder.
* @return instance of workflow description
+ * @throws WorkflowException workflow exception
*/
- public DefaultWorkflowDescription build() {
+ public DefaultWorkflowDescription build() throws WorkflowException {
+ check(workplaceName != null, "workplaceName is invalid");
+ check(id != null, "id is invalid");
+ check(data != null, "data is invalid");
return new DefaultWorkflowDescription(this);
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java
index 95da550..e8125f7 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/DefaultWorkplaceDescription.java
@@ -27,6 +27,8 @@
import java.util.List;
import java.util.Optional;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Class for default workplace description.
*/
@@ -161,8 +163,10 @@
/**
* Builds workplace description from builder.
* @return instance of workflow description
+ * @throws WorkflowException workflow exception
*/
- public DefaultWorkplaceDescription build() {
+ public DefaultWorkplaceDescription build() throws WorkflowException {
+ check(name != null, "name is invalid");
return new DefaultWorkplaceDescription(this);
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java
index 3ad1847..076bfc0 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventHintSupplier.java
@@ -22,5 +22,5 @@
*/
@FunctionalInterface
public interface EventHintSupplier {
- String apply(Event event);
+ String apply(Event event) throws Throwable;
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java
index 3b2b30a..c2af3e6 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTask.java
@@ -20,6 +20,8 @@
import java.util.Objects;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Class for event task.
*/
@@ -91,7 +93,7 @@
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("context", context())
- .add("workletType", workletType())
+ .add("programCounter", programCounter())
.add("event", event())
.add("eventHint", eventHint())
.toString();
@@ -147,16 +149,19 @@
}
@Override
- public Builder workletType(String workletType) {
- super.workletType(workletType);
+ public Builder programCounter(ProgramCounter programCounter) {
+ super.programCounter(programCounter);
return this;
}
/**
* Builds EventTask.
* @return instance of EventTask
+ * @throws WorkflowException workflow exception
*/
- public EventTask build() {
+ public EventTask build() throws WorkflowException {
+ check(event != null, "event is invalid");
+ check(eventHint != null, "eventHint is invalid");
return new EventTask(this);
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java
index 92516d4..9aa870d 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/EventTimeoutTask.java
@@ -19,6 +19,8 @@
import java.util.Objects;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Class for event timeout task.
*/
@@ -81,7 +83,7 @@
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("context", context())
- .add("workletType", workletType())
+ .add("programCounter", programCounter())
.add("eventType", eventType())
.add("eventHint", eventHint())
.toString();
@@ -136,16 +138,19 @@
}
@Override
- public Builder workletType(String workletType) {
- super.workletType(workletType);
+ public Builder programCounter(ProgramCounter programCounter) {
+ super.programCounter(programCounter);
return this;
}
/**
* Builds EventTimeoutTask.
* @return instance of EventTimeoutTask
+ * @throws WorkflowException workflow exception
*/
- public EventTimeoutTask build() {
+ public EventTimeoutTask build() throws WorkflowException {
+ check(eventType != null, "eventType is invalid");
+ check(eventHint != null, "eventType is invalid");
return new EventTimeoutTask(this);
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java
index 106466b..94203bc 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/HandlerTask.java
@@ -28,9 +28,9 @@
private final WorkflowContext context;
/**
- * Worklet type of handler task.
+ * Program counter of handler task.
*/
- private final String workletType;
+ private final ProgramCounter programCounter;
/**
* Constructor for handler task.
@@ -38,7 +38,7 @@
*/
protected HandlerTask(Builder builder) {
this.context = builder.context;
- this.workletType = builder.workletType;
+ this.programCounter = builder.programCounter;
}
/**
@@ -50,18 +50,18 @@
}
/**
- * Returns worklet type name of this handler task.
- * @return worklet type
+ * Returns program counter of this handler task.
+ * @return program counter
*/
- public String workletType() {
- return workletType;
+ public ProgramCounter programCounter() {
+ return programCounter;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("context", context())
- .add("workletType", workletType())
+ .add("programCounter", programCounter())
.toString();
}
@@ -70,7 +70,7 @@
*/
public static class Builder {
protected WorkflowContext context;
- protected String workletType;
+ protected ProgramCounter programCounter;
/**
* Sets workflow context of handler task.
@@ -83,12 +83,12 @@
}
/**
- * Sets worklet type of handler task.
- * @param workletType worklet type
+ * Sets program counter of handler task.
+ * @param programCounter program counter of handler type
* @return builder of handler task
*/
- public Builder workletType(String workletType) {
- this.workletType = workletType;
+ public Builder programCounter(ProgramCounter programCounter) {
+ this.programCounter = programCounter;
return this;
}
}
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 b5ea456..e948a7a 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
@@ -29,6 +29,8 @@
import java.util.Objects;
import java.util.Set;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
/**
* Class for immutable list workflow.
*/
@@ -49,6 +51,8 @@
*/
private Set<WorkflowAttribute> attributes;
+ private static JsonDataModelInjector dataModelInjector = new JsonDataModelInjector();
+
/**
* Constructor of ImmutableListWorkflow.
* @param builder builder of ImmutableListWorkflow
@@ -71,10 +75,14 @@
@Override
- public Worklet next(WorkflowContext context) throws WorkflowException {
+ public ProgramCounter next(WorkflowContext context) throws WorkflowException {
int cnt = 0;
- for (int i = 0; i < workletTypeList.size(); i++) {
+
+ ProgramCounter pc = context.current();
+ check(pc != null, "Invalid program counter");
+
+ for (int i = pc.workletIndex(); i < workletTypeList.size(); i++) {
if (cnt++ > Worklet.MAX_WORKS) {
throw new WorkflowException("Maximum worklet execution exceeded");
@@ -83,7 +91,7 @@
String workletType = workletTypeList.get(i);
if (Worklet.Common.COMPLETED.tag().equals(workletType)) {
- return Worklet.Common.COMPLETED;
+ return ProgramCounter.valueOf(workletType, i);
}
if (Worklet.Common.INIT.tag().equals(workletType)) {
@@ -109,17 +117,35 @@
continue;
} else {
+ // isNext is read only. It does not perform 'inhale'.
+ dataModelInjector.inject(worklet, context);
if (worklet.isNext(context)) {
- return worklet;
+ return ProgramCounter.valueOf(workletType, i);
}
}
}
- return Worklet.Common.COMPLETED;
+ throw new WorkflowException("workflow reached to end but not COMPLETED");
+ }
+
+ @Override
+ public ProgramCounter increased(ProgramCounter pc) throws WorkflowException {
+
+ int increaedIndex = pc.workletIndex() + 1;
+ if (increaedIndex >= workletTypeList.size()) {
+ throw new WorkflowException("Out of bound in program counter(" + pc + ")");
+ }
+
+ String workletType = workletTypeList.get(increaedIndex);
+ return ProgramCounter.valueOf(workletType, increaedIndex);
}
@Override
public Worklet getWorkletInstance(String workletType) throws WorkflowException {
+ if (Worklet.Common.COMPLETED.tag().equals(workletType)) {
+ return Worklet.Common.COMPLETED;
+ }
+
WorkflowStore store;
try {
store = DefaultServiceDirectory.getService(WorkflowStore.class);
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModel.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModel.java
new file mode 100644
index 0000000..1dc8e52
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModel.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018-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 com.fasterxml.jackson.databind.JsonNode;
+
+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 json data model on work-let execution context.
+ */
+public @interface JsonDataModel {
+
+ /**
+ * Path of data model.
+ * @return path of data model
+ */
+ String path() default "/";
+
+ /**
+ * Type of data model.
+ * @return type of data model
+ */
+ Class<?> type() default JsonNode.class;
+
+ /**
+ * Representing whether this data model is optional or not.
+ * @return optional or not
+ */
+ boolean optional() default false;
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelInjector.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelInjector.java
new file mode 100644
index 0000000..d76aa52
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelInjector.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright 2018-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 com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.IntNode;
+import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Class for injecting json data model on the work-let execution context.
+ */
+public class JsonDataModelInjector {
+
+ private static final Logger log = LoggerFactory.getLogger(JsonDataModelInjector.class);
+
+ /**
+ * Injects data model 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::injectModel);
+ }
+
+ /**
+ * Inhales data model from work-let.
+ * @param worklet work-let to be inhaled
+ * @param context workflow context
+ * @throws WorkflowException workflow exception
+ */
+ public void inhale(Worklet worklet, WorkflowContext context) throws WorkflowException {
+
+ handle(worklet, context, this::inhaleModel);
+ }
+
+ private void handle(Worklet worklet, WorkflowContext context, DataModelFieldBehavior func)
+ throws WorkflowException {
+ Class cl = worklet.getClass();
+ List<Field> fields = getInheritedFields(cl);
+ if (Objects.isNull(fields)) {
+ log.error("Invalid fields on {}", cl);
+ return;
+ }
+
+ for (Field field: fields) {
+ Annotation[] annotations = field.getAnnotations();
+ if (Objects.isNull(annotations)) {
+ continue;
+ }
+ for (Annotation annotation: annotations) {
+ if (!(annotation instanceof JsonDataModel)) {
+ continue;
+ }
+ JsonDataModel model = (JsonDataModel) annotation;
+ func.apply(worklet, context, field, model);
+ }
+ }
+ }
+
+ private static List<Field> getInheritedFields(Class<?> type) {
+ List<Field> fields = new ArrayList<Field>();
+
+ 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 json data model annotated field behavior.
+ */
+ @FunctionalInterface
+ public interface DataModelFieldBehavior {
+ void apply(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException;
+ }
+
+ private static Map<Class, DataModelFieldBehavior> injectTypeMap = new HashMap<>();
+ static {
+ injectTypeMap.put(String.class, JsonDataModelInjector::injectText);
+ injectTypeMap.put(Integer.class, JsonDataModelInjector::injectInteger);
+ injectTypeMap.put(Boolean.class, JsonDataModelInjector::injectBoolean);
+ injectTypeMap.put(JsonNode.class, JsonDataModelInjector::injectJsonNode);
+ injectTypeMap.put(ArrayNode.class, JsonDataModelInjector::injectArrayNode);
+ injectTypeMap.put(ObjectNode.class, JsonDataModelInjector::injectObjectNode);
+ }
+
+ /**
+ * Injects data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private void injectModel(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ DataModelFieldBehavior behavior = injectTypeMap.get(model.type());
+ if (Objects.isNull(behavior)) {
+ throw new WorkflowException("Not supported type(" + model.type() + ")");
+ }
+ behavior.apply(worklet, context, field, model);
+ }
+
+ /**
+ * Injects text data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model text data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectText(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ String text = ((JsonDataModelTree) context.data()).textAt(model.path());
+ if (Objects.isNull(text)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid text data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), String.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not String");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, text);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects integer data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model integer data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectInteger(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ Integer number = ((JsonDataModelTree) context.data()).intAt(model.path());
+ if (Objects.isNull(number)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid number data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), Integer.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Integer");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, number);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects boolean data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model boolean data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectBoolean(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ Boolean bool = ((JsonDataModelTree) context.data()).booleanAt(model.path());
+ if (Objects.isNull(bool)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid boolean data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), Boolean.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Boolean");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, bool);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects json node data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectJsonNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ JsonNode jsonNode = ((JsonDataModelTree) context.data()).nodeAt(model.path());
+ if (Objects.isNull(jsonNode)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid json node data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), JsonNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not JsonNode");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, jsonNode);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects json array node data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json array node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectArrayNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ ArrayNode arrayNode = ((JsonDataModelTree) context.data()).arrayAt(model.path());
+ if (Objects.isNull(arrayNode)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid array node data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), ArrayNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ArrayNode");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, arrayNode);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ /**
+ * Injects json object node data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json object node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void injectObjectNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ ObjectNode objNode = ((JsonDataModelTree) context.data()).objectAt(model.path());
+ if (Objects.isNull(objNode)) {
+ if (model.optional()) {
+ return;
+ }
+ throw new WorkflowException("Invalid object node data model on (" + model.path() + ")");
+ }
+
+ if (!(Objects.equals(field.getType(), ObjectNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ObjectNode");
+ }
+
+ try {
+ field.setAccessible(true);
+ field.set(worklet, objNode);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+ }
+
+ private static Map<Class, DataModelFieldBehavior> inhaleTypeMap = new HashMap<>();
+ static {
+ inhaleTypeMap.put(String.class, JsonDataModelInjector::inhaleText);
+ inhaleTypeMap.put(Integer.class, JsonDataModelInjector::inhaleInteger);
+ inhaleTypeMap.put(Boolean.class, JsonDataModelInjector::inhaleBoolean);
+ inhaleTypeMap.put(JsonNode.class, JsonDataModelInjector::inhaleJsonNode);
+ inhaleTypeMap.put(ArrayNode.class, JsonDataModelInjector::inhaleArrayNode);
+ inhaleTypeMap.put(ObjectNode.class, JsonDataModelInjector::inhaleObjectNode);
+ }
+
+ /**
+ * Inhales data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private void inhaleModel(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ DataModelFieldBehavior behavior = inhaleTypeMap.get(model.type());
+ if (Objects.isNull(behavior)) {
+ throw new WorkflowException("Not supported type(" + model.type() + ")");
+ }
+ behavior.apply(worklet, context, field, model);
+ }
+
+ /**
+ * Inhales text data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model text data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleText(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), String.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not String");
+ }
+
+ String text;
+ try {
+ field.setAccessible(true);
+ text = (String) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(text)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.setAt(model.path(), text);
+ } else if (!(jsonNode instanceof TextNode)) {
+ throw new WorkflowException("Invalid text data model on (" + model.path() + ")");
+ } else {
+ tree.remove(model.path());
+ tree.setAt(model.path(), text);
+ }
+ }
+
+ /**
+ * Inhales integer data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model integer data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleInteger(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), Integer.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Integer");
+ }
+
+ Integer number;
+ try {
+ field.setAccessible(true);
+ number = (Integer) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(number)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.setAt(model.path(), number);
+ } else if (!(jsonNode instanceof IntNode)) {
+ throw new WorkflowException("Invalid integer data model on (" + model.path() + ")");
+ } else {
+ tree.remove(model.path());
+ tree.setAt(model.path(), number);
+ }
+ }
+
+ /**
+ * Inhales boolean data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model boolean data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleBoolean(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), Boolean.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not Boolean");
+ }
+
+ Boolean bool;
+ try {
+ field.setAccessible(true);
+ bool = (Boolean) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(bool)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.setAt(model.path(), bool);
+ } else if (!(jsonNode instanceof BooleanNode)) {
+ throw new WorkflowException("Invalid boolean data model on (" + model.path() + ")");
+ } else {
+ tree.remove(model.path());
+ tree.setAt(model.path(), bool);
+ }
+ }
+
+ /**
+ * Inhales json node data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleJsonNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+
+ if (!(Objects.equals(field.getType(), JsonNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not JsonNode");
+ }
+
+ JsonNode tgtJsonNode;
+ try {
+ field.setAccessible(true);
+ tgtJsonNode = (JsonNode) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(tgtJsonNode)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.attach(model.path(), new JsonDataModelTree(tgtJsonNode));
+ } else if (!(jsonNode instanceof JsonNode)) {
+ throw new WorkflowException("Invalid json node data model on (" + model.path() + ")");
+ } else {
+ // do nothing
+ }
+ }
+
+ /**
+ * Inhales json array node data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json array node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleArrayNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+ if (!(Objects.equals(field.getType(), ArrayNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ArrayNode");
+ }
+
+ ArrayNode tgtArrayNode;
+ try {
+ field.setAccessible(true);
+ tgtArrayNode = (ArrayNode) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(tgtArrayNode)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.attach(model.path(), new JsonDataModelTree(tgtArrayNode));
+ } else if (!(jsonNode instanceof ArrayNode)) {
+ throw new WorkflowException("Invalid array node data model on (" + model.path() + ")");
+ } else {
+ // do nothing
+ }
+ }
+
+ /**
+ * Inhales json object node data model on the filed of work-let.
+ * @param worklet work-let
+ * @param context workflow context
+ * @param field the field of work-let
+ * @param model json object node data model for the field
+ * @throws WorkflowException workflow exception
+ */
+ private static void inhaleObjectNode(Worklet worklet, WorkflowContext context, Field field, JsonDataModel model)
+ throws WorkflowException {
+ if (!(Objects.equals(field.getType(), ObjectNode.class))) {
+ throw new WorkflowException("Target field (" + field + ") is not ObjectNode");
+ }
+
+ ObjectNode tgtObjNode;
+ try {
+ field.setAccessible(true);
+ tgtObjNode = (ObjectNode) field.get(worklet);
+ } catch (IllegalAccessException e) {
+ throw new WorkflowException(e);
+ }
+
+ if (Objects.isNull(tgtObjNode)) {
+ return;
+ }
+
+ JsonDataModelTree tree = (JsonDataModelTree) context.data();
+ JsonNode jsonNode = tree.nodeAt(model.path());
+
+ if (Objects.isNull(jsonNode) || jsonNode instanceof MissingNode) {
+ tree.attach(model.path(), new JsonDataModelTree(tgtObjNode));
+ } else if (!(jsonNode instanceof ObjectNode)) {
+ throw new WorkflowException("Invalid object node data model on (" + model.path() + ")");
+ } else {
+ // do nothing
+ }
+ }
+}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
index 3a74bd2..7ee1be7 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/JsonDataModelTree.java
@@ -21,11 +21,17 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.BooleanNode;
+import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.MissingNode;
+import com.fasterxml.jackson.databind.node.NumericNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.MoreObjects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.util.Objects;
@@ -34,6 +40,8 @@
*/
public final class JsonDataModelTree implements DataModelTree {
+ private static final Logger log = LoggerFactory.getLogger(JsonDataModelTree.class);
+
/**
* Root node of json data model tree.
*/
@@ -65,30 +73,88 @@
@Override
public void attach(String path, DataModelTree tree) throws WorkflowException {
+
if (root == null || root instanceof MissingNode) {
throw new WorkflowException("Invalid root node");
}
JsonPointer ptr = JsonPointer.compile(path);
- JsonNode node = root.at(ptr);
- if (!(node instanceof MissingNode)) {
- throw new WorkflowException("Path(" + path + ") has already subtree(" + node + ")");
- }
if (!(tree instanceof JsonDataModelTree)) {
throw new WorkflowException("Invalid subTree(" + tree + ")");
}
JsonNode attachingNode = ((JsonDataModelTree) tree).root();
- alloc(ptr.head(), Nodetype.MAP);
- JsonNode parentNode = root.at(ptr.head());
+ attach(ptr, attachingNode);
+ }
- if (!parentNode.isObject()) {
- throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + ")");
+ private void attach(JsonPointer ptr, JsonNode attachingNode) throws WorkflowException {
+
+ JsonNode node = root.at(ptr);
+ if (!(node instanceof MissingNode)) {
+ throw new WorkflowException("Path(" + ptr + ") has already subtree(" + node + ")");
}
- String key = ptr.last().getMatchingProperty();
- ((ObjectNode) parentNode).put(key, attachingNode);
+ if (ptr.last().getMatchingIndex() != -1) {
+
+ alloc(ptr.head(), Nodetype.ARRAY);
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isArray()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
+ }
+ int index = ptr.last().getMatchingIndex();
+ ((ArrayNode) parentNode).insert(index, attachingNode);
+
+ } else if (ptr.last().getMatchingProperty() != null) {
+
+ alloc(ptr.head(), Nodetype.MAP);
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isObject()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
+ }
+ String key = ptr.last().getMatchingProperty();
+ ((ObjectNode) parentNode).put(key, attachingNode);
+
+ } else {
+ throw new WorkflowException("Invalid path(" + ptr + ")");
+ }
+ }
+
+ @Override
+ public void remove(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ remove(ptr);
+ }
+
+ private void remove(JsonPointer ptr) throws WorkflowException {
+
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ log.warn("{} does not have valid node", ptr);
+ return;
+ }
+
+ if (ptr.last().getMatchingIndex() != -1) {
+
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isArray()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
+ }
+ int index = ptr.last().getMatchingIndex();
+ ((ArrayNode) parentNode).remove(index);
+
+ } else if (ptr.last().getMatchingProperty() != null) {
+
+ JsonNode parentNode = root.at(ptr.head());
+ if (!parentNode.isObject()) {
+ throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
+ }
+ String key = ptr.last().getMatchingProperty();
+ ((ObjectNode) parentNode).remove(key);
+
+ } else {
+ throw new WorkflowException("Invalid path(" + ptr + ")");
+ }
}
@Override
@@ -200,6 +266,9 @@
throw new WorkflowException("Invalid root node");
}
JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
if (!(node instanceof ObjectNode)) {
throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
}
@@ -228,6 +297,9 @@
throw new WorkflowException("Invalid root node");
}
JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
if (!(node instanceof ArrayNode)) {
throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
}
@@ -235,6 +307,165 @@
}
/**
+ * Gets text node on specific path.
+ * @param path path of json node
+ * @return text on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public String textAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return textAt(ptr);
+ }
+
+ /**
+ * Gets text on specific json pointer.
+ * @param ptr json pointer
+ * @return text on specific json pointer
+ * @throws WorkflowException workflow exception
+ */
+ public String textAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof TextNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return ((TextNode) node).asText();
+ }
+
+ /**
+ * Gets integer node on specific path.
+ * @param path path of json node
+ * @return integer on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public Integer intAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return intAt(ptr);
+ }
+
+ /**
+ * Gets integer on specific json pointer.
+ * @param ptr json pointer
+ * @return integer on specific json pointer
+ * @throws WorkflowException workflow exception
+ */
+ public Integer intAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof NumericNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return ((NumericNode) node).asInt();
+ }
+
+ /**
+ * Gets boolean on specific path.
+ * @param path path of json node
+ * @return boolean on specific path
+ * @throws WorkflowException workflow exception
+ */
+ public Boolean booleanAt(String path) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ return booleanAt(ptr);
+ }
+
+ /**
+ * Gets boolean on specific json pointer.
+ * @param ptr json pointer
+ * @return boolean on specific json pointer
+ * @throws WorkflowException workflow exception
+ */
+ public Boolean booleanAt(JsonPointer ptr) throws WorkflowException {
+ if (root == null || root instanceof MissingNode) {
+ throw new WorkflowException("Invalid root node");
+ }
+ JsonNode node = root.at(ptr);
+ if (node instanceof MissingNode) {
+ return null;
+ }
+ if (!(node instanceof BooleanNode)) {
+ throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
+ }
+ return ((BooleanNode) node).asBoolean();
+ }
+
+ /**
+ * Sets text on specific json path.
+ * @param path json path
+ * @param text text to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(String path, String text) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ setAt(ptr, text);
+ }
+
+ /**
+ * Sets text on the specific json pointer.
+ * @param ptr json pointer
+ * @param text text to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(JsonPointer ptr, String text) throws WorkflowException {
+ TextNode textNode = TextNode.valueOf(text);
+ attach(ptr, textNode);
+ }
+
+ /**
+ * Sets boolean on specific json path.
+ * @param path json path
+ * @param isTrue boolean to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(String path, Boolean isTrue) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ setAt(ptr, isTrue);
+ }
+
+ /**
+ * Sets boolean on the specific json pointer.
+ * @param ptr json pointer
+ * @param isTrue boolean to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(JsonPointer ptr, Boolean isTrue) throws WorkflowException {
+ BooleanNode booleanNode = BooleanNode.valueOf(isTrue);
+ attach(ptr, booleanNode);
+ }
+
+ /**
+ * Sets integer on specific json path.
+ * @param path json path
+ * @param number number to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(String path, Integer number) throws WorkflowException {
+ JsonPointer ptr = JsonPointer.compile(path);
+ setAt(ptr, number);
+ }
+
+ /**
+ * Sets integer on the specific json pointer.
+ * @param ptr json pointer
+ * @param number number to set
+ * @throws WorkflowException workflow exception
+ */
+ public void setAt(JsonPointer ptr, Integer number) throws WorkflowException {
+ IntNode intNode = IntNode.valueOf(number);
+ attach(ptr, intNode);
+ }
+
+ /**
* Allocates json data model tree on json pointer path with specific leaf type.
* @param node current json node in the json tree path
* @param ptr json pointer
@@ -245,11 +476,11 @@
private JsonNode alloc(JsonNode node, JsonPointer ptr, JsonNodeType leaftype) throws WorkflowException {
if (ptr.matches()) {
- if (node instanceof MissingNode) {
+ if (node == null || node instanceof MissingNode) {
node = createEmpty(leaftype);
} else {
//TODO: checking existing node type is matched with leaftype
- if (Objects.equals(node.getNodeType(), leaftype)) {
+ if (!Objects.equals(node.getNodeType(), leaftype)) {
throw new WorkflowException("Requesting leaftype(" + leaftype + ") is not matched with "
+ "existing nodetype(" + node.getNodeType() + ") for " + ptr);
}
@@ -258,7 +489,7 @@
}
if (ptr.getMatchingIndex() != -1) {
- if (node instanceof MissingNode) {
+ if (node == null || node instanceof MissingNode) {
node = createEmpty(JsonNodeType.ARRAY);
}
JsonNode child = alloc(node.get(ptr.getMatchingIndex()), ptr.tail(), leaftype);
@@ -266,7 +497,7 @@
((ArrayNode) node).insert(ptr.getMatchingIndex(), child);
}
} else if (ptr.getMatchingProperty() != null) {
- if (node instanceof MissingNode) {
+ if (node == null || node instanceof MissingNode) {
node = createEmpty(JsonNodeType.OBJECT);
}
JsonNode child = alloc(node.get(ptr.getMatchingProperty()), ptr.tail(), leaftype);
@@ -304,7 +535,7 @@
try {
str = (new ObjectMapper()).writerWithDefaultPrettyPrinter().writeValueAsString(root);
} catch (JsonProcessingException e) {
- e.printStackTrace();
+ log.error("Exception: ", e);
}
return str;
}
@@ -312,7 +543,7 @@
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
- .add("json", formattedRootString())
+ .add("json", root)
.toString();
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ProgramCounter.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ProgramCounter.java
new file mode 100644
index 0000000..6af50e0
--- /dev/null
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/ProgramCounter.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018-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.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * An interface representing workflow program counter.
+ */
+public final class ProgramCounter {
+
+ public static final ProgramCounter INIT_PC = ProgramCounter.valueOf(Worklet.Common.INIT.name(), 0);
+
+ /**
+ * index of the worklet.
+ */
+ private int workletIndex;
+
+ /**
+ * Type of worklet.
+ */
+ private String workletType;
+
+ /**
+ * Index of worklet.
+ * @return index of worklet
+ */
+ public int workletIndex() {
+ return this.workletIndex;
+ }
+
+ /**
+ * Type of worklet.
+ * @return type of worklet
+ */
+ public String workletType() {
+ return this.workletType;
+ }
+
+ /**
+ * Constructor of workflow Program Counter.
+ * @param workletType type of worklet
+ * @param workletIndex index of worklet
+ */
+ private ProgramCounter(String workletType, int workletIndex) {
+ this.workletType = workletType;
+ this.workletIndex = workletIndex;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.toString());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof ProgramCounter)) {
+ return false;
+ }
+ return Objects.equals(this.workletType(), ((ProgramCounter) obj).workletType())
+ && Objects.equals(this.workletIndex(), ((ProgramCounter) obj).workletIndex());
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(%d)%s", workletIndex, workletType);
+ }
+
+ /**
+ * Builder of workflow Program Counter.
+ * @param workletType type of worklet
+ * @param workletIndex index of worklet
+ * @return program counter
+ */
+ public static ProgramCounter valueOf(String workletType, int workletIndex) {
+ return new ProgramCounter(workletType, workletIndex);
+ }
+
+ /**
+ * Builder of workflow Program Counter.
+ * @param strProgramCounter string format for program counter
+ * @return program counter
+ */
+ public static ProgramCounter valueOf(String strProgramCounter) {
+
+ Matcher m = Pattern.compile("\\((\\d+)\\)(.+)").matcher(strProgramCounter);
+
+ if (!m.matches()) {
+ throw new IllegalArgumentException("Malformed program counter string");
+ }
+
+ return new ProgramCounter(m.group(2), Integer.parseInt(m.group(1)));
+ }
+
+}
+
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java
index 5642c97..4cc15bd 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/SystemWorkflowContext.java
@@ -17,8 +17,6 @@
import com.google.common.base.MoreObjects;
-import java.net.URI;
-
/**
* WorkflowContext for system workflow.
*/
@@ -36,12 +34,10 @@
/**
* The constructor of SystemWorkflowContext.
- * @param workflowId id of workflow
- * @param workplaceName workplace name
- * @param data data model tree
+ * @param builder builder of SystemWorkflowContext
*/
- public SystemWorkflowContext(URI workflowId, String workplaceName, DataModelTree data) {
- super(workflowId, workplaceName, data);
+ public SystemWorkflowContext(Builder builder) {
+ super(builder);
timestamp = System.currentTimeMillis();
//initial distributor(It can be changed)
distributor = name();
@@ -78,4 +74,27 @@
.add("cause", cause())
.toString();
}
+
+ /**
+ * Gets systemBuilder instance.
+ * @return systemBuilder instance
+ */
+ public static final Builder systemBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for system workflow context.
+ */
+ public static class Builder extends DefaultWorkflowContext.Builder {
+
+ /**
+ * Builds system workflow context.
+ * @return instance of default workflow context.
+ */
+ public SystemWorkflowContext build() {
+ return new SystemWorkflowContext(this);
+ }
+ }
+
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java
index 5b98bd1..180d9eb 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimeoutTask.java
@@ -17,6 +17,8 @@
import com.google.common.base.MoreObjects;
+import static org.onosproject.workflow.api.CheckCondition.check;
+
public final class TimeoutTask extends HandlerTask {
@@ -28,7 +30,7 @@
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("context", context())
- .add("workletType", workletType())
+ .add("programCounter", programCounter())
.toString();
}
@@ -51,16 +53,19 @@
}
@Override
- public Builder workletType(String workletType) {
- super.workletType(workletType);
+ public Builder programCounter(ProgramCounter programCounter) {
+ super.programCounter(programCounter);
return this;
}
/**
* Builds TimeoutTask.
* @return instance of TimeoutTask
+ * @throws WorkflowException workflow exception
*/
- public TimeoutTask build() {
+ public TimeoutTask build() throws WorkflowException {
+ check(context != null, "context is invalid");
+ check(programCounter != null, "programCounter is invalid");
return new TimeoutTask(this);
}
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java
index 1118568..afa2ac6 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/TimerChain.java
@@ -32,6 +32,13 @@
private TimerChainTask impendingTask;
/**
+ * Constructor of timer chain.
+ */
+ public TimerChain() {
+
+ }
+
+ /**
* Schedules timer event.
* @param afterMs millisecond which time event happens.
* @param runnable runnable to be executed after 'afterMs'
@@ -55,7 +62,7 @@
*/
private void schedule(TimerChainTask task) {
synchronized (this) {
- if (taskQueue.size() == 0) {
+ if (taskQueue.isEmpty()) {
scheduleImpending(task);
return;
}
@@ -124,7 +131,7 @@
* @return timer chain task
*/
public TimerChainTask head() {
- if (taskQueue.size() > 0) {
+ if (!taskQueue.isEmpty()) {
return taskQueue.peek();
} else {
return null;
@@ -136,7 +143,7 @@
* @return timer chain task
*/
public TimerChainTask pop() {
- if (taskQueue.size() > 0) {
+ if (!taskQueue.isEmpty()) {
return taskQueue.poll();
} else {
return null;
@@ -186,7 +193,6 @@
TimerChainTask nextTask;
synchronized (timerchain) {
if (timerchain.impendingTask() != this) {
- System.out.println("Invalid impendingTask");
runnable().run();
return;
}
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 95e6e08..96e623f 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
@@ -38,12 +38,20 @@
Worklet init(WorkflowContext context) throws WorkflowException;
/**
- * Returns next worklet.
+ * Returns next program counter.
* @param context workflow context
- * @return next worklet
+ * @return next program counter
* @throws WorkflowException workflow exception
*/
- Worklet next(WorkflowContext context) throws WorkflowException;
+ ProgramCounter next(WorkflowContext context) throws WorkflowException;
+
+ /**
+ * Gets increased program coounter.
+ * @param pc program counter
+ * @return increased program counter
+ * @throws WorkflowException workflow exception
+ */
+ ProgramCounter increased(ProgramCounter pc) throws WorkflowException;
/**
* Returns instance of worklet.
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java
index 12d4673..9f0b4fe 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowContext.java
@@ -57,16 +57,16 @@
public abstract void setState(WorkflowState state);
/**
- * Sets the current worklet of workflow context.
- * @param worklet current worklet
+ * Sets the current program counter of workflow context.
+ * @param pc current program counter
*/
- public abstract void setCurrent(Worklet worklet);
+ public abstract void setCurrent(ProgramCounter pc);
/**
- * Returns the class name of current worklet.
- * @return the class name of current worklet
+ * Returns the current program counter of workflow.
+ * @return the current program counter of workflow
*/
- public abstract String current();
+ public abstract ProgramCounter current();
/**
* Returns the cause string of exception state.
@@ -163,4 +163,13 @@
* @return workplace store
*/
public abstract WorkplaceStore workplaceStore();
+
+ /**
+ * Get service.
+ * @param serviceClass service class
+ * @param <T> service class type
+ * @return service reference
+ * @throws WorkflowException workflow exception
+ */
+ public abstract <T> T getService(Class<T> serviceClass) throws WorkflowException;
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java
index ad9f731..9f609b3 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/WorkflowExecutionService.java
@@ -30,6 +30,12 @@
void execInitWorklet(WorkflowContext context);
/**
+ * Evals workflow context.
+ * @param contextName the name of workflow context
+ */
+ void eval(String contextName);
+
+ /**
* Triggers workflow event map.
* @param event triggering event
* @param generator event hint generation method reference
@@ -41,10 +47,9 @@
* @param eventType event type (class name of event)
* @param eventHint event hint value
* @param contextName workflow context name to be called by this event map
- * @param workletType worklet type to be called by this event map
+ * @param programCounterString worklet type to be called by this event map
* @throws WorkflowException workflow exception
*/
void registerEventMap(Class<? extends Event> eventType, String eventHint,
- String contextName, String workletType) throws WorkflowException;
-
+ String contextName, String programCounterString) throws WorkflowException;
}
diff --git a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java
index 386fa38..c4b4fb2 100644
--- a/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java
+++ b/apps/workflow/api/src/main/java/org/onosproject/workflow/api/Worklet.java
@@ -23,7 +23,7 @@
*/
public interface Worklet {
- int MAX_WORKS = 1000;
+ int MAX_WORKS = 10000;
/**
* Returns tag name of worklet. class name is usually used.