package act.conf; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.Act; import org.osgl.$; import org.osgl.exception.ConfigurationException; import org.osgl.exception.NotAppliedException; import org.osgl.logging.L; import org.osgl.logging.Logger; import org.osgl.util.S; import java.io.File; import java.lang.reflect.Array; import java.net.URI; import java.net.URL; import java.util.*; /** * Provides configuration key enum manipulating utilities */ class ConfigKeyHelper { private static Logger logger = L.get(ConfigKeyHelper.class); private $.F0<Act.Mode> mode; private $.F0<ClassLoader> classLoaderProvider; public ConfigKeyHelper($.F0<Act.Mode> mode, final ClassLoader cl) { this.mode = mode; this.classLoaderProvider = new $.F0<ClassLoader>() { @Override public ClassLoader apply() throws NotAppliedException, $.Break { return cl; } }; } public ConfigKeyHelper($.F0<Act.Mode> mode) { this.mode = mode; } ConfigKeyHelper classLoaderProvider($.F0<ClassLoader> provider) { this.classLoaderProvider = provider; return this; } <T> T getConfiguration(final ConfigKey confKey, Map<String, ?> configuration) { String key = confKey.key(); $.F0<?> defVal = new $.F0<Object>() { @Override public Object apply() throws NotAppliedException, $.Break { return confKey.defVal(); } }; return getConfiguration(key, defVal, configuration); } <T> T getConfiguration(String key, $.F0<?> defVal, Map<String, ?> configuration) { if (key.endsWith(".enabled") || key.endsWith(".disabled")) { String key0 = S.beforeLast(key, "."); Boolean B = getEnabled(key0, configuration, defVal); if (null == B) { return null; } if (key.endsWith(".disabled")) { B = !B; } return (T) B; } if (key.endsWith(".impl")) { return getImpl(configuration, key, suffixOf(key), defVal); } if (key.endsWith(".dir") || key.endsWith(".home") || key.endsWith(".path")) { return (T) getUri(configuration, key, suffixOf(key), defVal); } if (key.endsWith(".bool") || key.endsWith(".boolean")) { return (T) getX(configuration, key, suffixOf(key), defVal, F.TO_BOOLEAN); } if (key.endsWith(".long") || key.endsWith(".ttl")) { return (T) getX(configuration, key, suffixOf(key), defVal, F.TO_LONG); } if (key.endsWith(".int") || key.endsWith(".len") || key.endsWith(".count") || key.endsWith(".times") || key.endsWith(".size")) { return (T) getX(configuration, key, suffixOf(key), defVal, F.TO_INT); } if (key.endsWith(".float")) { return (T) getX(configuration, key, suffixOf(key), defVal, F.TO_FLOAT); } if (key.endsWith(".double")) { return (T) getX(configuration, key, suffixOf(key), defVal, F.TO_DOUBLE); } return (T) getValFromAliases(configuration, key, null, defVal); } <T> List<T> getImplList(String key, Map<String, ?> configuration, Class<T> c) { final Object v = getValFromAliases(configuration, key, "impls", null); if (null == v) return Collections.EMPTY_LIST; final boolean needClass = (Class.class.isAssignableFrom(c)); final List<T> l = new ArrayList<T>(); final Class vc = v.getClass(); if (c.isAssignableFrom(vc)) { l.add((T) v); return l; } if (v instanceof Class) { if (needClass) { l.add((T) v); } else { T inst = newInstance(key, (Class) v, c); if (null != inst) { l.add(inst); } } return l; } if (vc.isArray()) { int len = Array.getLength(v); for (int i = 0; i < len; ++i) { Object el = Array.get(v, i); if (null == el) { continue; } Class elc = el.getClass(); if (c.isAssignableFrom(elc)) { l.add((T) el); } else if (el instanceof Class) { if (needClass) { l.add((T) el); } else { T inst = newInstance(key, (Class) el, c); if (null != inst) { l.add(inst); } } } else { try { elc = Class.forName(el.toString()); if (needClass) { l.add((T) elc); } else { T inst = newInstance(key, elc, c); if (null != inst) { l.add(inst); } } } catch (Exception e) { logger.warn(e, "Error getting impl class out from %s for configuration %s", el, key); } } } return l; } else if (Collection.class.isAssignableFrom(vc)) { Collection col = (Collection) v; for (Object el : col) { if (null == el) { continue; } Class elc = el.getClass(); if (c.isAssignableFrom(elc)) { l.add((T) el); } else if (el instanceof Class) { if (needClass) { l.add((T) el); } else { T inst = newInstance(key, (Class) el, c); if (null != inst) { l.add(inst); } } } else { try { elc = Class.forName(el.toString()); if (needClass) { l.add((T) elc); } else { T inst = newInstance(key, elc, c); if (null != inst) { l.add(inst); } } } catch (Exception e) { logger.warn(e, "Error getting impl class out from %s for configuration %s", el, key); } } } return l; } for (String s : v.toString().split("[ \t,;]+")) { try { Class ec = Class.forName(s); if (needClass) { l.add((T) ec); } else { T inst = newInstance(key, ec, c); if (null != inst) { l.add(inst); } } } catch (Exception e) { logger.warn(e, "Error getting impl class out from %s for configuration %s", s, key); } } return l; } <T> T getX(Map<String, ?> configuration, String key, String suffix, $.F0<?> defVal, $.Func1<Object, T> converter) { Object v = getValFromAliases(configuration, key, suffix, defVal); return converter.apply(v); } private Boolean getEnabled(String key, Map<String, ?> configuration, $.F0<?> defVal) { Object v = getValFromAliases(configuration, key, "enabled", defVal); if (null == v) { v = getValFromAliases(configuration, key, "disabled", defVal); return null == v ? null : !toBoolean(v); } return toBoolean(v); } private ClassLoader myClassLoader() { return classLoaderProvider.apply(); } <T> T getImpl(Map<String, ?> configuration, String key, String suffix, $.F0<?> defVal) { Object v = getValFromAliases(configuration, key, "impl", defVal); if (null == v) return null; if (v instanceof Class) { try { return $.newInstance((Class<T>) v, myClassLoader()); } catch (Exception e) { throw new ConfigurationException(e, "Error getting implementation configuration: %s", key); } } if (!(v instanceof String)) return (T) v; String clsName = (String) v; try { return $.newInstance(clsName, myClassLoader()); } catch (Exception e) { throw new ConfigurationException(e, "Error getting implementation configuration: %s", key); } } private URI getUri(Map<String, ?> configuration, String key, String suffix, $.F0<?> defVal) { Object v = getValFromAliases(configuration, key, suffix, defVal); if (null == v) return null; if (v instanceof File) { return ((File) v).toURI(); } String s = v.toString(); return asUri(s, key); } private static URI asUri(String s, String key) { boolean isAbsolute = false; if (s.startsWith("/") || s.startsWith(File.separator)) { isAbsolute = true; } else if (s.matches("^[a-zA-Z]:.*")) { isAbsolute = true; } if (isAbsolute) { File f = new File(s); if (f.exists() && f.isDirectory() && f.canRead()) { return f.toURI(); } return null; } try { if (s.startsWith("..")) { URL url = Thread.currentThread().getContextClassLoader().getResource("."); if (null == url) { // must running from inside a jar file, and it doesn't support // template root starts with ".." return null; } String path = url.getPath(); if (path.endsWith("/")) path = path + s; else path = path + "/" + s; return new URI(path); } else { URL url = Thread.currentThread().getContextClassLoader().getResource(s); return null == url ? null : url.toURI(); } } catch (Exception e) { throw new ConfigurationException(e, "Error reading file configuration %s", key); } } private static Boolean toBoolean(Object v) { if (null == v) return null; if (v instanceof Boolean) return (Boolean) v; return Boolean.parseBoolean(v.toString()); } private static Long toLong(Object v) { if (null == v) return null; if (v instanceof Number) return ((Number) v).longValue(); return Long.parseLong(v.toString()); } private static Integer toInt(Object v) { if (null == v) return null; if (v instanceof Number) return ((Number) v).intValue(); return Integer.parseInt(v.toString()); } private static Float toFloat(Object v) { if (null == v) return null; if (v instanceof Number) return ((Number) v).floatValue(); return Float.parseFloat(v.toString()); } private static Double toDouble(Object v) { if (null == v) return null; if (v instanceof Number) return ((Number) v).doubleValue(); return Double.parseDouble(v.toString()); } private Object getValFromAliases(Map<String, ?> configuration, String key, String suffix) { Object v = configuration.get(key); if (null == v) { for (String k0 : aliases(key, suffix)) { v = configuration.get(k0); if (null != v) break; } } if (null != v && v instanceof String) { v = evaluate((String) v, configuration); } return v; } /* * Check if v has variable e.g. `${foo.bar}` inside and expand it recursively */ public String evaluate(String s, Map<String, ?> map) { int n = 0, n0 = 0, len = s.length(); S.Buffer sb = S.newBuffer(); while (n > -1 && n < len) { n = s.indexOf("${", n); if (n < 0) { if (n0 == 0) { return s; } sb.append(s.substring(n0, len)); break; } sb.append(s.substring(n0, n)); // now search for "}" n += 2; n0 = n; n = s.indexOf("}", n0 + 1); if (n < 0) { logger.warn("Invalid expression found in the configuration value: %s", s); return s; } String expression = s.substring(n0, n); if (S.notBlank(expression)) { Object o = getConfiguration(expression, null, map); if (null != o) { sb.append(o); } else { logger.warn("Cannot find expression value for: %s", expression); } } n += 1; n0 = n; } return sb.toString(); } private Object getValFromAliasesWithModelPrefix(Map<String, ?> configuration, String key, String suffix) { return getValFromAliases(configuration, mode().configKey(key), suffix); } Object getValFromAliases(Map<String, ?> configuration, String key, String suffix, $.F0<?> defVal) { Object v = getValFromAliasesWithModelPrefix(configuration, key, suffix); if (null != v) { return v; } v = getValFromAliases(configuration, key, suffix); if (null != v) { return v; } // still not found, load default value if (null != defVal) { v = defVal.apply(); } return v; } private Set<String> aliases(String key, String suffix) { Set<String> set = new HashSet<>(); set.add(Config.PREFIX + key); set.add(key); if (S.notBlank(suffix)) { if (key.contains(suffix)) { String k0 = key.replace("." + suffix, ""); set.add(Config.PREFIX + k0); set.add(k0); } else { if (!suffix.startsWith(".")) { suffix = "." + suffix; } String k0 = key + suffix; set.add(Config.PREFIX + k0); set.add(k0); } } return set; } private Act.Mode mode() { return mode.apply(); } private String suffixOf(String key) { return S.afterLast(key, "."); } private static <T> T newInstance(String key, Class c, Class<T> expectedClass) { if (!expectedClass.isAssignableFrom(c)) { logger.warn("Mismatched type found for configuration %s", key); return null; } try { return (T) c.newInstance(); } catch (Exception e) { logger.warn(e, "Cannot create new instance for configuration %s", key); return null; } } public enum F { ; public static $.Func1<Object, Integer> TO_INT = new $.Transformer<Object, Integer>() { @Override public Integer transform(Object object) { return toInt(object); } }; public static $.Func1<Object, Long> TO_LONG = new $.Transformer<Object, Long>() { @Override public Long transform(Object object) { return toLong(object); } }; public static $.Func1<Object, Float> TO_FLOAT = new $.Transformer<Object, Float>() { @Override public Float transform(Object object) { return toFloat(object); } }; public static $.Func1<Object, Double> TO_DOUBLE = new $.Transformer<Object, Double>() { @Override public Double transform(Object object) { return toDouble(object); } }; public static $.Func1<Object, Boolean> TO_BOOLEAN = new $.Transformer<Object, Boolean>() { @Override public Boolean transform(Object object) { return toBoolean(object); } }; } }