diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..63420019 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,47 @@ +# Gradle +# Build your Java project and run tests with Gradle using a Gradle wrapper script. +# Add steps that analyze code, save build artifacts, deploy, and more: +# https://docs.microsoft.com/azure/devops/pipelines/languages/java + +trigger: +- master + +pool: + vmImage: ubuntu-latest + +steps: +- script: | + git checkout $(Build.SourceBranchName) + git config --global user.name "gravity9piotr" + displayName: "git config" +- task: MavenAuthenticate@0 + displayName: 'authenticate maven' + inputs: + artifactsFeeds: bt +- task: Gradle@2 + inputs: + workingDirectory: '' + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + testResultsFiles: '**/TEST-*.xml' + tasks: 'clean test' +- task: Gradle@2 + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + workingDirectory: '' + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + tasks: 'release -Prelease.useAutomaticVersion=true' +- task: PublishPipelineArtifact@1 + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + targetPath: $(System.DefaultWorkingDirectory)/build/libs/$(artifactName) + artifactName: artifact + displayName: "publish build artifact" diff --git a/build.gradle b/build.gradle index 8ffa1a34..f7c44b66 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { repositories { mavenCentral() maven { - url "http://repo.springsource.org/plugins-release"; + url "https://repo.springsource.org/plugins-release"; } } dependencies { @@ -32,6 +32,7 @@ buildscript { plugins { id("net.ltgt.errorprone") version "0.8.1" apply false + id 'net.researchgate.release' version '2.8.1' } configure(allprojects) { @@ -53,27 +54,13 @@ apply(from: "project.gradle"); group = "com.github.java-json-tools"; -ext.forRelease = !version.endsWith("-SNAPSHOT"); - /* * Repositories to use */ repositories { mavenCentral(); - if (!forRelease) { - maven { - url "https://oss.sonatype.org/content/repositories/snapshots" - } - } - /* Allow staging references for last pre-release testing. */ - if (project.properties.containsKey("sonatypeUsername")) { - maven { - url "https://oss.sonatype.org/service/local/staging/deploy/maven2" - credentials { - username = project.properties["sonatypeUsername"] - password = project.properties["sonatypePassword"] - } - } + maven { + url "https://pkgs.dev.azure.com/Gravity9Solutions/BT/_packaging/bt/maven/v1" } } @@ -117,15 +104,6 @@ allprojects { } } -/* - * Javadoc: we need to tell where the overview.html is, it will not pick it up - * automatically... - */ - -//javadoc { -// options.overview = "src/main/java/overview.html"; -//} - task javadocJar(type: Jar, dependsOn: javadoc) { classifier = "javadoc"; from javadoc.destinationDir; @@ -142,108 +120,4 @@ wrapper { distributionUrl = "https://services.gradle.org/distributions/gradle-${gradleVersion}-all.zip"; } -task pom { - doLast { - pom {}.writeTo("${projectDir}/pom.xml"); - } -} - -/* - * SIGNING - */ - -project.ext { - scmUrl = sprintf("git@github.com:java-json-tools/%s.git", name); - projectURL = sprintf("https://github.com/java-json-tools/%s", name); - sonatypeStaging = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"; - sonatypeSnapshots = "https://oss.sonatype.org/content/repositories/snapshots/"; -}; - -task checkSigningRequirements { - doLast { - def requiredProperties = [ "sonatypeUsername", "sonatypePassword" ]; - def noDice = false; - requiredProperties.each { - if (project.properties[it] == null) { - noDice = true; - System.err.printf("property \"%s\" is not defined!\n", it); - } - } - if (noDice) - throw new IllegalStateException("missing required properties for " + - "upload"); - } -} - -uploadArchives { - dependsOn(checkSigningRequirements); - repositories { - mavenDeployer { - beforeDeployment { - MavenDeployment deployment -> signing.signPom(deployment); - } - - repository(url: "${sonatypeStaging}") { - authentication( - userName: project.properties["sonatypeUsername"], - password: project.properties["sonatypePassword"] - ); - } - - snapshotRepository(url: "${sonatypeSnapshots}") { - authentication( - userName: project.properties["sonatypeUsername"], - password: project.properties["sonatypePassword"] - ); - } - } - } -} - -/* - * Configure pom.xml on install, uploadArchives - */ -[ - install.repositories.mavenInstaller, - uploadArchives.repositories.mavenDeployer -]*.pom*.whenConfigured { pom -> - pom.project { - name "${project.name}"; - packaging "jar"; - description "${project.ext.description}"; - url "${projectURL}"; - - scm { - url "${scmUrl}"; - connection "${scmUrl}"; - developerConnection "scm:git:${scmUrl}"; - } - - licenses { - license { - name "Lesser General Public License, version 3 or greater"; - url "http://www.gnu.org/licenses/lgpl.html"; - distribution "repo"; - }; - license { - name "Apache Software License, version 2.0"; - url "http://www.apache.org/licenses/LICENSE-2.0"; - distribution "repo"; - } - } - - developers { - developer { - id "huggsboson"; - name "John Huffaker"; - email "jhuffaker+java-json-tools@gmail.com"; - } - } - } -} - -signing { - required { forRelease && gradle.taskGraph.hasTask("uploadArchives") }; - sign configurations.archives; -} diff --git a/project.gradle b/project.gradle index 8d571b35..1d30a33e 100644 --- a/project.gradle +++ b/project.gradle @@ -21,10 +21,10 @@ * Project-specific settings. Unfortunately we cannot put the name in there! */ group = "com.github.java-json-tools"; -version = "1.13"; +version = "2.0.0-SNAPSHOT"; sourceCompatibility = JavaVersion.VERSION_1_7; targetCompatibility = JavaVersion.VERSION_1_7; // defaults to sourceCompatibility -project.ext.description = "JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386) implementation in Java"; +project.ext.description = "JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386) implementation in Java, using extended TMF620 JsonPath syntax"; /* * List of dependencies @@ -32,8 +32,8 @@ project.ext.description = "JSON Patch (RFC 6902) and JSON Merge Patch (RFC 7386) dependencies { provided(group: "com.google.code.findbugs", name: "jsr305", version: "3.0.2"); compile(group: "com.fasterxml.jackson.core", name: "jackson-databind", version: "2.11.0"); + compile(group: 'com.jayway.jsonpath', name: 'json-path', version: '2.6.0') compile(group: "com.github.java-json-tools", name: "msg-simple", version: "1.2"); - compile(group: "com.github.java-json-tools", name: "jackson-coreutils", version: "2.0"); testCompile(group: "org.testng", name: "testng", version: "7.1.0") { exclude(group: "junit", module: "junit"); @@ -54,9 +54,9 @@ javadoc.options { } links("https://docs.oracle.com/javase/7/docs/api/"); links("https://www.javadoc.io/doc/com.google.code.findbugs/jsr305/3.0.2/"); - links("https://fasterxml.github.com/jackson-databind/javadoc/2.11/"); - links("https://fasterxml.github.com/jackson-core/javadoc/2.11/"); - links("https://fasterxml.github.com/jackson-annotations/javadoc/2.11/"); + links("https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/2.11.4/index.html"); + links("https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-core/2.11.4/index.html"); + links("https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.11.4/index.html"); links("https://www.javadoc.io/doc/com.google.guava/guava/28.2-android/"); links("https://java-json-tools.github.io/msg-simple/"); links("https://java-json-tools.github.io/jackson-coreutils/"); diff --git a/settings.gradle b/settings.gradle index ffd96855..a6a7a519 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,4 +17,4 @@ * - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt */ -rootProject.name = "json-patch"; +rootProject.name = "json-patch-query"; diff --git a/src/main/java/com/github/fge/jsonpatch/AddOperation.java b/src/main/java/com/github/fge/jsonpatch/AddOperation.java index 5e5fb57a..759fe5de 100644 --- a/src/main/java/com/github/fge/jsonpatch/AddOperation.java +++ b/src/main/java/com/github/fge/jsonpatch/AddOperation.java @@ -23,12 +23,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.github.fge.jackson.jsonpointer.JsonPointer; -import com.github.fge.jackson.jsonpointer.ReferenceToken; -import com.github.fge.jackson.jsonpointer.TokenResolver; - -import java.util.NoSuchElementException; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; /** @@ -65,78 +61,89 @@ * [ 1, 2, 3 ] * */ -public final class AddOperation - extends PathValueOperation -{ - private static final ReferenceToken LAST_ARRAY_ELEMENT - = ReferenceToken.fromRaw("-"); +public final class AddOperation extends PathValueOperation { + public static final String LAST_ARRAY_ELEMENT_SYMBOL = "-"; @JsonCreator - public AddOperation(@JsonProperty("path") final JsonPointer path, - @JsonProperty("value") final JsonNode value) - { + public AddOperation(@JsonProperty("path") final String path, + @JsonProperty("value") final JsonNode value) { super("add", path, value); } @Override - public JsonNode apply(final JsonNode node) - throws JsonPatchException - { - if (path.isEmpty()) + public JsonNode apply(final JsonNode node) throws JsonPatchException { + if (path.isEmpty()) { return value; - + } /* * Check the parent node: it must exist and be a container (ie an array * or an object) for the add operation to work. */ - final JsonNode parentNode = path.parent().path(node); - if (parentNode.isMissingNode()) - throw new JsonPatchException(BUNDLE.getMessage( - "jsonPatch.noSuchParent")); - if (!parentNode.isContainerNode()) - throw new JsonPatchException(BUNDLE.getMessage( - "jsonPatch.parentNotContainer")); - return parentNode.isArray() - ? addToArray(path, node) - : addToObject(path, node); - } + final String fullJsonPath = JsonPathParser.tmfStringToJsonPath(path); + final int lastDotIndex = fullJsonPath.lastIndexOf('.'); + final String newNodeName = fullJsonPath.substring(lastDotIndex + 1) + .replace("[", "").replace("]", ""); + final String pathToParent = fullJsonPath.substring(0, lastDotIndex); - private JsonNode addToArray(final JsonPointer path, final JsonNode node) - throws JsonPatchException - { - final JsonNode ret = node.deepCopy(); - final ArrayNode target = (ArrayNode) path.parent().get(ret); + final DocumentContext nodeContext = JsonPath.parse(node.deepCopy()); - final TokenResolver token = Iterables.getLast(path); + final JsonNode evaluatedJsonParents = nodeContext.read(pathToParent); + if (evaluatedJsonParents == null) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchParent")); + } + if (!evaluatedJsonParents.isContainerNode()) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer")); + } - if (token.getToken().equals(LAST_ARRAY_ELEMENT)) { - target.add(value); - return ret; + if (pathToParent.contains("[?(")) { // json filter result is always a list + for (int i = 0; i < evaluatedJsonParents.size(); i++) { + JsonNode parentNode = evaluatedJsonParents.get(i); + if (!parentNode.isContainerNode()) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.parentNotContainer")); + } + DocumentContext containerContext = JsonPath.parse(parentNode); + if (parentNode.isArray()) { + addToArray(containerContext, "$", newNodeName); + } else { + addToObject(containerContext, "$", newNodeName); + } + } + return nodeContext.read("$"); + } else { + return evaluatedJsonParents.isArray() + ? addToArray(nodeContext, pathToParent, newNodeName) + : addToObject(nodeContext, pathToParent, newNodeName); } + } - final int size = target.size(); - final int index; - try { - index = Integer.parseInt(token.toString()); - } catch (NumberFormatException ignored) { - throw new JsonPatchException(BUNDLE.getMessage( - "jsonPatch.notAnIndex")); + private JsonNode addToArray(final DocumentContext node, String jsonPath, String newNodeName) throws JsonPatchException { + if (newNodeName.equals(LAST_ARRAY_ELEMENT_SYMBOL)) { + return node.add(jsonPath, value).read("$", JsonNode.class); } - if (index < 0 || index > size) - throw new JsonPatchException(BUNDLE.getMessage( - "jsonPatch.noSuchIndex")); + final int size = node.read(jsonPath, JsonNode.class).size(); + final int index = verifyAndGetArrayIndex(newNodeName, size); + + ArrayNode updatedArray = node.read(jsonPath, ArrayNode.class).insert(index, value); + return "$".equals(jsonPath) ? updatedArray : node.set(jsonPath, updatedArray).read("$", JsonNode.class); + } - target.insert(index, value); - return ret; + private JsonNode addToObject(final DocumentContext node, String jsonPath, String newNodeName) { + return node + .put(jsonPath, newNodeName, value) + .read("$", JsonNode.class); } - private JsonNode addToObject(final JsonPointer path, final JsonNode node) - { - final TokenResolver token = Iterables.getLast(path); - final JsonNode ret = node.deepCopy(); - final ObjectNode target = (ObjectNode) path.parent().get(ret); - target.set(token.getToken().getRaw(), value); - return ret; + private int verifyAndGetArrayIndex(String stringIndex, int size) throws JsonPatchException { + int index; + try { + index = Integer.parseInt(stringIndex); + } catch (NumberFormatException ignored) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.notAnIndex")); + } + if (index < 0 || index > size) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchIndex")); + } + return index; } } diff --git a/src/main/java/com/github/fge/jsonpatch/CopyOperation.java b/src/main/java/com/github/fge/jsonpatch/CopyOperation.java index 9a5a75b9..5afef3cc 100644 --- a/src/main/java/com/github/fge/jsonpatch/CopyOperation.java +++ b/src/main/java/com/github/fge/jsonpatch/CopyOperation.java @@ -22,7 +22,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; -import com.github.fge.jackson.jsonpointer.JsonPointer; +import com.jayway.jsonpath.JsonPath; /** * JSON Patch {@code copy} operation @@ -40,24 +40,20 @@ * *

