1   /*
2    * Copyright 2017 LINE Corporation
3    *
4    * LINE Corporation licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package com.linecorp.centraldogma.internal.jsonpatch;
18  
19  import java.io.IOException;
20  
21  import com.fasterxml.jackson.annotation.JsonCreator;
22  import com.fasterxml.jackson.annotation.JsonProperty;
23  import com.fasterxml.jackson.core.JsonGenerator;
24  import com.fasterxml.jackson.core.JsonPointer;
25  import com.fasterxml.jackson.databind.JsonNode;
26  import com.fasterxml.jackson.databind.SerializerProvider;
27  import com.fasterxml.jackson.databind.annotation.JsonSerialize;
28  import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
29  import com.fasterxml.jackson.databind.node.ArrayNode;
30  import com.fasterxml.jackson.databind.node.ObjectNode;
31  import com.google.common.base.Equivalence;
32  
33  public final class SafeReplaceOperation extends JsonPatchOperation {
34  
35      private static final Equivalence<JsonNode> EQUIVALENCE = JsonNumEquals.getInstance();
36  
37      @JsonSerialize
38      private final JsonNode oldValue;
39      @JsonSerialize
40      private final JsonNode newValue;
41  
42      @JsonCreator
43      SafeReplaceOperation(@JsonProperty("path") final JsonPointer path,
44                           @JsonProperty("oldValue") JsonNode oldValue,
45                           @JsonProperty("value") JsonNode newValue) {
46          super("safeReplace", path);
47          this.oldValue = oldValue.deepCopy();
48          this.newValue = newValue.deepCopy();
49      }
50  
51      @Override
52      JsonNode apply(JsonNode node) {
53          final JsonNode actual = ensureExistence(node);
54          if (!EQUIVALENCE.equivalent(actual, oldValue)) {
55              throw new JsonPatchException("mismatching value at '" + path + "': " +
56                                           actual + " (expected: " + oldValue + ')');
57          }
58          final JsonNode replacement = newValue.deepCopy();
59          if (path.toString().isEmpty()) {
60              return replacement;
61          }
62          final JsonNode parent = node.at(path.head());
63          final String rawToken = path.last().getMatchingProperty();
64          if (parent.isObject()) {
65              ((ObjectNode) parent).set(rawToken, replacement);
66          } else {
67              ((ArrayNode) parent).set(Integer.parseInt(rawToken), replacement);
68          }
69          return node;
70      }
71  
72      @Override
73      public void serialize(JsonGenerator gen, SerializerProvider serializers) throws IOException {
74          gen.writeStartObject();
75          gen.writeStringField("op", op);
76          gen.writeStringField("path", path.toString());
77          gen.writeFieldName("oldValue");
78          gen.writeTree(oldValue);
79          gen.writeFieldName("value");
80          gen.writeTree(newValue);
81          gen.writeEndObject();
82      }
83  
84      @Override
85      public void serializeWithType(JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer)
86              throws IOException {
87          serialize(gen, serializers);
88      }
89  
90      @Override
91      public String toString() {
92          return "op: " + op + "; path: \"" + path + "\"; oldValue: " + oldValue + "; value: " + newValue;
93      }
94  }