diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/AddOperation.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/AddOperation.java
index ca8fbf981..4d0ca6827 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/AddOperation.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/AddOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -16,26 +16,30 @@
package org.springframework.data.rest.webmvc.json.patch;
/**
- * Operation to add a new value to the given "path".
- * Will throw a {@link PatchException} if the path is invalid or if the given value
- * is not assignable to the given path.
+ * Operation to add a new value to the given "path". Will throw a {@link PatchException} if the path is invalid or if
+ * the given value is not assignable to the given path.
*
* @author Craig Walls
+ * @author Oliver Gierke
*/
-public class AddOperation extends PatchOperation {
+class AddOperation extends PatchOperation {
/**
* Constructs the add operation
+ *
* @param path The path where the value will be added. (e.g., '/foo/bar/4')
* @param value The value to add.
*/
public AddOperation(String path, Object value) {
super("add", path, value);
}
-
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.json.patch.PatchOperation#perform(java.lang.Object, java.lang.Class)
+ */
@Override
void perform(Object targetObject, Class type) {
addValue(targetObject, evaluateValueFromTarget(targetObject, type));
}
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/CopyOperation.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/CopyOperation.java
index e2ba689ef..ee8f84817 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/CopyOperation.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/CopyOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -37,8 +37,9 @@ import static org.springframework.data.rest.webmvc.json.patch.PathToSpEL.*;
*
*
* @author Craig Walls
+ * @author Oliver Gierke
*/
-public class CopyOperation extends FromOperation {
+class CopyOperation extends FromOperation {
/**
* Constructs the copy operation
@@ -50,9 +51,12 @@ public class CopyOperation extends FromOperation {
super("copy", path, from);
}
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.json.patch.PatchOperation#perform(java.lang.Object, java.lang.Class)
+ */
@Override
void perform(Object target, Class type) {
- addValue(target, pathToExpression(from).getValue(target));
+ addValue(target, pathToExpression(getFrom()).getValue(target));
}
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/FromOperation.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/FromOperation.java
index 05a3e9e58..5fffe7dcd 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/FromOperation.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/FromOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -16,16 +16,19 @@
package org.springframework.data.rest.webmvc.json.patch;
/**
- * Abstract base class for operations requiring a source property, such as "copy" and "move".
- * (e.g., copy from here to there.
+ * Abstract base class for operations requiring a source property, such as "copy" and "move". (e.g., copy from
+ * here to there.
+ *
* @author Craig Walls
+ * @author Oliver Gierke
*/
-public abstract class FromOperation extends PatchOperation {
+abstract class FromOperation extends PatchOperation {
+
+ private final String from;
- protected String from;
-
/**
* Constructs the operation
+ *
* @param op The name of the operation to perform. (e.g., 'copy')
* @param path The operation's target path. (e.g., '/foo/bar/4')
* @param from The operation's source path. (e.g., '/foo/bar/5')
@@ -34,9 +37,8 @@ public abstract class FromOperation extends PatchOperation {
super(op, path);
this.from = from;
}
-
+
public String getFrom() {
return from;
}
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonLateObjectEvaluator.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonLateObjectEvaluator.java
index 55de8965e..59ccd15c4 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonLateObjectEvaluator.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonLateObjectEvaluator.java
@@ -15,6 +15,9 @@
*/
package org.springframework.data.rest.webmvc.json.patch;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -23,23 +26,23 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*
* @author Craig Walls
*/
+@RequiredArgsConstructor
class JsonLateObjectEvaluator implements LateObjectEvaluator {
- private static final ObjectMapper MAPPER = new ObjectMapper();
-
- private JsonNode valueNode;
-
- public JsonLateObjectEvaluator(JsonNode valueNode) {
- this.valueNode = valueNode;
- }
+ private final @NonNull ObjectMapper mapper;
+ private final @NonNull JsonNode valueNode;
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.json.patch.LateObjectEvaluator#evaluate(java.lang.Class)
+ */
@Override
public Object evaluate(Class type) {
+
try {
- return MAPPER.readValue(valueNode.traverse(), type);
+ return mapper.readValue(valueNode.traverse(), type);
} catch (Exception e) {
return null;
}
}
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonPatchPatchConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonPatchPatchConverter.java
index bf2834cb3..27bff320f 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonPatchPatchConverter.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/JsonPatchPatchConverter.java
@@ -15,13 +15,13 @@
*/
package org.springframework.data.rest.webmvc.json.patch;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-import org.springframework.util.Assert;
-
-import com.fasterxml.jackson.core.JsonPointer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -35,21 +35,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
* @author Oliver Gierke
* @author Mathias Düsterhöft
*/
+@RequiredArgsConstructor
public class JsonPatchPatchConverter implements PatchConverter {
- private final ObjectMapper mapper;
-
- /**
- * Creates a new {@link JsonPatchPatchConverter} for the given {@link ObjectMapper}.
- *
- * @param mapper must not be {@literal null}.
- */
- public JsonPatchPatchConverter(ObjectMapper mapper) {
-
- Assert.notNull(mapper, "ObjectMapper must not be null!");
-
- this.mapper = mapper;
- }
+ private final @NonNull ObjectMapper mapper;
/**
* Constructs a {@link Patch} object given a JsonNode.
@@ -58,13 +47,16 @@ public class JsonPatchPatchConverter implements PatchConverter {
* @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 ops = new ArrayList(opNodes.size());
+
for (Iterator elements = opNodes.elements(); elements.hasNext();) {
+
JsonNode opNode = elements.next();
String opType = opNode.get("op").textValue();
@@ -105,18 +97,25 @@ public class JsonPatchPatchConverter implements PatchConverter {
List 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);
}
@@ -124,6 +123,7 @@ public class JsonPatchPatchConverter implements PatchConverter {
}
private Object valueFromJsonNode(String path, JsonNode valueNode) {
+
if (valueNode == null || valueNode.isNull()) {
return null;
} else if (valueNode.isTextual()) {
@@ -137,36 +137,11 @@ public class JsonPatchPatchConverter implements PatchConverter {
} else if (valueNode.isLong()) {
return valueNode.asLong();
} else if (valueNode.isObject()) {
- return new JsonLateObjectEvaluator(valueNode);
+ return new JsonLateObjectEvaluator(mapper, valueNode);
} else if (valueNode.isArray()) {
// TODO: Convert valueNode to array
}
return null;
}
-
- /**
- * Returns whether the trailing element of the given {@link JsonPointer} is a pointer into an array or collection.
- *
- * @param pointer must not be {@literal null}.
- * @return
- */
- private static boolean isCollectionElementReference(String pointer) {
-
- String[] segments = pointer.split("/");
-
- if (segments.length == 0) {
- return false;
- }
-
- String trailing = segments[segments.length - 1];
-
- try {
- Integer.parseInt(trailing);
- return true;
- } catch (NumberFormatException o_O) {
- return false;
- }
- }
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/LateObjectEvaluator.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/LateObjectEvaluator.java
index aa4303cbd..652310f7c 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/LateObjectEvaluator.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/LateObjectEvaluator.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -19,18 +19,15 @@ package org.springframework.data.rest.webmvc.json.patch;
*
* Strategy interface for resolving values from an operation definition.
*
- *
*
- * {@link Patch} implementation generically defines a patch without being tied to any particular
- * patch specification. But it's important to know the patch format when resolving the value of
- * an operation, as the value format will likely be tied to the patch specification. For example,
- * the value attribute of a JSON Patch operation will contain a JSON object. A different
- * patch specification may define values in some non-JSON format.
+ * {@link Patch} implementation generically defines a patch without being tied to any particular patch specification.
+ * But it's important to know the patch format when resolving the value of an operation, as the value format will likely
+ * be tied to the patch specification. For example, the value attribute of a JSON Patch operation will
+ * contain a JSON object. A different patch specification may define values in some non-JSON format.
*
- *
*
- * This interface allows for pluggable evaluation of values, allowing {@link Patch} to remain
- * independent of any specific patch representation.
+ * This interface allows for pluggable evaluation of values, allowing {@link Patch} to remain independent of any
+ * specific patch representation.
*
*
* @author Craig Walls
@@ -38,5 +35,4 @@ package org.springframework.data.rest.webmvc.json.patch;
public interface LateObjectEvaluator {
Object evaluate(Class type);
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/MoveOperation.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/MoveOperation.java
index 863c852e8..ef1869c4a 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/MoveOperation.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/MoveOperation.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -17,34 +17,37 @@ package org.springframework.data.rest.webmvc.json.patch;
/**
*
- * Operation that moves a value from the given "from" path to the given "path".
- * Will throw a {@link PatchException} if either path is invalid or if the from path is non-nullable.
+ * Operation that moves a value from the given "from" path to the given "path". Will throw a {@link PatchException} if
+ * either path is invalid or if the from path is non-nullable.
*
- *
*
- * NOTE: When dealing with lists, the move operation may effectively be a no-op.
- * That's because the order of a list is probably dictated by a database query that produced the list.
- * Moving things around in the list will have no bearing on the values of each item in the list.
- * When the same list resource is retrieved again later, the order will again be decided by the query,
- * effectively undoing any previous move operation.
+ * NOTE: When dealing with lists, the move operation may effectively be a no-op. That's because the order of a list is
+ * probably dictated by a database query that produced the list. Moving things around in the list will have no bearing
+ * on the values of each item in the list. When the same list resource is retrieved again later, the order will again be
+ * decided by the query, effectively undoing any previous move operation.
*
*
* @author Craig Walls
+ * @author Oliver Gierke
*/
-public class MoveOperation extends FromOperation {
+class MoveOperation extends FromOperation {
/**
* Constructs the move operation.
+ *
* @param path The path to move the source value to. (e.g., '/foo/bar/4')
* @param from The source path from which a value will be moved. (e.g., '/foo/bar/5')
*/
public MoveOperation(String path, String from) {
super("move", path, from);
}
-
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.rest.webmvc.json.patch.PatchOperation#perform(java.lang.Object, java.lang.Class)
+ */
@Override
void perform(Object target, Class type) {
- addValue(target, popValueAtPath(target, from));
+ addValue(target, popValueAtPath(target, getFrom()));
}
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/Patch.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/Patch.java
index 05f25ecea..d0ad2c587 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/Patch.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/Patch.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -27,6 +27,7 @@ import java.util.List;
*
*
* @author Craig Walls
+ * @author Oliver Gierke
*/
public class Patch {
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchConverter.java
index 0f00404c4..4b1ebdc56 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchConverter.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
*
*
* @author Craig Walls
+ * @author Oliver Gierke
* @param A type holding a representation of the patch. For example, a JsonNode if working with JSON Patch.
*/
public interface PatchConverter {
@@ -48,5 +49,4 @@ public interface PatchConverter {
* @return the patch representation object.
*/
T convert(Patch patch);
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchException.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchException.java
index 5eb393caf..9f902afab 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchException.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 the original author or authors.
+ * Copyright 2014-2016 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.
@@ -19,6 +19,7 @@ package org.springframework.data.rest.webmvc.json.patch;
* Exception thrown if an error occurs in the course of applying a Patch.
*
* @author Craig Walls
+ * @author Oliver Gierke
*/
public class PatchException extends RuntimeException {
@@ -27,9 +28,8 @@ public class PatchException extends RuntimeException {
public PatchException(String message) {
super(message);
}
-
+
public PatchException(String message, Exception e) {
super(message, e);
}
-
}
diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchOperation.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchOperation.java
index 2bb53eb5c..f28fb8191 100644
--- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchOperation.java
+++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/patch/PatchOperation.java
@@ -27,15 +27,13 @@ import org.springframework.expression.ExpressionException;
*
* @author Craig Walls
* @author Mathias Düsterhöft
+ * @author Oliver Gierke
*/
public abstract class PatchOperation {
protected final String op;
-
protected final String path;
-
protected final Object value;
-
protected final Expression spelExpression;
/**
@@ -57,6 +55,7 @@ public abstract class PatchOperation {
* {@link LateObjectEvaluator}.
*/
public PatchOperation(String op, String path, Object value) {
+
this.op = op;
this.path = path;
this.value = value;
@@ -92,17 +91,22 @@ public abstract class PatchOperation {
* @return the value popped from the list
*/
protected Object popValueAtPath(Object target, String removePath) {
+
Integer listIndex = targetListIndex(removePath);
Expression expression = pathToExpression(removePath);
Object value = expression.getValue(target);
+
if (listIndex == null) {
+
try {
expression.setValue(target, null);
return value;
} catch (NullPointerException e) {
throw new PatchException("Path '" + removePath + "' is not nullable.");
}
+
} else {
+
Expression parentExpression = pathToParentExpression(removePath);
List> list = (List>) parentExpression.getValue(target);
list.remove(listIndex >= 0 ? listIndex.intValue() : list.size() - 1);
@@ -117,17 +121,19 @@ public abstract class PatchOperation {
* @param target The target object.
* @param value The value to add.
*/
+ @SuppressWarnings({ "unchecked", "null" })
protected void addValue(Object target, Object value) {
+
Expression parentExpression = pathToParentExpression(path);
Object parent = parentExpression != null ? parentExpression.getValue(target) : null;
Integer listIndex = targetListIndex(path);
+
if (parent == null || !(parent instanceof List) || listIndex == null) {
spelExpression.setValue(target, value);
} else {
- @SuppressWarnings("unchecked")
+
List