1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 package com.linecorp.centraldogma.internal.jsonpatch;
36
37 import static com.fasterxml.jackson.annotation.JsonSubTypes.Type;
38 import static com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
39 import static java.util.Objects.requireNonNull;
40
41 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
42 import com.fasterxml.jackson.annotation.JsonSubTypes;
43 import com.fasterxml.jackson.annotation.JsonTypeInfo;
44 import com.fasterxml.jackson.core.JsonPointer;
45 import com.fasterxml.jackson.databind.JsonNode;
46 import com.fasterxml.jackson.databind.JsonSerializable;
47 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
48
49 import com.linecorp.centraldogma.internal.Jackson;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 @JsonTypeInfo(use = Id.NAME, property = "op")
65 @JsonSubTypes({
66 @Type(name = "add", value = AddOperation.class),
67 @Type(name = "copy", value = CopyOperation.class),
68 @Type(name = "move", value = MoveOperation.class),
69 @Type(name = "remove", value = RemoveOperation.class),
70 @Type(name = "removeIfExists", value = RemoveIfExistsOperation.class),
71 @Type(name = "replace", value = ReplaceOperation.class),
72 @Type(name = "safeReplace", value = SafeReplaceOperation.class),
73 @Type(name = "test", value = TestOperation.class),
74 @Type(name = "testAbsence", value = TestAbsenceOperation.class)
75 })
76 @JsonIgnoreProperties(ignoreUnknown = true)
77 public abstract class JsonPatchOperation implements JsonSerializable {
78
79
80
81
82 public static JsonNode asJsonArray(JsonPatchOperation... jsonPatchOperations) {
83 requireNonNull(jsonPatchOperations, "jsonPatchOperations");
84 return Jackson.valueToTree(jsonPatchOperations);
85 }
86
87 final String op;
88
89
90
91
92
93
94
95 final JsonPointer path;
96
97
98
99
100
101
102
103 JsonPatchOperation(final String op, final JsonPointer path) {
104 this.op = op;
105 this.path = path;
106 }
107
108 public JsonPointer path() {
109 return path;
110 }
111
112
113
114
115
116
117
118
119 abstract JsonNode apply(JsonNode node);
120
121 @Override
122 public abstract String toString();
123
124
125
126
127 public JsonNode toJsonNode() {
128 return JsonNodeFactory.instance.arrayNode().add(Jackson.valueToTree(this));
129 }
130
131 JsonNode ensureExistence(JsonNode node) {
132 final JsonNode found = node.at(path);
133 if (found.isMissingNode()) {
134 throw new JsonPatchException("non-existent path: " + path);
135 }
136 return found;
137 }
138
139 static JsonNode ensureSourceParent(JsonNode node, JsonPointer path) {
140 return ensureParent(node, path, "source");
141 }
142
143 static JsonNode ensureTargetParent(JsonNode node, JsonPointer path) {
144 return ensureParent(node, path, "target");
145 }
146
147 private static JsonNode ensureParent(JsonNode node, JsonPointer path, String typeName) {
148
149
150
151
152 final JsonPointer parentPath = path.head();
153 final JsonNode parentNode = node.at(parentPath);
154 if (parentNode.isMissingNode()) {
155 throw new JsonPatchException("non-existent " + typeName + " parent: " + parentPath);
156 }
157 if (!parentNode.isContainerNode()) {
158 throw new JsonPatchException(typeName + " parent is not a container: " + parentPath +
159 " (" + parentNode.getNodeType() + ')');
160 }
161 return parentNode;
162 }
163 }