/*
* 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.searchisko.api.util;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;
import org.elasticsearch.common.joda.time.LocalDateTime;
import org.elasticsearch.common.joda.time.format.ISODateTimeFormat;
import org.elasticsearch.common.settings.SettingsException;
import javax.ws.rs.core.MultivaluedMap;
/**
* Distinct utility methods.
*
* @author Libor Krzyzanek
* @author Vlastimil Elias (velias at redhat dot com)
* @author Lukas Vlcek
*/
public class SearchUtils {
private static final Logger log = Logger.getLogger(SearchUtils.class.getName());
/**
* Load properties from defined path e.g. "/app.properties"
*
* @param path
* @return newly initialized {@link Properties}
* @throws IOException
* @see {@link Class#getResourceAsStream(String)}
*/
public static Properties loadProperties(String path) throws IOException {
Properties prop = new Properties();
InputStream inStream = SearchUtils.class.getResourceAsStream(path);
prop.load(inStream);
inStream.close();
return prop;
}
/**
* Trim string and return null if empty.
*
* @param value to trim
* @return trimmed value or null if empty
*/
public static String trimToNull(String value) {
if (value != null) {
value = value.trim();
if (value.isEmpty())
value = null;
}
return value;
}
/**
* Return a new list which contains non-null and trimmed non-empty items from input list.
*
* @param values list of strings (for example taken form URL parameters)
* @return safe list or null (never empty list)
*/
public static List<String> safeList(List<String> values) {
List<String> safeValues = null;
if (values != null) {
for (String value : values) {
String sv = trimToNull(value);
if (sv != null) {
if (safeValues == null) {
safeValues = new ArrayList<>();
}
safeValues.add(sv);
}
}
}
return safeValues;
}
/**
* Check if String is blank.
*
* @param value to check
* @return true if value is blank (so null or empty or whitespaces only string)
*/
public static boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
/**
* Check if object is blank, which mean null Object, or blank String, or empty {@link Collection} or {@link Map}.
*
* @param value to check
* @return true if value is blank (so null or empty or whitespaces only string)
*/
public static boolean isBlank(Object value) {
return value == null
|| ((value instanceof String) && isBlank((String) value))
|| ((value instanceof Collection) && ((Collection<?>) value).isEmpty() || ((value instanceof Map) && ((Map<?, ?>) value)
.isEmpty()));
}
/**
* Convert JSON Map structure into String with JSON content.
*
* @param jsonMapValue to convert
* @return
* @throws IOException
*/
public static String convertJsonMapToString(Map<String, Object> jsonMapValue) throws IOException {
if (jsonMapValue == null)
return "";
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(getISODateFormat());
return mapper.writeValueAsString(jsonMapValue);
}
/**
* Get ISO date time formatter.
*
* @return DateFormat instance for ISO format
*/
public static DateFormat getISODateFormat() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
sdf.setLenient(false);
return sdf;
}
/**
* Parse ISO date time formatted string into {@link Date} instance.
*
* @param string ISO formatted date string to parse
* @param silent if true then null is returned instead of {@link IllegalArgumentException} thrown
* @return parsed date or null
* @throws IllegalArgumentException in case of bad format
*/
public static Date dateFromISOString(String string, boolean silent) throws IllegalArgumentException {
if (string == null)
return null;
try {
return ISODateTimeFormat.dateTimeParser().parseDateTime(string).toDate();
} catch (IllegalArgumentException e) {
if (!silent)
throw e;
else
return null;
}
}
/**
* Test if Date is after date computed as current date minus threshold
*
* @param date date to be tested. Can be String in ISO format or java.util.date
* @param thresholdInMinutes threshold in minutes
* @return false if sysUpdated is newer otherwise true
*/
public static boolean isDateAfter(Object date, int thresholdInMinutes) {
LocalDateTime d = null;
if (date instanceof Date) {
d = new LocalDateTime(((Date) date).getTime());
} else if (date instanceof String) {
try {
d = new LocalDateTime(SearchUtils.dateFromISOString((String) date, true));
} catch (Exception e) {
// should never happen. See dateFromISOString method
}
}
log.log(Level.FINEST, "date to check: {0}", d);
if (d != null) {
LocalDateTime now = new LocalDateTime();
LocalDateTime test = now.minusMinutes(thresholdInMinutes);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "date to test again: {0}, threshold: {1}", new Object[] { test, thresholdInMinutes });
}
if (d.isBefore(test)) {
return false;
}
}
return true;
}
/**
* Convert String with JSON content into JSON Map structure.
*
* @param jsonData string to convert
* @return JSON MAP structure
* @throws IOException
*/
public static Map<String, Object> convertToJsonMap(String jsonData) throws IOException {
if (jsonData == null)
return null;
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonData, new TypeReference<Map<String, Object>>() {
});
}
/**
* Get Integer value from value in Json Map. Can convert from {@link String} and {@link Number} values.
*
* @param map to get Integer value from
* @param key in map to get value from
* @return Integer value if found. <code>null</code> if value is not present in map.
* @throws NumberFormatException if value from map is not convertible to integer number
*/
public static Integer getIntegerFromJsonMap(Map<String, Object> map, String key) throws NumberFormatException {
if (map == null)
return null;
Object o = map.get(key);
if (o == null)
return null;
if (o instanceof Integer)
return (Integer) o;
else if (o instanceof Number)
return ((Number) o).intValue();
else if (o instanceof String)
return Integer.valueOf((String) o);
else
throw new NumberFormatException();
}
/**
* Get list of Strings from given key on given map. If it contains simple String then List is created with it.
* {@link #safeList(List)} is used inside to filter list.
*
* @param map to get value from
* @param key in map to get value from
* @return list of strings or null.
* @throws SettingsException if value in json map is invalid
*/
@SuppressWarnings("unchecked")
public static List<String> getListOfStringsFromJsonMap(Map<String, Object> map, String key) throws SettingsException {
if (map == null)
return null;
try {
Object o = map.get(key);
if (o instanceof String) {
String v = StringUtils.trimToNull((String) o);
if (v != null) {
List<String> l = new ArrayList<>();
l.add(v);
return l;
}
return null;
}
return safeList((List<String>) o);
} catch (ClassCastException e) {
throw new SettingsException("No String or Array of strings present in field '" + key);
}
}
/**
* Merge values from source into target JSON Map. Target Map is more important during merge, so in case of some
* conflicts target Map wins and original value is preserved. Lists (used for JSON Array) merging do not create
* duplication (uses <code>equals()</code> to detect them). Structure inside source Map is not changed any way.
*
* @param source Map to merge values from
* @param target Map to merge values into
*/
public static void mergeJsonMaps(Map<String, Object> source, Map<String, Object> target) {
mergeJsonMaps(source, target, null);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void mergeJsonMaps(Map<String, Object> source, Map<String, Object> target, String keyBase) {
if (source == null || target == null || source.isEmpty())
return;
if (keyBase == null || keyBase.trim().isEmpty())
keyBase = "";
else {
keyBase = keyBase.trim() + ".";
}
for (String key : source.keySet()) {
Object sourceValue = source.get(key);
if (sourceValue == null)
continue;
Object targetValue = target.get(key);
if (targetValue == null) {
target.put(key, sourceValue);
} else {
if (targetValue instanceof List) {
if (sourceValue instanceof List) {
if (!((List) sourceValue).isEmpty()) {
// merge without duplicities
if (((List) targetValue).isEmpty()) {
((List) targetValue).addAll((List) sourceValue);
} else {
LinkedHashSet nv = new LinkedHashSet();
nv.addAll((List) targetValue);
nv.addAll((List) sourceValue);
((List) targetValue).clear();
((List) targetValue).addAll(nv);
}
}
} else {
if (!((List) targetValue).contains(sourceValue))
((List) targetValue).add(sourceValue);
}
} else if (targetValue instanceof Map) {
if (sourceValue instanceof Map) {
mergeJsonMaps((Map<String, Object>) sourceValue, (Map<String, Object>) targetValue, keyBase + key);
} else {
log.fine("Can't merge value for key " + keyBase + key
+ " because target is Map but source is not Map. Keeping source value there.");
}
} else {
if (sourceValue instanceof List) {
List newList = new ArrayList();
newList.addAll((List) sourceValue);
if (!newList.contains(targetValue))
newList.add(targetValue);
target.put(key, newList);
} else if (sourceValue instanceof Map) {
log.fine("Can't merge value for key " + keyBase + key
+ " because source is Map but target is simple value. Keeping source value.");
} else {
// merge values into list if not same
if (!sourceValue.equals(targetValue)) {
List newList = new ArrayList<>();
newList.add(sourceValue);
newList.add(targetValue);
target.put(key, newList);
}
}
}
}
}
}
/**
* Convert URL params format to type that is required by internal Elasticsearch API.
*
* @param params URL parameters
* @return
*/
public static Map<String, Object> collapseURLParams(MultivaluedMap<String, String> params) {
Map output = new HashMap<>();
for (String key: params.keySet()) {
List values = params.get(key);
if (values != null) {
output.put(key, values.size() == 1 ? values.get(0) : values.toArray());
}
}
return output;
}
/**
* Determine if database product name is mysql
* @param databaseProductName
* @return true if database i mysql
* @see java.sql.Connection#getMetaData()#getDatabaseProductName()
*/
public static boolean isMysqlDialect(String databaseProductName) {
return StringUtils.containsIgnoreCase(databaseProductName, "mysql");
}
}