Java tutorial
/* * Copyright (c) 2013. Evan Moseman * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.emoseman.aconf; import com.google.common.reflect.Invokable; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import java.io.InputStreamReader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigInteger; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.commons.lang3.StringUtils; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * AutoConfig information. * * @author Evan Moseman (evan.moseman@gmail.com) * @version 1.0 * @since 5/20/13 */ public class AutoConfig { private static Logger log = LoggerFactory.getLogger(AutoConfig.class); private static AtomicBoolean shutdown = new AtomicBoolean(false); private static ReentrantReadWriteLock configLock = new ReentrantReadWriteLock(); private static final HashMap<Class<? extends ConfigContainer>, ArrayList<Field>> configContainerFields = new HashMap<>(); private static final HashMap<Class<? extends ConfigContainer>, ArrayList<String>> configContainerRequiredFields = new HashMap<>(); private static final HashMap<Class<? extends ConfigContainer>, HashMap<String, Field>> fieldMap = new HashMap<>(); private static final HashMap<Class<? extends ConfigContainer>, HashMap<String, Class>> fieldTypeMap = new HashMap<>(); private static Set<Class<? extends ConfigContainer>> configContainerTypes; private static final Set<ConfigChangeListener> listeners = new HashSet<>(); public static final String RELOAD_DELAY = "reload.delay"; public static final String DATE_FORMAT = "date.format"; public static final String TIME_FORMAT = "time.format"; public static final String PACKAGE_SEARCH_NAME = "package.search.name"; public static final SimpleDateFormat dateFormat; public static final SimpleDateFormat timeFormat; private static final ScheduledExecutorService timedPool = Executors.newScheduledThreadPool(1); static { String packageName = ""; if (System.getProperty(PACKAGE_SEARCH_NAME) != null) { packageName = System.getProperty(PACKAGE_SEARCH_NAME); } else if (System.getenv(PACKAGE_SEARCH_NAME) != null) { packageName = System.getenv(PACKAGE_SEARCH_NAME); } final Reflections reflections = new Reflections(packageName); configContainerTypes = reflections.getSubTypesOf(ConfigContainer.class); if (configContainerTypes.size() == 0) { System.err.println("No classes found implementing ConfigContainer interface."); } long reloadDelay = -1; if (System.getProperty(RELOAD_DELAY) != null) { reloadDelay = Long.valueOf(System.getProperty(RELOAD_DELAY)); } else if (System.getenv(RELOAD_DELAY) != null) { reloadDelay = Long.valueOf(System.getProperty(RELOAD_DELAY)); } if (reloadDelay > 0) { timedPool.scheduleWithFixedDelay(new Reloader(), 0, reloadDelay, TimeUnit.MILLISECONDS); } if (System.getProperty(DATE_FORMAT) != null) { dateFormat = new SimpleDateFormat(System.getProperty(DATE_FORMAT)); } else if (System.getenv(DATE_FORMAT) != null) { dateFormat = new SimpleDateFormat(System.getenv(DATE_FORMAT)); } else { dateFormat = new SimpleDateFormat("yyyy-MM-dd"); } if (System.getProperty(TIME_FORMAT) != null) { timeFormat = new SimpleDateFormat(System.getProperty(TIME_FORMAT)); } else if (System.getenv(TIME_FORMAT) != null) { timeFormat = new SimpleDateFormat(System.getenv(TIME_FORMAT)); } else { timeFormat = new SimpleDateFormat("hh:mm a"); } } private AutoConfig() { } public static synchronized final void init() { processConfigContainers(); } public static synchronized final void reload() { processConfigContainers(); } private static void processConfigContainers() { if (shutdown.get()) { return; } for (Class<? extends ConfigContainer> containerType : configContainerTypes) { log.debug("processing config container: {}", containerType); ConfigFile configFileAnnotation = containerType.getAnnotation(ConfigFile.class); if (configFileAnnotation == null) { throw new RuntimeException("ConfigContainer class " + containerType.getName() + " does not have the required ConfigFile annotation."); } final String configFilename; configFilename = configFileAnnotation.value(); configContainerRequiredFields.put(containerType, new ArrayList<String>()); configContainerFields.put(containerType, new ArrayList<Field>()); fieldTypeMap.put(containerType, new HashMap<String, Class>()); fieldMap.put(containerType, new HashMap<String, Field>()); for (Field f : containerType.getDeclaredFields()) { ConfigElement configElement = f.getAnnotation(ConfigElement.class); if (configElement == null) { continue; } log.debug("config element: {}", configElement.toString()); if (!Modifier.isStatic(f.getModifiers())) { throw new RuntimeException("Fields annotated with ConfigElement, like " + f.getName() + " are required to be static!"); } configContainerFields.get(containerType).add(f); fieldTypeMap.get(containerType).put(configElement.propertyName(), configElement.propertyType()); fieldMap.get(containerType).put(configElement.propertyName(), f); if (configElement.required()) { configContainerRequiredFields.get(containerType).add(configElement.propertyName()); } } log.debug("field map:{}", fieldMap.get(containerType).toString()); readConfigFromFile(containerType, configFilename); } } private static void readConfigFromFile(Class<? extends ConfigContainer> configContainer, String configFilename) { configLock.writeLock().lock(); URL fileURL = null; if (configFilename.startsWith("file:///")) { try { fileURL = new URL(configFilename); } catch (MalformedURLException e) { throw new RuntimeException("Failed to create URL from " + configFilename, e); } } else { ClassLoader cl = ClassLoader.getSystemClassLoader(); fileURL = cl.getResource(configFilename); } if (fileURL == null) { throw new RuntimeException("Failed to load config file " + configFilename); } Gson gson = new Gson(); String val; Field field; Class type; try { // read json from config file // TODO enforce extension? HashMap<String, String> tmp = gson.fromJson(new InputStreamReader(fileURL.openStream()), HashMap.class); // search for required keys ArrayList<String> missingKeys = new ArrayList<>(); for (String k : configContainerRequiredFields.get(configContainer)) { if (!tmp.containsKey(k)) { missingKeys.add(k); } } if (missingKeys.size() > 0) { throw new RuntimeException( "Properties " + StringUtils.join(missingKeys, ", ") + " are required, and missing"); } // assign defaults for (Field f : configContainerFields.get(configContainer)) { ConfigElement configElement = f.getAnnotation(ConfigElement.class); if (configElement == null) { continue; } if (StringUtils.isEmpty(configElement.defaultValue())) { continue; } if (!tmp.containsKey(configElement.propertyName())) { tmp.put(configElement.propertyName(), configElement.defaultValue()); } } // populate fields for (String k : tmp.keySet()) { if (fieldMap.get(configContainer).containsKey(k)) { val = tmp.get(k); if (val.equalsIgnoreCase("${hostname}")) { val = populateMacro(val); } field = fieldMap.get(configContainer).get(k); field.setAccessible(true); type = fieldTypeMap.get(configContainer).get(k); if (type == String.class) { field.set(configContainer, String.valueOf(val)); } else if (type == BigInteger.class) { field.set(configContainer, new BigInteger(val)); } else if (type == Float.class || type == float.class) { field.set(configContainer, Float.valueOf(val)); } else if (type == Long.class || type == long.class) { field.set(configContainer, Long.valueOf(val)); } else if (type == Double.class || type == double.class) { field.set(configContainer, Double.valueOf(val)); } else if (type == Integer.class || type == int.class) { field.set(configContainer, Integer.valueOf(val)); } else if (type == Boolean.class || type == boolean.class) { field.set(configContainer, Boolean.valueOf(val)); } else { // try to execute the method reflectively try { Method m = type.getDeclaredMethod("valueOf", String.class); Invokable invokable = new TypeToken() { }.method(m); if (invokable.isStatic() && !invokable.isPrivate()) { field.set(configContainer, m.invoke(type, val)); continue; } } catch (Throwable ignored) { // ignored } try { Constructor typeConstructor = type.getConstructor(String.class); Invokable invokable = new TypeToken() { }.constructor(typeConstructor); if (!invokable.isPrivate()) { field.set(configContainer, typeConstructor.newInstance(val)); continue; } } catch (Throwable ignored) { // ignored } throw new RuntimeException( "Not coded for type " + type + " and could not find a method to execute!"); } } } for (ConfigChangeListener listener : listeners) { listener.configReloaded(); } } catch (Exception e) { log.error(e.getClass() + "-" + e.getMessage()); if (log.isDebugEnabled()) { e.printStackTrace(System.err); } shutdown.set(true); timedPool.shutdownNow(); throw new RuntimeException(e); } configLock.writeLock().unlock(); } public static final String HOSTNAME_MACRO = "${hostname}"; public static final String DATE_MACRO = "${date}"; public static final String TIME_MACRO = "${time}"; private static String populateMacro(final String val) { switch (val) { case HOSTNAME_MACRO: return gethostname(); case DATE_MACRO: return getDate(); case TIME_MACRO: return getTime(); default: return val; } } public static final boolean addListener(ConfigChangeListener listener) { return listeners.add(listener); } public static final boolean removeListener(ConfigChangeListener listener) { return listeners.remove(listener); } private static String getDate() { return dateFormat.format(new Date()); } private static String getTime() { return timeFormat.format(new Date()); } private static String gethostname() { try { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { log.error(e.getMessage()); } return "failed to resolve hostname"; } private static class Reloader implements Runnable { @Override public void run() { reload(); } } }