Skip to content

Commit ca122aa

Browse files
committed
1 parent 7018964 commit ca122aa

File tree

12 files changed

+306
-38
lines changed

12 files changed

+306
-38
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# For most projects, this workflow file will not need changing; you simply need
2+
# to commit it to your repository.
3+
#
4+
# You may wish to alter this file to override the set of languages analyzed,
5+
# or to provide custom queries or build logic.
6+
#
7+
# ******** NOTE ********
8+
# We have attempted to detect the languages in your repository. Please check
9+
# the `language` matrix defined below to confirm you have the correct set of
10+
# supported CodeQL languages.
11+
#
12+
name: "CodeQL"
13+
14+
on:
15+
push:
16+
branches: [ master ]
17+
pull_request:
18+
# The branches below must be a subset of the branches above
19+
branches: [ master ]
20+
schedule:
21+
- cron: '34 20 * * 0'
22+
23+
jobs:
24+
analyze:
25+
name: Analyze
26+
runs-on: ubuntu-latest
27+
permissions:
28+
actions: read
29+
contents: read
30+
security-events: write
31+
32+
strategy:
33+
fail-fast: false
34+
matrix:
35+
language: [ 'java' ]
36+
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37+
# Learn more about CodeQL language support at https://git.io/codeql-language-support
38+
39+
steps:
40+
- name: Checkout repository
41+
uses: actions/checkout@v2
42+
43+
# Initializes the CodeQL tools for scanning.
44+
- name: Initialize CodeQL
45+
uses: github/codeql-action/init@v1
46+
with:
47+
languages: ${{ matrix.language }}
48+
# If you wish to specify custom queries, you can do so here or in a config file.
49+
# By default, queries listed here will override any specified in a config file.
50+
# Prefix the list here with "+" to use these queries and those in the config file.
51+
# queries: ./path/to/local/query, your-org/your-repo/queries@main
52+
53+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54+
# If this step fails, then you should remove it and run the build manually (see below)
55+
- name: Autobuild
56+
uses: github/codeql-action/autobuild@v1
57+
58+
# ℹ️ Command-line programs to run using the OS shell.
59+
# 📚 https://git.io/JvXDl
60+
61+
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62+
# and modify them (or add more) to build your code if your project
63+
# uses a compiled language
64+
65+
#- run: |
66+
# make bootstrap
67+
# make release
68+
69+
- name: Perform CodeQL Analysis
70+
uses: github/codeql-action/analyze@v1

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ target/*
44
.settings/*
55
.DS_Store
66
/target/
7+
.idea

NOTICE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
bsonpatch library
2-
Copyright 2017,2018 eBay, Inc.
2+
Copyright 2017,2018,2022 eBay, Inc.
33

44
This product includes software developed at
55
eBay, Inc. (https://www.ebay.com/).

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
# This is an implementation of [RFC 6902 JSON Patch](http://tools.ietf.org/html/rfc6902) written in Java.
1+
# This is an implementation of [RFC 6902 JSON Patch](https://datatracker.ietf.org/doc/html/rfc6902) written in Java.
22

33
This [JSON Patch](http://jsonpatch.com) implementation works directly with [BSON documents](http://bsonspec.org/) using the [MongoDB Java driver implementation of BSON](https://www.mongodb.com/json-and-bson).
44

55
The code here was ported (copied, renamed, repackaged, modified) from the [zjsonpatch project](https://github.com/flipkart-incubator/zjsonpatch).
66

77
## Description & Use-Cases
8-
- Java Library to find / apply JSON Patches according to [RFC 6902](http://tools.ietf.org/html/rfc6902).
8+
- Java Library to find / apply JSON Patches according to [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902).
99
- JSON Patch defines a JSON document structure for representing changes to a JSON document.
1010
- It can be used to avoid sending a whole document when only a part has changed, thus reducing network bandwidth requirements if data (in JSON format) is required to send across multiple systems over network or in case of multi DC transfer.
11-
- This library compares two [BsonValue](http://mongodb.github.io/mongo-java-driver/3.6/javadoc/org/bson/BsonValue.html) inputs and produces a [BsonArray](http://mongodb.github.io/mongo-java-driver/3.6/javadoc/org/bson/BsonArray.html) of the changes.
11+
- When used in combination with the HTTP PATCH method as per [RFC 5789 HTTP PATCH](https://datatracker.ietf.org/doc/html/rfc5789), it will do partial updates for HTTP APIs in a standard way.
12+
- This library compares two [BsonValue](https://mongodb.github.io/mongo-java-driver/3.12/javadoc/org/bson/BsonValue.html) inputs and produces a [BsonArray](https://mongodb.github.io/mongo-java-driver/3.12/javadoc/org/bson/BsonArray.html) of the changes.
1213

1314

1415
### Compatible with : Java 8 and above all versions
@@ -20,15 +21,15 @@ The code here was ported (copied, renamed, repackaged, modified) from the [zjson
2021

2122
### How to use:
2223

23-
### Current Version : 0.4.9
24+
### Current Version : 0.4.12
2425

2526
Add following to `<dependencies/>` section of your pom.xml -
2627

2728
```xml
2829
<dependency>
2930
<groupId>com.ebay.bsonpatch</groupId>
3031
<artifactId>bsonpatch</artifactId>
31-
<version>0.4.9</version>
32+
<version>0.4.12</version>
3233
</dependency>
3334
```
3435

@@ -42,7 +43,7 @@ Computes and returns a JSON `patch` (as a BsonArray) from `source` to `target`,
4243
Both `source` and `target` must be either valid BSON objects or arrays or values.
4344
Further, if resultant `patch` is applied to `source`, it will yield `target`.
4445

45-
The algorithm which computes this JsonPatch currently generates following operations as per [RFC 6902](https://tools.ietf.org/html/rfc6902) -
46+
The algorithm which computes this JsonPatch currently generates following operations as per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902#section-4) -
4647
- `add`
4748
- `remove`
4849
- `replace`

pom.xml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.ebay.bsonpatch</groupId>
88
<artifactId>bsonpatch</artifactId>
9-
<version>0.4.9</version>
9+
<version>0.4.12</version>
1010
<packaging>jar</packaging>
1111

1212
<name>${project.groupId}:${project.artifactId}</name>
@@ -47,7 +47,7 @@
4747
<plugin>
4848
<groupId>org.apache.maven.plugins</groupId>
4949
<artifactId>maven-compiler-plugin</artifactId>
50-
<version>3.8.1</version>
50+
<version>3.10.1</version>
5151
<configuration>
5252
<source>1.8</source>
5353
<target>1.8</target>
@@ -61,7 +61,7 @@
6161
<plugin>
6262
<groupId>org.apache.maven.plugins</groupId>
6363
<artifactId>maven-source-plugin</artifactId>
64-
<version>3.0.1</version>
64+
<version>3.2.1</version>
6565
<executions>
6666
<execution>
6767
<id>attach-sources</id>
@@ -118,7 +118,7 @@
118118
<plugin>
119119
<groupId>org.apache.maven.plugins</groupId>
120120
<artifactId>maven-javadoc-plugin</artifactId>
121-
<version>2.10.4</version>
121+
<version>3.3.2</version>
122122
<executions>
123123
<execution>
124124
<id>attach-javadocs</id>
@@ -137,25 +137,25 @@
137137
<dependency>
138138
<groupId>org.mongodb</groupId>
139139
<artifactId>mongo-java-driver</artifactId>
140-
<version>3.11.2</version>
140+
<version>3.12.10</version>
141141
</dependency>
142142
<dependency>
143143
<groupId>org.apache.commons</groupId>
144144
<artifactId>commons-collections4</artifactId>
145-
<version>4.2</version>
145+
<version>4.3</version>
146146
</dependency>
147147
<!-- For IOUtils.toString(inputStream, charset) and StringBuilderWriter -->
148148
<dependency>
149149
<scope>test</scope>
150150
<groupId>commons-io</groupId>
151151
<artifactId>commons-io</artifactId>
152-
<version>2.6</version>
152+
<version>2.7</version>
153153
</dependency>
154154
<dependency>
155155
<scope>test</scope>
156156
<groupId>junit</groupId>
157157
<artifactId>junit</artifactId>
158-
<version>4.12</version>
158+
<version>4.13.1</version>
159159
</dependency>
160160
</dependencies>
161161
</project>

src/main/java/com/ebay/bsonpatch/BsonDiff.java

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,29 @@ public static BsonArray asBson(final BsonValue source, final BsonValue target) {
4848

4949
public static BsonArray asBson(final BsonValue source, final BsonValue target, EnumSet<DiffFlags> flags) {
5050
BsonDiff diff = new BsonDiff(flags);
51-
52-
// generating diffs in the order of their occurrence
53-
diff.generateDiffs(JsonPointer.ROOT, source, target);
54-
55-
if (!flags.contains(DiffFlags.OMIT_MOVE_OPERATION)) {
56-
// Merging remove & add to move operation
57-
diff.introduceMoveOperation();
51+
if (source == null && target != null) {
52+
// return add node at root pointing to the target
53+
diff.diffs.add(Diff.generateDiff(Operation.ADD, JsonPointer.ROOT, target));
5854
}
59-
60-
if (!flags.contains(DiffFlags.OMIT_COPY_OPERATION)) {
61-
// Introduce copy operation
62-
diff.introduceCopyOperation(source, target);
55+
if (source != null && target == null) {
56+
// return remove node at root pointing to the source
57+
diff.diffs.add(Diff.generateDiff(Operation.REMOVE, JsonPointer.ROOT, source));
6358
}
59+
if (source != null && target != null) {
60+
diff.generateDiffs(JsonPointer.ROOT, source, target);
61+
62+
if (!flags.contains(DiffFlags.OMIT_MOVE_OPERATION))
63+
// Merging remove & add to move operation
64+
diff.introduceMoveOperation();
65+
66+
if (!flags.contains(DiffFlags.OMIT_COPY_OPERATION))
67+
// Introduce copy operation
68+
diff.introduceCopyOperation(source, target);
6469

70+
if (flags.contains(DiffFlags.ADD_EXPLICIT_REMOVE_ADD_ON_REPLACE))
71+
// Split replace into remove and add instructions
72+
diff.introduceExplicitRemoveAndAddOperation();
73+
}
6574
return diff.getBsonNodes();
6675
}
6776

@@ -212,6 +221,26 @@ private void introduceMoveOperation() {
212221
}
213222
}
214223

224+
/**
225+
* This method splits a {@link Operation#REPLACE} operation within a diff into a {@link Operation#REMOVE}
226+
* and {@link Operation#ADD} in order, respectively.
227+
* Does nothing if {@link Operation#REPLACE} op does not contain a from value
228+
*/
229+
private void introduceExplicitRemoveAndAddOperation() {
230+
List<Diff> updatedDiffs = new ArrayList<Diff>();
231+
for (Diff diff : diffs) {
232+
if (!diff.getOperation().equals(Operation.REPLACE) || diff.getSrcValue() == null) {
233+
updatedDiffs.add(diff);
234+
continue;
235+
}
236+
//Split into two #REMOVE and #ADD
237+
updatedDiffs.add(new Diff(Operation.REMOVE, diff.getPath(), diff.getSrcValue()));
238+
updatedDiffs.add(new Diff(Operation.ADD, diff.getPath(), diff.getValue()));
239+
}
240+
diffs.clear();
241+
diffs.addAll(updatedDiffs);
242+
}
243+
215244
//Note : only to be used for arrays
216245
//Finds the longest common Ancestor ending at Array
217246
private static JsonPointer computeRelativePath(JsonPointer path, int startIdx, int endIdx, List<Diff> diffs) {

src/main/java/com/ebay/bsonpatch/CompatibilityFlags.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
public enum CompatibilityFlags {
2525
MISSING_VALUES_AS_NULLS,
2626
REMOVE_NONE_EXISTING_ARRAY_ELEMENT,
27-
ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE;
27+
ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE,
28+
FORBID_REMOVE_MISSING_OBJECT;
2829

2930
public static EnumSet<CompatibilityFlags> defaults() {
3031
return EnumSet.noneOf(CompatibilityFlags.class);

src/main/java/com/ebay/bsonpatch/DiffFlags.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.EnumSet;
2323

2424
public enum DiffFlags {
25+
2526
/**
2627
* This flag omits the <i>value</i> field on remove operations.
2728
* This is a default flag.
@@ -51,14 +52,32 @@ public enum DiffFlags {
5152
* <i>fromValue</i> represents the the value replaced by a {@link Operation#REPLACE}
5253
* operation, in other words, the original value. This can be useful for debugging
5354
* output or custom processing of the diffs by downstream systems.
54-
*
5555
* Please note that this is a non-standard extension to RFC 6902 and will not affect
5656
* how patches produced by this library are processed by this or other libraries.
5757
*
5858
* @since 0.4.1
5959
*/
6060
ADD_ORIGINAL_VALUE_ON_REPLACE,
61-
61+
62+
/**
63+
* This flag normalizes a {@link Operation#REPLACE} operation into its respective
64+
* {@link Operation#REMOVE} and {@link Operation#ADD} operations. Although it adds
65+
* a redundant step, this can be useful for auditing systems in which immutability
66+
* is a requirement.
67+
* <p>
68+
* For the flag to work, {@link DiffFlags#ADD_ORIGINAL_VALUE_ON_REPLACE} has to be
69+
* enabled as the new instructions in the patch need to grab the old <i>fromValue</i>
70+
* {@code "op": "replace", "fromValue": "F1", "value": "F2" }
71+
* The above instruction will be split into
72+
* {@code "op":"remove", "value":"F1" } and {@code "op":"add", "value":"F2"} respectively.
73+
* <p>
74+
* Please note that this is a non-standard extension to RFC 6902 and will not affect
75+
* how patches produced by this library are processed by this or other libraries.
76+
*
77+
* @since 0.4.11
78+
*/
79+
ADD_EXPLICIT_REMOVE_ADD_ON_REPLACE,
80+
6281
/**
6382
* This flag instructs the diff generator to emit {@link Operation#TEST} operations
6483
* that validate the state of the source document before each mutation. This can be

src/main/java/com/ebay/bsonpatch/InPlaceApplyProcessor.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,17 @@ public void remove(JsonPointer path) throws JsonPointerEvaluationException {
125125

126126
BsonValue parentNode = path.getParent().evaluate(target);
127127
JsonPointer.RefToken token = path.last();
128-
if (parentNode.isDocument())
128+
if (parentNode.isDocument()) {
129+
if (flags.contains(CompatibilityFlags.FORBID_REMOVE_MISSING_OBJECT) && !parentNode.asDocument().containsKey(token.getField()))
130+
throw new BsonPatchApplicationException(
131+
"Missing field " + token.getField(), Operation.REMOVE, path.getParent());
129132
parentNode.asDocument().remove(token.getField());
133+
}
130134
else if (parentNode.isArray()) {
131-
if (!flags.contains(CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT) && token.getIndex() >= parentNode.asArray().size()) {
132-
135+
if (!flags.contains(CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT) &&
136+
token.getIndex() >= parentNode.asArray().size()) {
133137
throw new BsonPatchApplicationException(
134-
"Array index " + token.getIndex() + " out of bounds", Operation.REPLACE, path.getParent());
138+
"Array index " + token.getIndex() + " out of bounds", Operation.REMOVE, path.getParent());
135139
} else if (token.getIndex() >= parentNode.asArray().size()) {
136140
// do nothing, don't get upset about index out of bounds if REMOVE_NONE_EXISTING_ARRAY_ELEMENT set
137141
// can't just call remove on BsonArray because it throws index out of bounds exception
@@ -140,7 +144,7 @@ else if (parentNode.isArray()) {
140144
}
141145
} else {
142146
throw new BsonPatchApplicationException(
143-
"Cannot reference past scalar value", Operation.REPLACE, path.getParent());
147+
"Cannot reference past scalar value", Operation.REMOVE, path.getParent());
144148
}
145149
}
146150

src/test/java/com/ebay/bsonpatch/CompatibilityTest.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package com.ebay.bsonpatch;
2121

2222
import static com.ebay.bsonpatch.CompatibilityFlags.ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE;
23+
import static com.ebay.bsonpatch.CompatibilityFlags.FORBID_REMOVE_MISSING_OBJECT;
2324
import static com.ebay.bsonpatch.CompatibilityFlags.MISSING_VALUES_AS_NULLS;
2425
import static com.ebay.bsonpatch.CompatibilityFlags.REMOVE_NONE_EXISTING_ARRAY_ELEMENT;
2526
import static org.hamcrest.core.IsEqual.equalTo;
@@ -30,6 +31,7 @@
3031

3132
import org.bson.BsonArray;
3233
import org.bson.BsonDocument;
34+
import org.bson.BsonValue;
3335
import org.junit.Before;
3436
import org.junit.Test;
3537

@@ -39,13 +41,15 @@ public class CompatibilityTest {
3941
BsonArray replaceNodeWithMissingValue;
4042
BsonArray removeNoneExistingArrayElement;
4143
BsonArray replaceNode;
44+
BsonArray removeNode;
4245

4346
@Before
4447
public void setUp() throws Exception {
4548
addNodeWithMissingValue = BsonArray.parse("[{\"op\":\"add\",\"path\":\"/a\"}]");
4649
replaceNodeWithMissingValue = BsonArray.parse("[{\"op\":\"replace\",\"path\":\"/a\"}]");
4750
removeNoneExistingArrayElement = BsonArray.parse("[{\"op\": \"remove\",\"path\": \"/b/0\"}]");
4851
replaceNode = BsonArray.parse("[{\"op\":\"replace\",\"path\":\"/a\",\"value\":true}]");
52+
removeNode = BsonArray.parse("[{\"op\":\"remove\",\"path\":\"/b\"}]");
4953
}
5054

5155
@Test
@@ -87,4 +91,17 @@ public void withFlagReplaceShouldAddValueWhenMissingInTarget() throws Exception
8791
BsonDocument result = BsonPatch.apply(replaceNode, BsonDocument.parse("{}"), EnumSet.of(ALLOW_MISSING_TARGET_OBJECT_ON_REPLACE)).asDocument();
8892
assertThat(result, equalTo(expected));
8993
}
90-
}
94+
95+
@Test(expected = BsonPatchApplicationException.class)
96+
public void withFlagRemoveMissingValueShouldThrow() throws Exception {
97+
BsonDocument source = BsonDocument.parse("{\"a\": true}");
98+
BsonPatch.apply(removeNode, source, EnumSet.of(FORBID_REMOVE_MISSING_OBJECT));
99+
}
100+
101+
@Test
102+
public void withFlagRemoveShouldRemove() throws Exception {
103+
BsonDocument source = BsonDocument.parse("{\"b\": true}");
104+
BsonDocument expected = BsonDocument.parse("{}");
105+
BsonValue result = BsonPatch.apply(removeNode, source, EnumSet.of(FORBID_REMOVE_MISSING_OBJECT));
106+
assertThat(result, equalTo(expected));
107+
}}

0 commit comments

Comments
 (0)