/*
* Copyright 2014 the original author or authors.
*
* 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.springframework.sync.json;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.sync.AddOperation;
import org.springframework.sync.CopyOperation;
import org.springframework.sync.FromOperation;
import org.springframework.sync.MoveOperation;
import org.springframework.sync.Patch;
import org.springframework.sync.PatchException;
import org.springframework.sync.PatchOperation;
import org.springframework.sync.RemoveOperation;
import org.springframework.sync.ReplaceOperation;
import org.springframework.sync.TestOperation;
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.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Convert {@link JsonNode}s containing JSON Patch to/from {@link Patch} objects.
* @author Craig Walls
*/
public class JsonPatchPatchConverter implements PatchConverter<JsonNode> {
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* Constructs a {@link Patch} object given a JsonNode.
* @param jsonNode a JsonNode containing the JSON Patch
* @return a {@link Patch}
*/
public Patch convert(JsonNode jsonNode) {
if (!(jsonNode instanceof ArrayNode)) {
throw new IllegalArgumentException("JsonNode must be an instance of ArrayNode");
}
ArrayNode opNodes = (ArrayNode) jsonNode;
List<PatchOperation> ops = new ArrayList<PatchOperation>(opNodes.size());
for(Iterator<JsonNode> elements = opNodes.elements(); elements.hasNext(); ) {
JsonNode opNode = elements.next();
String opType = opNode.get("op").textValue();
String path = opNode.get("path").textValue();
JsonNode valueNode = opNode.get("value");
Object value = valueFromJsonNode(path, valueNode);
String from = opNode.has("from") ? opNode.get("from").textValue() : null;
if (opType.equals("test")) {
ops.add(new TestOperation(path, value));
} else if (opType.equals("replace")) {
ops.add(new ReplaceOperation(path, value));
} else if (opType.equals("remove")) {
ops.add(new RemoveOperation(path));
} else if (opType.equals("add")) {
ops.add(new AddOperation(path, value));
} else if (opType.equals("copy")) {
ops.add(new CopyOperation(path, from));
} else if (opType.equals("move")) {
ops.add(new MoveOperation(path, from));
} else {
throw new PatchException("Unrecognized operation type: " + opType);
}
}
return new Patch(ops);
}
/**
* Renders a {@link Patch} as a {@link JsonNode}.
* @param patch the patch
* @return a {@link JsonNode} containing JSON Patch.
*/
public JsonNode convert(Patch patch) {
List<PatchOperation> operations = patch.getOperations();
JsonNodeFactory nodeFactory = JsonNodeFactory.instance;
ArrayNode patchNode = nodeFactory.arrayNode();
for (PatchOperation operation : operations) {
ObjectNode opNode = nodeFactory.objectNode();
opNode.set("op", nodeFactory.textNode(operation.getOp()));
opNode.set("path", nodeFactory.textNode(operation.getPath()));
if (operation instanceof FromOperation) {
FromOperation fromOp = (FromOperation) operation;
opNode.set("from", nodeFactory.textNode(fromOp.getFrom()));
}
Object value = operation.getValue();
if (value != null) {
opNode.set("value", MAPPER.valueToTree(value));
}
patchNode.add(opNode);
}
return patchNode;
}
private Object valueFromJsonNode(String path, JsonNode valueNode) {
if (valueNode == null || valueNode.isNull()) {
return null;
} else if (valueNode.isTextual()) {
return valueNode.asText();
} else if (valueNode.isFloatingPointNumber()) {
return valueNode.asDouble();
} else if (valueNode.isBoolean()) {
return valueNode.asBoolean();
} else if (valueNode.isInt()) {
return valueNode.asInt();
} else if (valueNode.isLong()) {
return valueNode.asLong();
} else if (valueNode.isObject()) {
return new JsonLateObjectEvaluator(valueNode);
} else if (valueNode.isArray()) {
// TODO: Convert valueNode to array
}
return null;
}
}