package org.commons.jconfig.configloader; import java.lang.management.ManagementFactory; import java.nio.charset.Charset; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import org.apache.log4j.Logger; import org.commons.jconfig.annotations.ConfigResource; import org.commons.jconfig.config.ConfigContext; import org.commons.jconfig.config.ConfigException; import org.commons.jconfig.config.ConfigManager; import org.commons.jconfig.config.ConfigManagerConfig; import org.commons.jconfig.internal.ConfigAdapterJson; import org.commons.jconfig.internal.ConfigManagerCache; import org.commons.jconfig.internal.WorkerExecutorService; import org.commons.jconfig.internal.jmx.ConfigLoaderJvm; import org.commons.jconfig.internal.jmx.VirtualMachineException; /** * The Configuration Loader application. * * Config Loader application runs as service on client host. One Config Loader * services all applications on host.<BR> * <BR> * * Config Loader registers @ConfigLoaderMXBean JMX MBean with subscribeConfigs * operations<Br> * <br> * * Applications call <i>subscribeConfigs<i> method of @ConfigLoaderMXBean <Br> * <br> * * Config loader application periodically checks for configuration source * changes; changes are detected<br> * and pushed to the Application config MBeans that previously called * <i>subscribeConfigs<i>.<br> * <br> * * Config Loader employs modular design to operate on different config sources * (properties files, mhl, <br> * yinst settings, etc) AutoConf is first source supported; Detects refresh of * AutoConf file on local disk<br> * * @author jaikit */ public class ConfigLoaderRunner { private final static Logger logger = Logger .getLogger(ConfigLoaderRunner.class); /** * Appname for ConfigLoader. This name should be used if any beans owned by * ConfigLoader needs to be registered to {@link MBeanServer} */ public static final String CONFIG_LOADER_APP_NAME = "org.commons.jconfig.loader"; /** Excecutor for pushing config values to individual jvm's */ private WorkerExecutorService executor; /** Scheduler for reading jmx values */ private final ScheduledExecutorService jmxScheduler = Executors .newScheduledThreadPool(1); /** Scheduler for reading config files from config server. */ private final ScheduledExecutorService confServerReader = Executors .newScheduledThreadPool(1); /** ConfigLoader config */ private ConfigLoaderConfig config; /** * ConfigLoader config resource directory path. This setting is the only * value which is hardcoded. */ String loaderConfigDirPath = System.getProperty("JCONFIG_CDIR", "etc/yjava_ymail_config_loader"); /** * @param args * @throws Exception */ public static void main(final String[] args) throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException, ConfigException { boolean configLoaderNotFound = false; try { logger.info("Start running Config loader. "); ConfigLoaderJvm vm = new ConfigLoaderJvm(); vm.attach(); // We are already running, only one instance at a time... // TODO : vm.isRunning() call versus Attach attempt and catch // exception logger.error("Instance of Config Loader already running; this instance will not launch"); vm.close(); } catch (VirtualMachineException e) { configLoaderNotFound = true; } // If config loader is running exit this JVM, otherwise start it. if (configLoaderNotFound) { ConfigLoaderRunner runner = new ConfigLoaderRunner(); runner.start(); } } public void start() throws InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException, MalformedObjectNameException, NullPointerException, ConfigException { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); /* * Create ConfigLoaderJmx instance with default constructor, since * ConfigLoaderJmx init requires ConfigLoaderConfig which will again * result to chicken and egg problem */ ConfigLoaderJmx mbean = new ConfigLoaderJmx(); mbs.registerMBean(mbean, new ObjectName( ConfigLoaderJvm.CONFIG_LOADER_MBEAN_NAME)); loadLoaderConfig(); mbean.init(config); startConfServerReader(); startJMXReader(); executor = new WorkerExecutorService("ConfigLoaderExecutor", config .getMaxWorkerThreads().intValue()); try { logger.info("Start worker job for pushing configs to applications. "); executor.submit(new ConfigLoaderWorker(executor, mbean)).get(); } catch (InterruptedException e) { logger.error("Error running ConfigLoaderWorker ", e); } catch (ExecutionException e) { logger.error("Error running ConfigLoaderWorker ", e); } } /** * Start task to read config files from config server, merge the files and * save the merged file */ private void startConfServerReader() { logger.info("start reading config files from server " + config.getConfigServerURL()); final ConfigMerger merger = new ConfigMerger(config); Runnable r1 = new Runnable() { @Override public void run() { try { merger.mergeConfig(); } catch (Exception e) { // any exception thrown will suppress future calls to this // scheduler and hence catch all exceptions. logger.error("error in config server scheduler: ", e); } } }; logger.info("Start task for reading config files from config server every " + config.getConfigServerReadInterval().toSeconds() + "s"); confServerReader.scheduleAtFixedRate(r1, 0, config .getConfigServerReadInterval().toSeconds(), TimeUnit.SECONDS); } private static Charset UTF8 = Charset.forName("UTF-8"); /** * @throws ConfigException * */ private void loadLoaderConfig() throws ConfigException { logger.info("loading ConfigLoaderConfig "); config = new ConfigLoaderConfig(); ConfigManagerConfig configManagerConfig = new ConfigManagerConfig(); configManagerConfig.setConfigPath(loaderConfigDirPath); ConfigResource anno = ConfigLoaderConfig.class .getAnnotation(ConfigResource.class); ConfigAdapterJson adapter = new ConfigAdapterJson(anno.name(), Charset.forName("UTF-8"), configManagerConfig); ConfigManagerCache localCache = new ConfigManagerCache( ConfigManager.INSTANCE); adapter.loadValue(localCache); localCache.flipCache(); ConfigContext context = new ConfigContext(); context.put("TLD", "com"); ConfigManager.INSTANCE.buildConfigObject(config, context, UTF8, localCache); } /** * Start task to read application MBeans */ private void startJMXReader() { /* * Run JMX reader application in a separate thread. It logs application * MBeans to log file. This file is hacked to be read via tomcat * application. */ Runnable r1 = new Runnable() { @Override public void run() { try { String[] args = { "-save", config.getJmxFileName() }; ConfManCmd.main(args); } catch (Exception e) { // any exception thrown will suppress future calls to this // scheduler and hence catch all exceptions. logger.error("error jmx scheduler: ", e); } } }; logger.info("Start thread for collecting MBeans for applications running after every " + config.getJmxReadInterval().toSeconds() + "s"); jmxScheduler.scheduleAtFixedRate(r1, 30, config.getJmxReadInterval() .toSeconds(), TimeUnit.SECONDS); } }