blob: 6f87411468053399f0ef25ecda3c4ca8d7ddd1a5 [file] [log] [blame]
/*
* 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(field.getType());
if (Objects.isNull(behavior)) {
throw new WorkflowException("Not supported type(" + field.getType() + ")");
}
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(field.getType());
if (Objects.isNull(behavior)) {
throw new WorkflowException("Not supported type(" + field.getType() + ")");
}
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
}
}
}