It is an error if {@code from} fails to resolve to a JSON value.

*/ -public final class CopyOperation - extends DualPathOperation -{ +public final class CopyOperation extends DualPathOperation { + @JsonCreator - public CopyOperation(@JsonProperty("from") final JsonPointer from, - @JsonProperty("path") final JsonPointer path) - { + public CopyOperation(@JsonProperty("from") final String from, @JsonProperty("path") final String path) { super("copy", from, path); } @Override - public JsonNode apply(final JsonNode node) - throws JsonPatchException - { - final JsonNode dupData = from.path(node).deepCopy(); - if (dupData.isMissingNode()) - throw new JsonPatchException(BUNDLE.getMessage( - "jsonPatch.noSuchPath")); + public JsonNode apply(final JsonNode node) throws JsonPatchException { + final String jsonPath = JsonPathParser.tmfStringToJsonPath(from); + final JsonNode dupData = JsonPath.parse(node.deepCopy()).read(jsonPath); + if (dupData == null) { + throw new JsonPatchException(BUNDLE.getMessage("jsonPatch.noSuchPath")); + } return new AddOperation(path, dupData).apply(node); } } diff --git a/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java b/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java index 91455bb4..41d4a49d 100644 --- a/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java +++ b/src/main/java/com/github/fge/jsonpatch/DualPathOperation.java @@ -25,60 +25,49 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import com.github.fge.jackson.jsonpointer.JsonPointer; import java.io.IOException; /** * Base class for JSON Patch operations taking two JSON Pointers as arguments */ -public abstract class DualPathOperation - extends JsonPatchOperation -{ +public abstract class DualPathOperation extends JsonPatchOperation { @JsonSerialize(using = ToStringSerializer.class) - protected final JsonPointer from; + protected final String from; /** * Protected constructor * - * @param op operation name + * @param op operation name * @param from source path * @param path destination path */ - protected DualPathOperation(final String op, final JsonPointer from, - final JsonPointer path) - { + protected DualPathOperation(final String op, final String from, final String path) { super(op, path); this.from = from; } @Override - public final void serialize(final JsonGenerator jgen, - final SerializerProvider provider) - throws IOException, JsonProcessingException - { + public final void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeStartObject(); jgen.writeStringField("op", op); - jgen.writeStringField("path", path.toString()); - jgen.writeStringField("from", from.toString()); + jgen.writeStringField("path", path); + jgen.writeStringField("from", from); jgen.writeEndObject(); } @Override - public final void serializeWithType(final JsonGenerator jgen, - final SerializerProvider provider, final TypeSerializer typeSer) - throws IOException, JsonProcessingException - { + public final void serializeWithType(final JsonGenerator jgen, final SerializerProvider provider, + final TypeSerializer typeSer) throws IOException, JsonProcessingException { serialize(jgen, provider); } - public final JsonPointer getFrom() { + public final String getFrom() { return from; } @Override - public final String toString() - { + public final String toString() { return "op: " + op + "; from: \"" + from + "\"; path: \"" + path + '"'; } } diff --git a/src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java b/src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java index 06a6a62d..de406eba 100644 --- a/src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java +++ b/src/main/java/com/github/fge/jsonpatch/JsonPatchOperation.java @@ -24,9 +24,17 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializable; -import com.github.fge.jackson.jsonpointer.JsonPointer; import com.github.fge.msgsimple.bundle.MessageBundle; import com.github.fge.msgsimple.load.MessageBundles; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.Option; +import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; +import com.jayway.jsonpath.spi.json.JsonProvider; +import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; +import com.jayway.jsonpath.spi.mapper.MappingProvider; + +import java.util.EnumSet; +import java.util.Set; import static com.fasterxml.jackson.annotation.JsonSubTypes.*; import static com.fasterxml.jackson.annotation.JsonTypeInfo.*; @@ -34,12 +42,12 @@ @JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "op") @JsonSubTypes({ - @Type(name = "add", value = AddOperation.class), - @Type(name = "copy", value = CopyOperation.class), - @Type(name = "move", value = MoveOperation.class), - @Type(name = "remove", value = RemoveOperation.class), - @Type(name = "replace", value = ReplaceOperation.class), - @Type(name = "test", value = TestOperation.class) + @Type(name = "add", value = AddOperation.class), + @Type(name = "copy", value = CopyOperation.class), + @Type(name = "move", value = MoveOperation.class), + @Type(name = "remove", value = RemoveOperation.class), + @Type(name = "replace", value = ReplaceOperation.class), + @Type(name = "test", value = TestOperation.class) }) /** @@ -56,11 +64,27 @@ * */ @JsonIgnoreProperties(ignoreUnknown = true) -public abstract class JsonPatchOperation - implements JsonSerializable -{ - protected static final MessageBundle BUNDLE - = MessageBundles.getBundle(JsonPatchMessages.class); +public abstract class JsonPatchOperation implements JsonSerializable { + protected static final MessageBundle BUNDLE = MessageBundles.getBundle(JsonPatchMessages.class); + + static { + Configuration.setDefaults(new Configuration.Defaults() { + @Override + public JsonProvider jsonProvider() { + return new JacksonJsonNodeJsonProvider(); + } + + @Override + public Set