package act.util; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * #L% */ import act.data.util.StringOrPattern; import com.alibaba.fastjson.serializer.JSONSerializer; import com.alibaba.fastjson.serializer.PropertyPreFilter; import org.osgl.util.C; import org.osgl.util.FastStr; import org.osgl.util.S; import java.util.*; import java.util.regex.Pattern; /** * Extend the function of {@link com.alibaba.fastjson.serializer.SimplePropertyPreFilter} * so it can properly handle the property filters defined with hierarchies, e.g. "foo.bar.name" */ public class FastJsonPropertyPreFilter implements PropertyPreFilter { /** * The properties separator pattern {@code [,;:]+} */ public static final Pattern PROPERTY_SEPARATOR = Pattern.compile("[,;:]+"); private final Set<String> includes = new HashSet<String>(); private final Set<String> excludes = new HashSet<String>(); private final List<String> fullPaths = C.newList(); public FastJsonPropertyPreFilter(String... properties) { super(); addIncludes(properties); } public void setFullPaths(List<String> ls) { fullPaths.clear(); fullPaths.addAll(ls); } /** * Add name/path of the properties to be exported * <p> * It supports adding multiple properties in one string separated by * the {@link #PROPERTY_SEPARATOR} * </p> * <p> * It can add a multiple level path separated by "{@code .}" or "{@code /} * e.g. "{@code foo/bar}" or "{@code foo.bar}" * </p> * * @param properties the properties */ public void addIncludes(String... properties) { addTo(includes, properties); } /** * Add name/path of the properties to be exported * <p> * It supports adding multiple properties in one string separated by * the {@link #PROPERTY_SEPARATOR} * </p> * <p> * It can add a multiple level path separated by "{@code .}" or "{@code /} * e.g. "{@code foo/bar}" or "{@code foo.bar}" * </p> * * @param properties the properties */ public void addIncludes(Collection<String> properties) { String[] sa = new String[properties.size()]; addIncludes(properties.toArray(sa)); } /** * Add name/path of the properties to be banned * <p> * It supports adding multiple properties in one string separated by * the {@link #PROPERTY_SEPARATOR} * </p> * <p> * It can add a multiple level path separated by "{@code .}" or "{@code /} * e.g. "{@code foo/bar}" or "{@code foo.bar}" * </p> * * @param properties the properties */ public void addExcludes(String... properties) { addTo(excludes, properties); } /** * Add name/path of the properties to be banned * <p> * It supports adding multiple properties in one string separated by * the {@link #PROPERTY_SEPARATOR} * </p> * <p> * It can add a multiple level path separated by "{@code .}" or "{@code /} * e.g. "{@code foo/bar}" or "{@code foo.bar}" * </p> * * @param properties the properties */ public void addExcludes(Set<String> properties) { String[] sa = new String[properties.size()]; addTo(excludes, properties.toArray(sa)); } private void addTo(Set<String> set, String... properties) { for (String s : properties) { if (S.blank(s)) { continue; } String[] sa = PROPERTY_SEPARATOR.split(s); for (String s0 : sa) { addOneTo(set, s0); } } } private void addOneTo(Set<String> set, String path) { // use path's canonical form if (path.contains("/")) { path = path.replace('/', '.'); } set.add(path); } @Override public boolean apply(JSONSerializer serializer, Object source, String name) { if (source == null) { return true; } // if context path is "$.bar.zee" or "$[0].bar.zee" and name is "foo" // then path should be "bar.zee.foo" String path; FastStr fs = FastStr.of(serializer.getContext().toString()).append('.').append(name); path = fs.substring(fs.indexOf('.') + 1); // skip the first "." return !matches(excludes, path, true) && (includes.isEmpty() || matches(includes, path, false)); } private static final Pattern SQUARE_BLOCK = Pattern.compile("\\[.*\\]"); private boolean matches(Set<String> paths, String path, boolean exclude) { if (path.contains("[")) { path = SQUARE_BLOCK.matcher(path).replaceAll(""); } if (paths.contains(path)) { return true; } for (String s : paths) { if (path.startsWith(S.concat(s, "."))) { return true; } } if (hasPattern(paths)) { return patternMatches(paths, path, exclude); } if (exclude) { return false; } path = path + "."; for (String s : paths) { if (s.startsWith(path)) { return true; } } return false; } public static boolean hasPattern(Collection<String> paths) { return S.join("", paths).contains("*"); } private static Map<Collection<String>, List<StringOrPattern>> spCache = C.newMap(); private boolean patternMatches(Set<String> paths, String path, boolean exclude) { List<StringOrPattern> spList = spList(paths); for (StringOrPattern sp : spList) { if (sp.matches(path)) { return true; } else if (!exclude && sp.isPattern()) { // check if it is the case that path is still at the upper level Pattern p = Pattern.compile(path + "(\\.)?" + sp.s()); for (String fp : fullPaths) { if (p.matcher(fp).matches()) { return true; } } } } return false; } private static List<StringOrPattern> spList(Collection<String> strings) { List<StringOrPattern> ret = spCache.get(strings); if (null == ret) { ret = C.newList(); for (String s : strings) { ret.add(new StringOrPattern(s)); } spCache.put(strings, ret); } return ret; } }