From b4478380ec3408b63bd3e2f89334310bead0987d Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Tue, 15 Feb 2022 19:02:00 +0100 Subject: [PATCH] Extending JSONPath Support --- geoportal-common/pom.xml | 2 +- .../common/model/JSONPathWrapper.java | 191 +++++++++++++++--- .../lifecycle/LifecycleInformation.java | 2 + .../geoportal/common/JacksonProvider.java | 38 ++++ .../geoportal/common/model/JSONPathTests.java | 147 ++++++++++++++ .../src/test/resources/logback.xml | 18 ++ 6 files changed, 368 insertions(+), 30 deletions(-) create mode 100644 geoportal-common/src/test/java/org/gcube/application/geoportal/common/JacksonProvider.java create mode 100644 geoportal-common/src/test/java/org/gcube/application/geoportal/common/model/JSONPathTests.java create mode 100644 geoportal-common/src/test/resources/logback.xml diff --git a/geoportal-common/pom.xml b/geoportal-common/pom.xml index 4c429e6..513c053 100644 --- a/geoportal-common/pom.xml +++ b/geoportal-common/pom.xml @@ -44,7 +44,7 @@ com.jayway.jsonpath json-path - 2.4.0 + 2.7.0 diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/JSONPathWrapper.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/JSONPathWrapper.java index a03532b..b40869e 100644 --- a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/JSONPathWrapper.java +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/JSONPathWrapper.java @@ -2,18 +2,23 @@ package org.gcube.application.geoportal.common.model; import com.jayway.jsonpath.*; +import com.mongodb.util.JSON; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.gcube.application.geoportal.common.JSONSerializationProvider; import org.reflections.Reflections; import org.reflections.util.ConfigurationBuilder; import org.reflections.util.FilterBuilder; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Slf4j public class JSONPathWrapper { + public static Configuration JSON_PATH_ALWAYS_LIST_CONFIG=null; public static Configuration JSON_PATH_PATHS_CONFIGURATION=null; @@ -44,48 +49,176 @@ public class JSONPathWrapper { @Getter - DocumentContext ctx=null; + DocumentContext valueCTX =null; + DocumentContext pathsCTX =null; + public JSONPathWrapper(String json) { - ctx=JsonPath.using(JSON_PATH_ALWAYS_LIST_CONFIG).parse(json); + + valueCTX =JsonPath.using(JSON_PATH_ALWAYS_LIST_CONFIG).parse(json); + pathsCTX =JsonPath.using(JSON_PATH_PATHS_CONFIGURATION).parse(json); } public List getByPath(String path){ - return ctx.read(path); + return valueCTX.read(path); } + public List getMatchingPaths(String path){return pathsCTX.read(path); } + public List getByPath(String path,Class clazz){ - List l= ctx.read(path, new TypeRef>() {}); + List l= valueCTX.read(path, new TypeRef>() {}); l.removeIf(p->p==null); return l; } - public JSONPathWrapper set(String path, Object toSet){ - ctx.set(path,toSet); + /** + * Changes the value of an existent field at @path + * + * @param path + * @param toSet + * @return + */ + public JSONPathWrapper setElement(String path, Object toSet){ + log.debug("Setting Path {} = {}",path,toSet); + JsonPath jPath=JsonPath.compile(path); + if(jPath.isDefinite()){ + valueCTX.set(path,toSet); + } else{ + log.debug("Path is not definte, evaluating matching paths.."); + for(String p : getMatchingPaths(path)){ + setElement(p,toSet); + } + } return this; } -// public static final DocumentContext addElement(DocumentContext ctx,String path) throws JsonPathException{ -// JsonPath jPath=JsonPath.compile(path); -// if(jPath.isDefinite()) { -// String parent=path.substring(0,path.lastIndexOf(".")); -// List found=ctx.read(parent); -// if(found==null || found.size()==0 || found.get(0)==null) { -// //missing parent, use recursion -// addElement(ctx,parent); -// } -// // found parent, adding element -// String element=path.substring(path.lastIndexOf(".")+1); -// -// Object value=new HashMap(); -// if(element.contains("[")) { -// value=new ArrayList(Collections.singletonList(value)); -// -// element=element.substring(0,element.indexOf("[")); -// } -// -// ctx.put(parent, element, value); -// return ctx; -// }else throw new JsonPathException("Unable to initialize non-definite path : "+path); -// } + /** + * Creates a new element @elementName == @toSet as child of all matching @path + * + * @param path + * @param elementName + * @param toSet + * @return + */ + public JSONPathWrapper putElement(String path,String elementName, Object toSet){ + log.debug("Putting {} = {} at Path {}",elementName,toSet,path); + JsonPath jPath=JsonPath.compile(path); + if(jPath.isDefinite()){ + valueCTX.put(path,elementName,toSet); + } else{ + log.debug("Path is not definte, evaluating matching paths.."); + for(String p : getMatchingPaths(path)){ + putElement(p,elementName,toSet); + } + } + return this; + } + + /** + * Appends a new element @toSet as child of all arrays matching @path + * + * @param path + * @param toAdd + * @return + */ + public JSONPathWrapper addElementToArray(String path, Object toAdd){ + log.debug("Setting Path {} = {}",path,toAdd); + JsonPath jPath=JsonPath.compile(path); + if(jPath.isDefinite()){ + valueCTX.add(path,toAdd); + } else{ + log.debug("Path is not definte, evaluating matching paths.."); + for(String p : getMatchingPaths(path)){ + addElementToArray(p,toAdd); + } + } + return this; + } + + + //***** EXTENSIONS + + private static Pattern DOTTED_PATH_PATTERN=Pattern.compile("(\\$?\\.\\w*(\\[((\\?\\(.*\\))|(\\d+|\\*))\\])?)(?=(\\.|$)?)"); + private static Pattern BRACKET_PATH_PATTERN=Pattern.compile("(\\$?\\['\\w*'\\](\\[((\\?\\(.*\\))|(\\d+|\\*))\\])?)"); + + +/* private static final DocumentContext addElement(DocumentContext ctx,String path,Object value) throws JsonPathException{ + log.debug("Inserting object at {}",path); + JsonPath jPath=JsonPath.compile(path); + if(jPath.isDefinite()) { + List tokens=tokenizePath(path); + DocumentContext currentCtx=ctx; + String parentPath = null; + for(int i = 0; i foundElements = currentCtx.read(s); + foundElements.removeIf(v->(v==null)); + + if(foundElements.isEmpty()){ + Document newElement=null; + log.trace("{} not found.."); + // add child + if (s.matches("")){ + log.trace("{} is array, checking existence",s,value); + // ARRAY + // if array exists add elements else create new + List foundArray= currentCtx.read(elementName,List.class); + throw new RuntimeException("Implement array support"); + } else { + // Map + newElement = new Document(); + } + currentCtx.put("$",elementName,newElement); + log.trace("Set {}, json is {}",s,currentCtx.jsonString()); + currentCtx= JsonPath.parse(newElement.toJson()); + }else currentCtx = JsonPath.parse(currentCtx.read(s,Document.class)); + } + } return ctx; + }else throw new JsonPathException("Unable to initialize non-definite path : "+path); + } + +*/ + static List tokenizePath(String path){ + List toReturn=null; + log.debug("Tokenizing JSON Path {} ",path); + // if path is $.element.child + if(path.matches("(\\$\\.)?\\w.*")) + toReturn= getByRegex(path, DOTTED_PATH_PATTERN); + else if (path.matches("(\\$)?\\[\\'.*")) + toReturn = getByRegex(path, BRACKET_PATH_PATTERN); + + log.debug("Path {} as tokens {}",path,toReturn); + return toReturn; + } + + private static List getByRegex(String s,Pattern p){ + ArrayList toReturn = new ArrayList<>(); + Matcher m = p.matcher(s); + log.trace("Groups from {} with {} : ",s,p.pattern()); + while(m.find()) { + String found=m.group(); + toReturn.add(found); + } + return toReturn; + } + + static String extractFieldNameFromPathElement(String p){ + Matcher m=Pattern.compile("\\$?\\.?(\\[')?([a-zA-Z_]+)").matcher(p); + m.find(); + return m.group(2); + } + } diff --git a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/lifecycle/LifecycleInformation.java b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/lifecycle/LifecycleInformation.java index b5bd015..fa229c4 100644 --- a/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/lifecycle/LifecycleInformation.java +++ b/geoportal-common/src/main/java/org/gcube/application/geoportal/common/model/document/lifecycle/LifecycleInformation.java @@ -8,6 +8,8 @@ import java.util.List; @Data public class LifecycleInformation { + public static final String DRAFT_PHASE="DRAFT"; + public static final String PHASE="phase"; public static final String LAST_INVOKED_STEP="lastInvokedStep"; public static final String LAST_OPERATION_STATUS="lastOperationStatus"; diff --git a/geoportal-common/src/test/java/org/gcube/application/geoportal/common/JacksonProvider.java b/geoportal-common/src/test/java/org/gcube/application/geoportal/common/JacksonProvider.java new file mode 100644 index 0000000..f75d6bb --- /dev/null +++ b/geoportal-common/src/test/java/org/gcube/application/geoportal/common/JacksonProvider.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.common; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.Option; +import com.jayway.jsonpath.spi.json.JacksonJsonProvider; +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; + +public class JacksonProvider implements JSONSerializationProvider { + + + @Override + public void setJSONWrapperDefaults() { + Configuration.setDefaults(new Configuration.Defaults() { + private JsonProvider jacksonProvider = new JacksonJsonProvider(); + + private final MappingProvider mappingProvider = new JacksonMappingProvider(); + @Override + public JsonProvider jsonProvider() { + return jacksonProvider; + } + + @Override + public Set