Java tutorial
/* * Copyright 2014-2018 JKOOL, LLC. * * 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.jkoolcloud.tnt4j.streams.format; import java.util.*; import org.apache.commons.collections4.MapUtils; import com.jkoolcloud.tnt4j.core.Operation; import com.jkoolcloud.tnt4j.core.Property; import com.jkoolcloud.tnt4j.core.Snapshot; import com.jkoolcloud.tnt4j.core.Trackable; import com.jkoolcloud.tnt4j.utils.Utils; /** * This class provides key/value formatting for tnt4j activities, events and snapshots. The output format follows the * following format: * <p> * {@code "OBJ:object-path\name1=value1,...,object-path\nameN=valueN"}. * </p> * Newline is added at the end of each line. * <p> * This formatter supports the following configuration properties (in addition to those supported by * {@link com.jkoolcloud.tnt4j.streams.format.FactNameValueFormatter}): * <ul> * <li>DuplicateKeySuffix - suffix value for duplicate path level keys. Default value - {@code "_"}. (Optional)</li> * <li>PathLevelAttributes - configures produced path tokens sequence. Format is: * {@code event.formatter.PathLevelAttributes[.CONDITION]: fieldName1; fieldName2, fieldName4; fieldName6;...;fieldNameN}, * where: * <ul> * <li>{@code CONDITION} - path build condition having format {@code fieldName.fieldValue} (Optional)</li> * <li>{@code fieldNameX} - name of activity entity field or property</li> * <li>{@code ;} - path level delimiter</li> * <li>{@code ,} - path token field/property names delimiter (path adds first found non-null value)</li> * </ul> * E.g. {@code event.formatter.PathLevelAttributes.CastIronType.Jobs: CastIronType; CastIronStatus; Resource; Name} * {@code event.formatter.PathLevelAttributes.CastIronType.Logs: CastIronType; Name; Severity}. Default value - * {@code Name}. (Optional)</li> * </ul> * * @version $Revision: 1 $ * * @see com.jkoolcloud.tnt4j.core.Trackable#getFieldValue(String) */ public class FactPathValueFormatter extends FactNameValueFormatter { /** * Default duplicate path level key values suffix {@value}. */ public static final String UNIQUE_SUFFIX = "_"; // NON-NLS private String uniqueSuffix = UNIQUE_SUFFIX; private Map<Condition, String[][]> pathLevelAttrKeys = new TreeMap<>(); private Comparator<Snapshot> snapshotComparator; private Comparator<Property> propertyComparator; /** * Constructs a new instance of {@code FactPathValueFormatter}. */ public FactPathValueFormatter() { super(); } @Override protected Collection<Snapshot> getSnapshots(Operation op) { Collection<Snapshot> sList = super.getSnapshots(op); return getSortedCollection(sList, getSnapshotComparator()); } private Comparator<Snapshot> getSnapshotComparator() { if (snapshotComparator == null) { snapshotComparator = new Comparator<Snapshot>() { @Override public int compare(Snapshot s1, Snapshot s2) { String s1Path = getSnapName(s1); String s2Path = getSnapName(s2); return s1Path.compareTo(s2Path); } }; } return snapshotComparator; } @Override protected Collection<Property> getProperties(Snapshot snap) { Collection<Property> pList = super.getProperties(snap); return getSortedCollection(pList, getPropertyComparator()); } @Override protected Collection<Property> getProperties(Operation operation) { Collection<Property> pList = super.getProperties(operation); return getSortedCollection(pList, getPropertyComparator()); } private static <T> Collection<T> getSortedCollection(Collection<T> col, Comparator<T> comp) { List<T> cList; if (col instanceof List<?>) { cList = (List<T>) col; } else { cList = Collections.list(Collections.enumeration(col)); } Collections.sort(cList, comp); return cList; } private Comparator<Property> getPropertyComparator() { if (propertyComparator == null) { propertyComparator = new Comparator<Property>() { @Override public int compare(Property p1, Property p2) { return p1.getKey().compareTo(p2.getKey()); } }; } return propertyComparator; } /** * Makes string representation of snapshot and appends it to provided string builder. * <p> * In case snapshot properties have same key for "branch" and "leaf" nodes at same path level, than "leaf" node * property key value is appended by configuration defined (cfg. key {@code "DuplicateKeySuffix"}, default value * {@value #UNIQUE_SUFFIX}) suffix. * * @param nvString * string builder instance to append * @param snap * snapshot instance to represent as string * @return appended string builder reference * * @see #getUniquePropertyKey(String, com.jkoolcloud.tnt4j.core.Property[], int) */ @Override protected StringBuilder toString(StringBuilder nvString, Trackable t, Snapshot snap) { Collection<Property> list = getProperties(snap); Property[] pArray = new Property[list.size()]; pArray = list.toArray(pArray); String sName = getTrackableKey(t, getSnapName(snap)); for (int i = 0; i < pArray.length; i++) { Property p = pArray[i]; if (p.isTransient()) { continue; } String pKey = getUniquePropertyKey(p.getKey(), pArray, i); Object value = p.getValue(); nvString.append(getKeyStr(sName, pKey)); nvString.append(EQ).append(getValueStr(value)).append(FIELD_SEP); } return nvString; } /** * Gets property key value and makes it to be unique on same path level among all array properties. * <p> * In case of duplicate keys uniqueness is made by adding configuration defined (cfg. key * {@code "DuplicateKeySuffix"}, default value {@value #UNIQUE_SUFFIX}) suffix to property key value. * * @param pKey * property key value * @param pArray * properties array * @param pIdx * property index in array * @return unique property key value */ protected String getUniquePropertyKey(String pKey, Property[] pArray, int pIdx) { String ppKey; for (int i = pIdx + 1; i < pArray.length; i++) { ppKey = pArray[i].getKey(); if (ppKey.startsWith(pKey + PATH_DELIM)) { pKey += uniqueSuffix; } } return pKey; } /** * Returns name for provided {@code trackable} instance. * <p> * Builds path using user configured ({@code "PathLevelAttributes"} property) path of trackable values. * * @param trackable * trackable instance to get name for * @return name of provided trackable */ @Override protected String getTrackableName(Trackable trackable) { StringBuilder pathBuilder = new StringBuilder(128); Object pv; if (MapUtils.isNotEmpty(pathLevelAttrKeys)) { for (Map.Entry<Condition, String[][]> entry : pathLevelAttrKeys.entrySet()) { pv = trackable.getFieldValue(entry.getKey().variable); if (entry.getKey().evaluate(Utils.toString(pv))) { for (String[] levelAttrKeys : entry.getValue()) { inner: for (String pKey : levelAttrKeys) { pv = trackable.getFieldValue(pKey); if (pv != null) { appendPath(pathBuilder, pv); break inner; } } } break; // handle first } } } else { pathBuilder.append(trackable.getName()); } return pathBuilder.toString(); } /** * Appends provided path string builder with path token string. * * @param pathBuilder * path string builder to append * @param pathToken * path token data to add to path * @return appended path string builder instance */ protected StringBuilder appendPath(StringBuilder pathBuilder, Object pathToken) { if (pathToken != null) { pathBuilder.append(pathBuilder.length() > 0 ? PATH_DELIM : "").append(Utils.toString(pathToken)); } return pathBuilder; } @Override public void setConfiguration(Map<String, ?> settings) { super.setConfiguration(settings); uniqueSuffix = Utils.getString("DuplicateKeySuffix", settings, uniqueSuffix); // NON-NLS Map<String, ?> pathLevelAttributes = Utils.getAttributes("PathLevelAttributes", settings); // NON-NLS for (Map.Entry<String, ?> entry : pathLevelAttributes.entrySet()) { String[] split = entry.getKey().split("\\."); // NON-NLS Condition condition; switch (split.length) { case 3: condition = new Condition(split[1], split[2]); break; default: condition = new Condition(); break; } pathLevelAttrKeys.put(condition, initPathLevelAttrKeys(String.valueOf(entry.getValue()))); } } private static String[][] initPathLevelAttrKeys(String levelsStr) { String[][] pathLevelAttrKeys = null; List<List<String>> levelList = new ArrayList<>(); List<String> attrsList; String[] levels = levelsStr.split(";"); for (String level : levels) { level = level.trim(); if (!level.isEmpty()) { String[] levelAttrs = level.split(","); // NON-NLS attrsList = new ArrayList<>(levelAttrs.length); for (String lAttr : levelAttrs) { lAttr = lAttr.trim(); if (!lAttr.isEmpty()) { attrsList.add(lAttr); } } if (!attrsList.isEmpty()) { levelList.add(attrsList); } } } pathLevelAttrKeys = new String[levelList.size()][]; String[] levelAttrs; int i = 0; for (List<String> level : levelList) { levelAttrs = new String[level.size()]; levelAttrs = level.toArray(levelAttrs); pathLevelAttrKeys[i++] = levelAttrs; } return pathLevelAttrKeys; } private static class Condition implements Comparable<Condition> { boolean all = true; String variable; String value; Condition() { all = true; } Condition(String variable, String value) { this(); if (value != null && variable != null) { this.variable = variable; this.value = value; all = false; } } boolean evaluate(String variable) { if (all) { return true; } return value != null && value.equals(variable); } @Override public boolean equals(Object obj) { if (all) { return true; } return obj != null && obj instanceof Condition && ((Condition) obj).value.equals(value) && ((Condition) obj).variable.equals(variable); } @Override public int compareTo(Condition o) { return o.all ? -1 : 1; // ensures "all" is last } } }