package net.twentyonesolutions.m2pg; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.StringJoiner; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; public class Util { /** * returns true if the object is a boolean true or its toString() method starts with 't', 'y', or '1' * * @param value * @return */ public static boolean isSqlTrue(Object value){ if (value == null) return false; if (value instanceof Boolean) return (Boolean)value; String s = value.toString(); if (s.isEmpty()) return false; char c = s.trim().toLowerCase().charAt(0); return (c == 't' || c == 'y' || c == '1'); } public static boolean isEmpty(String value){ return value == null || value.isEmpty(); } /** * converts a string like thisIsString1 to this_is_string_1 * * @param camelCaseString * @return */ public static String convertCamelToSnakeCase(String camelCaseString){ return camelCaseString .replace(' ', '_') .replaceAll("([^_A-Z0-9])([A-Z0-9])", "$1_$2") .toLowerCase(); } public static Map<String, Object> flattenKeys(Map<String, Object> map){ Map<String, Object> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); return flattenKeys(map, "", result); } private static Map flattenKeys(Map<String, Object> json, String path, Map result) { for (Map.Entry<String, Object> e : json.entrySet()){ StringJoiner sj = new StringJoiner("."); if (!path.isEmpty()) sj.add(path); sj.add(e.getKey()); String k = sj.toString(); Object v = e.getValue(); result.put(k, v); if (v instanceof Map){ result.putAll(flattenKeys((Map<String, Object>)v, k, result)); } else { // result.put(k, v); } } return result; } public static Map<String, ? extends Object> createCaseInsensitiveMap(String key) { return new TreeMap(String.CASE_INSENSITIVE_ORDER); } public static Map<String, ? extends Object> createCaseInsensitiveMap() { return createCaseInsensitiveMap(null); } public static List<Map<String, ? extends Object>> createColumnList(String key) { return new ArrayList(); } public static List<Map<String, ? extends Object>> createColumnList() { return createColumnList(null); } /** * Returns a JSON sub-element from the given JsonElement and the given path * * @param json - a Gson JsonElement * @param path - a JSON path, e.g. a.b.c[2].d * @return */ public static JsonElement getJsonElement(JsonElement json, String path){ String[] parts = path.split("\\.|\\[|\\]"); JsonElement result = json; for (String key : parts) { key = key.trim(); if (key.isEmpty()) continue; if (result == null){ result = JsonNull.INSTANCE; break; } if (result.isJsonObject()){ result = ((JsonObject)result).get(key); } else if (result.isJsonArray()){ int ix = Integer.valueOf(key) - 1; result = ((JsonArray)result).get(ix); } else break; } return result; } /** * returns a Map, List, String, or null * * might throw an IndexOutOfBoundsException, NumberFormatException * * @param map * @param path * @return */ public static Object getJsonElement(Map map, String path){ String[] parts = path.split("\\.|\\[|\\]"); int ix; List list; Map m; Object curr; if (map instanceof Map && !(map instanceof TreeMap)){ // convert other types of Map to TreeMap with CASE_INSENSITIVE_ORDER m = new TreeMap(String.CASE_INSENSITIVE_ORDER); m.putAll(map); map = m; } curr = map; for (String key : parts){ key = key.trim(); if (key.isEmpty()) continue; if (curr instanceof Map){ m = (Map)curr; curr = m.get(key); } else if (curr instanceof List){ list = (List)curr; ix = Integer.valueOf(key) - 1; curr = list.get(ix); } } return curr; } public static String getStackTraceAsString(Exception ex) { return Arrays.stream(ex.getStackTrace()) .map(StackTraceElement::toString) .collect(Collectors.joining("\n")); } /** * Executes the queries in a transaction. * * @param queries - either SQL code or a file path that ends with .sql * @param log - a StringBuilder that * @param conTgt - a connection to the target server * @return - true if all the queries were executed successfully */ public static boolean executeQueries(List<String> queries, StringBuilder log, Connection conTgt) { String logentry; long tc = System.currentTimeMillis(); try { Statement statTgt = null; statTgt = conTgt.createStatement(); statTgt.execute("BEGIN TRANSACTION;"); for (String script : queries) { logentry = "\n -- Executing: " + script + "\n"; System.out.println(logentry); log.append(logentry); String sql = script.trim(); if (sql.toLowerCase().endsWith(".sql")) { Path path = Paths.get(sql); sql = Files.lines(path) .collect(Collectors.joining()); logentry = "\n/**\n" + sql + "\n*/\n"; System.out.println(logentry); log.append(logentry); } statTgt.execute(sql); } statTgt.execute("COMMIT;"); tc = System.currentTimeMillis() - tc; logentry = String.format(" /* executed %,d %s in %.3f seconds **/\n", queries.size(), queries.size() > 1 ? "queries" : "query", tc / 1000.0); System.out.println(logentry); log.append(logentry); } catch (Exception ex){ ex.printStackTrace(); log.append(getStackTraceAsString(ex)); return false; } return true; } public static boolean executeQueries(List<String> queries, StringBuilder log, Config config) { try { return executeQueries(queries, log, config.connect(config.target)); } catch (SQLException ex){ ex.printStackTrace(); log.append(getStackTraceAsString(ex)); return false; } } public static void log(Path path, String logentry) throws IOException { Files.write(path, Collections.singleton(logentry), StandardCharsets.UTF_8, StandardOpenOption.APPEND, StandardOpenOption.CREATE); } public static Map<String, Object> flattenAndPopulate(Map<String, Object> config) { Map<String, Object> result = flattenKeys(config); Map systemProperties = new TreeMap(String.CASE_INSENSITIVE_ORDER); systemProperties.putAll(System.getProperties()); // populate template %v.a.l.ues% with values from JSON path Pattern pattern = Pattern.compile("\\%[a-zA-Z_\\.]+\\%"); for (Map.Entry<String, Object> e : result.entrySet()){ Object v = e.getValue(); if (v instanceof String){ String origValue = (String)v; Matcher matcher = pattern.matcher(origValue); boolean hasMatches = matcher.find(); if (hasMatches){ String fullPath = e.getKey(); String parentPath = fullPath.contains(".") ? fullPath.substring(0, fullPath.lastIndexOf('.')) : ""; String localPath = fullPath.contains(".") ? fullPath.substring(fullPath.lastIndexOf('.') + 1) : fullPath; List<int[]> substringPosition = new ArrayList<int[]>(); do { substringPosition.add(new int[]{ matcher.start(), matcher.end() }); hasMatches = matcher.find(); } while (hasMatches); if (!substringPosition.isEmpty()){ StringBuilder sb = new StringBuilder(origValue.length() * 2); int pos = 0; for (int[] pair : substringPosition){ String substring = origValue.substring(pair[0], pair[1]); String localKey = substring.substring(1, substring.length() - 1); // remove %-% signs String key = parentPath.isEmpty() ? localKey : parentPath + "." + localKey; String value; // currently supporting only String replacements // check SystemProperty, full key, local key, default to original value if (systemProperties.containsKey(localKey)) value = (String)systemProperties.get(localKey); else if (result.containsKey(key)) value = (String)result.get(key); else if (result.containsKey(localKey)) value = (String)result.get(localKey); else value = substring; sb.append(origValue.substring(pos, pair[0])); pos = pair[1]; sb.append(value); } if (origValue.length() > pos) sb.append(origValue.substring(pos)); String populated = sb.toString(); // set the populated value to the flat key result.put(fullPath, populated); if (!origValue.equals(populated)){ // set the populated value to the hierarchical key if a Map is found in its location and its value is not of type Map String[] pathParts = parentPath.split("\\."); Map m = result; for (String part : pathParts){ if (m.get(part) instanceof Map){ m = (Map)m.get(part); } else { m = null; break; } } if (m instanceof Map && !(m.get(localPath) instanceof Map)) m.put(localPath, populated); } } } } } return result; } public static Map parseJsonToMap(String jsonString){ Gson gson = new Gson(); Map conf = gson.fromJson(jsonString, Map.class); Map map = (Map) getJsonElement(conf, ""); return map; } }