/* * 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. */ package com.addthis.hydra.data.filter.bundle; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.concurrent.ConcurrentHashMap; import com.addthis.basis.collect.HotMap; import com.addthis.basis.util.LessStrings; import com.addthis.bundle.core.Bundle; import com.addthis.bundle.util.AutoField; import com.addthis.bundle.value.ValueFactory; import com.addthis.bundle.value.ValueObject; import com.addthis.codec.annotations.FieldConfig; import com.addthis.maljson.JSONArray; import com.addthis.maljson.JSONObject; /** * This {@link BundleFilter BundleFilter} <span class="hydra-summary">does something with JSON</span>. * <p/> * <p/> * <p>Example:</p> * <pre> * </pre> * * @user-reference */ public class BundleFilterJSON implements BundleFilter { public static BundleFilterJSON create(String json, String set, BundleFilterTemplate query) { BundleFilterJSON bfj = new BundleFilterJSON(); bfj.json = AutoField.newAutoField(json); bfj.set = AutoField.newAutoField(set); bfj.query = query; return bfj; } @FieldConfig(codable = true) private int cache = 100; @FieldConfig(codable = true) private boolean trim; @FieldConfig(codable = true, required = true) private AutoField json; @FieldConfig(codable = true, required = true) private AutoField set; @FieldConfig(codable = true, required = true) private BundleFilterTemplate query; private HotMap<String, Object> objCache = new HotMap<>(new ConcurrentHashMap()); private HotMap<String, ArrayList<QueryToken>> tokCache = new HotMap<>(new ConcurrentHashMap()); @Override public boolean filter(Bundle bundle) { ValueObject bundleValue = json.getValue(bundle); switch (bundleValue.getObjectType()) { case STRING: case INT: case FLOAT: break; default: return true; } String value = bundleValue.asString().toString(); if (LessStrings.isEmpty(value)) { return true; } if (trim) { value = value.trim(); } try { Object o = null; synchronized (objCache) { o = objCache.get(value); } if (o == null) { if (value.charAt(0) == '{' && value.endsWith("}")) { o = new JSONObject(value); } else if (value.charAt(0) == '[' && value.endsWith("]")) { o = new JSONArray(value); } else { throw new Exception("Not a JSON Object : " + value); } synchronized (objCache) { objCache.put(value, o); while (objCache.size() > cache) { objCache.removeEldest(); } } } String qkey = query.template(bundle); ArrayList<QueryToken> tokens = null; synchronized (tokCache) { tokens = tokCache.get(qkey); } if (tokens == null) { tokens = tokenize(qkey); synchronized (tokCache) { tokCache.put(qkey, tokens); } } String qval = query(tokens, o, 0); if (qval != null) { set.setValue(bundle, ValueFactory.create(qval)); } } catch (Exception ex) { ex.printStackTrace(); } return true; } private String query(ArrayList<QueryToken> tokens, Object o, int index) { if (index < tokens.size()) { QueryToken qt = tokens.get(index); if (qt.index != null) { if (qt.field != null) { return query(tokens, ((JSONObject) o).optJSONArray(qt.field).opt(qt.index), index + 1); } else { return query(tokens, ((JSONArray) o).opt(qt.index), index + 1); } } else { return query(tokens, ((JSONObject) o).opt(qt.field), index + 1); } } return o.toString(); } private ArrayList<QueryToken> tokenize(String query) { ArrayList<QueryToken> tokens = new ArrayList<>(); QueryToken current = new QueryToken(); StringTokenizer st = new StringTokenizer(query, ".[", true); while (st.hasMoreTokens()) { String tok = st.nextToken(); if (tok.equals(".")) { tokens.add(current); current = new QueryToken(); continue; } if (tok.equals("[")) { tok = st.hasMoreTokens() ? st.nextToken() : ""; if (tok.endsWith("]")) { current.index = Integer.parseInt(tok.substring(0, tok.length() - 1)); } } else { current.field = tok; } } tokens.add(current); return tokens; } private static class QueryToken { String field; Integer index; @Override public String toString() { return index != null ? field + "[" + index + "]" : field; } } }