/* * JBoss, Home of Professional Open Source * Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. */ package org.jboss.elasticsearch.river.remote; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.common.xcontent.support.XContentMapValues; /** * Utility functions. * * @author Vlastimil Elias (velias at redhat dot com) */ public class Utils { private static final ESLogger logger = Loggers.getLogger(Utils.class); /** * Trim String value, return null if empty after trim. * * @param src value * @return trimmed value or null */ public static String trimToNull(String src) { if (src == null || src.length() == 0) { return null; } src = src.trim(); if (src.length() == 0) { return null; } return src; } /** * Check if String value is null or empty. * * @param src value * @return <code>true</code> if value is null or empty */ public static boolean isEmpty(String src) { return (src == null || src.length() == 0 || src.trim().length() == 0); } /** * Parse time value from river settings/config map. Value must be number, which is normally in milliseconds, but you * can postfix it by one of next letters to set units * <ul> * <li><code>s</code> - seconds * <li><code>m</code> - minutes * <li><code>h</code> - hours * <li><code>d</code> - days * <li><code>w</code> - weeks * </ul> * * @param settings map to get value from * @param key of config value in map * @param defaultDuration default duration used if no value in config * @param defaultTimeUnit time unit for default duration - if null no default is used, so return 0 as default in this * case * @return time value in millis */ protected static long parseTimeValue(Map<String, Object> settings, String key, long defaultDuration, TimeUnit defaultTimeUnit) { long ret = 0; if (settings == null || !settings.containsKey(key)) { if (defaultTimeUnit != null) { ret = new TimeValue(defaultDuration, defaultTimeUnit).millis(); } } else { try { ret = TimeValue.parseTimeValue(XContentMapValues.nodeStringValue(settings.get(key), null), new TimeValue(defaultDuration, defaultTimeUnit)).millis(); } catch (ElasticsearchParseException e) { throw new ElasticsearchParseException(e.getMessage() + " for setting: " + key); } } return ret; } /** * Parse comma separated string into list of tokens. Tokens are trimmed, empty tokens are not in result. * * @param toParse String to parse * @return List of tokens if at least one token exists, null otherwise. */ public static List<String> parseCsvString(String toParse) { if (toParse == null || toParse.length() == 0) { return null; } String[] t = toParse.split(","); if (t.length == 0) { return null; } List<String> ret = new ArrayList<String>(); for (String s : t) { if (s != null) { s = s.trim(); if (s.length() > 0) { ret.add(s); } } } if (ret.isEmpty()) return null; else return ret; } /** * Create string with comma separated list of values from input collection. Ordering by used Collection implementation * iteration order is used. * * @param in collection to format * @return <code>null</code> if <code>in</code> is <code>null</code>, CSV string in other cases (empty id in is empty) */ public static String createCsvString(Collection<String> in) { if (in == null) return null; if (in.isEmpty()) { return ""; } boolean first = true; StringBuilder sb = new StringBuilder(); for (String s : in) { if (first) first = false; else sb.append(","); sb.append(s); } return sb.toString(); } /** * Get node value as {@link Integer} object instance if possible. * * @param node to get value from * @return Integer value or null. * @throws NumberFormatException if value can't be converted to the int value * @see XContentMapValues#nodeIntegerValue(Object, int) */ public static Integer nodeIntegerValue(Object node) throws NumberFormatException { if (node == null) { return null; } if (node instanceof Integer) { return (Integer) node; } else if (node instanceof Number) { return new Integer(((Number) node).intValue()); } return Integer.parseInt(node.toString()); } /** * Filter data in Map. Leave here only data with keys passed in second parameter. * * @param map to filter data inside * @param keysToLeave keys leaved in map. If <code>null</code> or empty then no filtering is performed! */ public static <T> void filterDataInMap(Map<T, Object> map, Set<T> keysToLeave) { if (map == null || map.isEmpty()) return; if (keysToLeave == null || keysToLeave.isEmpty()) return; Set<T> keysToRemove = new HashSet<T>(map.keySet()); keysToRemove.removeAll(keysToLeave); if (!keysToRemove.isEmpty()) { for (T rk : keysToRemove) { map.remove(rk); } } } /** * Remap data in input Map. Leave here only data with defined keys, but change these keys to new ones if necessary. * Some new key can be same as some other old key, but if two new keys are same, then only latest value is preserved * (given by <code>mapToChange</code> key iteration order). * * @param mapToChange Map to remap data inside. Must be mutable! * @param remapInstructions instructions how to remap. If <code>null</code> or empty then remap is not performed and * <code>mapToChange</code> is not changed! Key in this Map must be same as key in <code>mapToChange</code> * which may leave there. Value in this map means new key of value in <code>mapToChange</code> after * remapping. */ public static <T> void remapDataInMap(Map<T, Object> mapToChange, Map<T, T> remapInstructions) { if (mapToChange == null || mapToChange.isEmpty()) return; if (remapInstructions == null || remapInstructions.isEmpty()) return; Map<T, Object> newMap = new HashMap<T, Object>(); for (T keyOrig : mapToChange.keySet()) { if (remapInstructions.containsKey(keyOrig)) { T keyNew = remapInstructions.get(keyOrig); newMap.put(keyNew, mapToChange.get(keyOrig)); } } mapToChange.clear(); mapToChange.putAll(newMap); } /** * Read JSON file from classpath into Map of Map structure. * * @param filePath path inside jar/classpath pointing to JSON file to read * @return parsed JSON file * @throws SettingsException */ public static Map<String, Object> loadJSONFromJarPackagedFile(String filePath) throws SettingsException { XContentParser parser = null; try { parser = XContentFactory.xContent(XContentType.JSON).createParser(Utils.class.getResourceAsStream(filePath)); Map<String, Object> ret = parser.mapAndClose(); if (logger.isDebugEnabled()) logger.debug("jar packaged JSON file {} content is: {}", filePath, ret); return ret; } catch (IOException e) { throw new SettingsException(e.getMessage(), e); } finally { if (parser != null) parser.close(); } } /** * Put value into Map of Maps structure. Dot notation supported for deeper level of nesting. * * @param map Map to put value into * @param field to put value into. Dot notation can be used. * @param value to be added into Map * @throws IllegalArgumentException if value can't be added due something wrong in data structure */ @SuppressWarnings("unchecked") public static void putValueIntoMapOfMaps(Map<String, Object> map, String field, Object value) throws IllegalArgumentException { if (map == null) return; if (isEmpty(field)) { throw new IllegalArgumentException("field argument must be defined"); } if (field.contains(".")) { String[] tokens = field.split("\\."); int tokensCount = tokens.length; Map<String, Object> levelData = map; for (String tok : tokens) { if (tokensCount == 1) { levelData.put(tok, value); } else { Object o = levelData.get(tok); if (o == null) { Map<String, Object> lv = new LinkedHashMap<String, Object>(); levelData.put(tok, lv); levelData = lv; } else if (o instanceof Map) { levelData = (Map<String, Object>) o; } else { throw new IllegalArgumentException("Cant put value for field '" + field + "' because some element in the path is not Map"); } } tokensCount--; } } else { map.put(field, value); } } /** * Get file extension from URL conwerted to lower case. Null if extension is not defined. * * @param url to get extension from * @return extension (without dot) or null */ public static String getFileExtensionLowercase(String url) { if (url == null || url.isEmpty()) return null; url = url.trim(); int il = url.lastIndexOf("/"); int id = url.lastIndexOf("."); if (id > il && id > 0 && id < url.length() - 1) { return url.substring(id + 1).toLowerCase(); } return null; } /** * Check if object is simple JSON value (not Array nor List nor Map) * * @param value to check * @return true if it is an simple value */ public static boolean isSimpleValue(Object value) { return (value instanceof String || value instanceof Integer || value instanceof Boolean || value instanceof Long || value instanceof Date); } }