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 { /** * Based on * * https://github.com/json-path/JsonPath */ public static Configuration JSON_PATH_ALWAYS_LIST_CONFIG=null; public static Configuration JSON_PATH_PATHS_CONFIGURATION=null; static { Reflections reflections = new Reflections( new ConfigurationBuilder() .forPackage("org.gcube.application") .filterInputsBy(new FilterBuilder().includePackage("org.gcube.application"))); reflections.getSubTypesOf(JSONSerializationProvider.class).iterator().forEachRemaining(providerClass->{ if(!providerClass.isInterface()){ try { log.warn("Loading JSON Provider {} ",providerClass); JSONSerializationProvider provider = providerClass.newInstance(); provider.setJSONWrapperDefaults(); }catch (Throwable t){ log.error("Unable to instantiate provider "+providerClass,t); } } }); JSON_PATH_ALWAYS_LIST_CONFIG= Configuration.builder().options(Option.ALWAYS_RETURN_LIST,Option.SUPPRESS_EXCEPTIONS,Option.DEFAULT_PATH_LEAF_TO_NULL).build(); JSON_PATH_PATHS_CONFIGURATION = Configuration.builder().options(Option.AS_PATH_LIST,Option.SUPPRESS_EXCEPTIONS,Option.DEFAULT_PATH_LEAF_TO_NULL).build(); } @Getter DocumentContext valueCTX =null; DocumentContext pathsCTX =null; public JSONPathWrapper(String 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 getByPath(path,Object.class); } public List getMatchingPaths(String path){ List l=pathsCTX.read(path); l.removeIf(p->p==null); return l; } public List getByPath(String path,Class clazz){ List l= valueCTX.read(path, new TypeRef>() {}); l.removeIf(p->p==null); return l; } /** * 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)){ log.debug("Actually setting {} as {} ",p,toSet); setElement(p,toSet); } } return this; } /** * 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); } }