com.siberhus.easyexecutor.EasyExecutor.java Source code

Java tutorial

Introduction

Here is the source code for com.siberhus.easyexecutor.EasyExecutor.java

Source

/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */
package com.siberhus.easyexecutor;

import it.sauronsoftware.cron4j.Scheduler;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.xml.DOMConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.siberhus.commons.converter.TypeConvertUtils;
import com.siberhus.commons.io.DirectoryFileUtils;
import com.siberhus.commons.io.FileLocker;
import com.siberhus.commons.lang.ClasspathUtils;
import com.siberhus.commons.lang.SystemProperties;
import com.siberhus.commons.properties.PropertiesUtil;
import com.siberhus.commons.util.ElapsedTimeUtils;
import com.siberhus.commons.util.ResourceUtils;
import com.siberhus.commons.xml.DataElement;
import com.siberhus.commons.xml.DomElementFinder;

/**
 * 
 * @author Hussachai Puripunpinyo (http://www.siberhus.com)
 * 
 */
public class EasyExecutor {

    public static enum LOG_IMPL {
        log4j, jdk
    }

    private static Logger logger;

    private static final Map<String, Properties> USER_PROPERTIES_MAP = new HashMap<String, Properties>();

    private static EasyExecutor instance;

    private ApplicationTask appTask;
    private Map<String, BeanExecutor> beanExecutorMap;

    private boolean scheduled = false;
    private ApplicationInfo applicationInfo;
    private FileLocker fileLocker;
    private boolean singleInstance = false;
    private Locale locale = Locale.getDefault();
    private String commandLineArgs[];
    private File configurationFile;
    private Date executionTime;

    static {
        /*
         * Fix Log4j "append to closed appender named [stdout]" issue.
         * This work around is quite non-sense but it works!
         */
        System.setProperty("log4j.configuration", "");
    }

    private EasyExecutor(File configFile, String[] args) throws Exception {

        this.executionTime = new Date();
        this.configurationFile = configFile;
        this.commandLineArgs = args;

        DomElementFinder def = DomElementFinder.newInstance(configFile);

        this.singleInstance = def.getRootElement().getAttribute(Boolean.class, "single-instance");
        //      boolean showTray = def.getRootElement().getAttribute(Boolean.class, "show-tray");
        this.locale = def.getRootElement().getAttribute(Locale.class, "locale");

        Locale.setDefault(getLocale());

        //=========== Recursive configure =============//
        configure(def);
        //=============================================//

        appTask = new ApplicationTask();
        DataElement appElem = def.findElement("application");

        String appId = appElem.getAttribute("id");
        String appMode = appElem.getAttribute("mode");

        String appName = def.getElement(appElem, "name").getValue(appId);
        String appVersion = def.getElement(appElem, "version").getValue("0.0");
        String appDesc = def.getElement(appElem, "description").getValue();

        //=========== System Properties ==================//
        System.setProperty("easyx.locale", locale.toString());
        System.setProperty("easyx.app.id", appId);
        System.setProperty("easyx.app.name", appName);
        System.setProperty("easyx.app.version", appVersion);

        applicationInfo = new ApplicationInfo();
        applicationInfo.setId(appId);
        applicationInfo.setMode(appMode);

        applicationInfo.setName(appName);
        applicationInfo.setVersion(appVersion);
        applicationInfo.setDescription(appDesc);

        //=========== Init Logger =======================//
        DataElement logElem = def.findElement(appElem, "logger");
        if (logElem != null) {
            LOG_IMPL logImpl = logElem.getAttribute(LOG_IMPL.class, "impl", LOG_IMPL.log4j);
            File logConfigFile = ResourceUtils.getFile(logElem.getAttribute("config"));
            if (logConfigFile.exists()) {
                if (logImpl == LOG_IMPL.log4j) {
                    org.apache.log4j.Logger.getRootLogger().removeAllAppenders();
                    if (logConfigFile.getName().endsWith(".xml")) {
                        DOMConfigurator.configure(logConfigFile.getAbsolutePath());
                    } else {
                        PropertyConfigurator.configure(logConfigFile.getAbsolutePath());
                    }
                } else if (logImpl == LOG_IMPL.jdk) {
                    System.setProperty("java.util.logging.config.file", logConfigFile.getAbsolutePath());
                }
            }
        }
        DataElement cronElem = def.findElement(appElem, "cron");
        if (cronElem != null) {
            scheduled = true;
            String cronExpression = cronElem.getAttribute("expression");
            cronExpression = CronEntries.resolveEntry(cronExpression);
            Scheduler scheduler = new Scheduler();
            scheduler.schedule(cronExpression, appTask);
            scheduler.start();
        }
        logger = LoggerFactory.getLogger(EasyExecutor.class);
        logger.info("Allow multiple instances? {}", !singleInstance);
        logger.info("Default locale: {}", locale);

        //=========== Ensure only one instance is run at a time =========//
        ensureSingleInstance(appId);
        //===============================================================//

        beanExecutorMap = new LinkedHashMap<String, BeanExecutor>();

        List<DataElement> beanElems = def.findElements(appElem, "beans/bean");
        for (DataElement beanElem : beanElems) {
            BeanExecutor be = createBeanExecutor(def, beanElem, args);
            beanExecutorMap.put(be.getId(), be);
        }

    }

    public static EasyExecutor getInstance() {
        return instance;
    }

    public boolean isScheduled() {
        return scheduled;
    }

    public BeanExecutor getBeanExecutor(String id) {
        return beanExecutorMap.get(id);
    }

    public boolean isSingleInstance() {
        return singleInstance;
    }

    public Locale getLocale() {
        return locale;
    }

    public ApplicationInfo getApplicationInfo() {
        return applicationInfo;
    }

    public Properties getProperties(String name) {
        return USER_PROPERTIES_MAP.get(name);
    }

    public String[] getCommandLineArgs() {
        return commandLineArgs;
    }

    public File getConfigurationFile() {
        return configurationFile;
    }

    public Date getExecutionTime() {
        return executionTime;
    }

    private void configure(DomElementFinder def) throws SAXException, IOException, ParserConfigurationException {

        List<DataElement> importElemList = def.getElements("import");
        for (DataElement importElem : importElemList) {
            String location = importElem.getAttribute("location");
            File pConfigFile = ResourceUtils.getFile(location);
            DomElementFinder pDef = DomElementFinder.newInstance(pConfigFile);

            configure(pDef);

        }

        DataElement sysPropElem = def.findElement("system-properties");
        if (sysPropElem != null) {
            String sysPropFilePath = sysPropElem.getAttribute("location");
            if (sysPropFilePath != null) {
                Properties sysProp = PropertiesUtil.create(sysPropFilePath);
                overridePropeties(System.getProperties(), sysProp);
            }
            for (DataElement pElem : def.getElements(sysPropElem, "property")) {
                String key = pElem.getAttribute("key");
                String value = pElem.getValue();
                System.getProperties().setProperty(key, value);
            }
        }

        List<DataElement> userPropElemList = def.getElements("user-properties");
        for (DataElement userPropElem : userPropElemList) {
            String userPropName = userPropElem.getAttribute("name");
            Properties mainUserProp = USER_PROPERTIES_MAP.get(userPropName);
            if (mainUserProp == null) {
                mainUserProp = new Properties();
                USER_PROPERTIES_MAP.put(userPropName, mainUserProp);
            }
            String parentUserPropName = userPropElem.getAttribute("extends");
            if (parentUserPropName != null) {
                Properties parentUserProp = USER_PROPERTIES_MAP.get(parentUserPropName);
                if (parentUserProp != null) {
                    overridePropeties(mainUserProp, parentUserProp);
                }
            }
            String userPropFilePath = userPropElem.getAttribute("location");
            if (userPropFilePath != null) {
                File userPropFile = ResourceUtils.getFile(userPropFilePath);
                Properties userProp = PropertiesUtil.create(userPropFile);
                overridePropeties(mainUserProp, userProp);
            }
            for (DataElement pElem : def.getElements(userPropElem, "property")) {
                String key = pElem.getAttribute("key");
                String value = pElem.getValue();
                mainUserProp.setProperty(key, value);
            }
        }

        DataElement classpathElem = def.findElement("classpath");
        if (classpathElem != null) {
            List<DataElement> classesElemList = def.getElements(classpathElem, "classes");
            for (DataElement classesElem : classesElemList) {
                File classesPathDir = classesElem.getAttribute(File.class, "path");
                if (classesPathDir.exists()) {
                    ClasspathUtils.addClassesDir(classesPathDir);
                }
            }
            List<DataElement> libElemList = def.getElements(classpathElem, "lib");
            for (DataElement libElem : libElemList) {
                File libPathDir = libElem.getAttribute(File.class, "path");
                if (libPathDir.exists()) {
                    ClasspathUtils.addLibDir(libPathDir);
                }
            }
        }

    }

    private BeanExecutor createBeanExecutor(DomElementFinder def, DataElement beanElem, String args[])
            throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException {

        BeanExecutor be = new BeanExecutor();

        String beanId = beanElem.getAttribute("id");
        be.setId(beanId);
        Class<?> beanClass = beanElem.getAttribute(Class.class, "class");
        be.setTargetClass(beanClass);
        be.setTargetObject(beanClass.newInstance());

        List<DataElement> methodElems = def.findElements(beanElem, "method");

        for (DataElement methodElem : methodElems) {
            if (methodElem != null) {
                String methodName = methodElem.getAttribute("name");
                Map<String, String> argMap = new HashMap<String, String>();
                for (int i = 0; i < args.length; i++) {
                    argMap.put(String.valueOf(i), args[i]);
                }

                List<DataElement> argElemList = def.findElements(methodElem, "arg");
                if (argElemList != null) {
                    int argSize = argElemList.size();
                    Class<?> argTypes[] = new Class[argSize];
                    Object argValues[] = new Object[argSize];
                    for (int i = 0; i < argSize; i++) {
                        DataElement paramElem = argElemList.get(i);
                        Class<?> argType = paramElem.getAttribute(Class.class, "type");
                        argTypes[i] = argType;
                        String argValueStr = paramElem.getValue();
                        argValueStr = StrSubstitutor.replace(argValueStr, argMap);
                        argValues[i] = TypeConvertUtils.convert(argValueStr, getLocale(), argType);
                    }
                    be.addMethodInvoker(methodName, argTypes, argValues);
                } else {
                    be.addMethodInvoker(methodName, null, null);
                }
            } else {
                Object passedArgs[] = { args };// Create the actual argument array
                be.addMethodInvoker("main", new Class[] { String[].class }, passedArgs);
            }
        }

        return be;
    }

    private void ensureSingleInstance(final String appId) {
        if (!isSingleInstance()) {
            logger.info("This appId: '{}' can be run in multiple instances.", appId);
            return;
        }
        fileLocker = new FileLocker();
        File eeDir = new File(SystemProperties.USER_HOME + File.separator + ".easyexecutor");
        if (!eeDir.exists()) {
            eeDir.mkdir();
        }
        File lockFile = DirectoryFileUtils.getFile(eeDir, appId + ".lck");
        logger.debug("Locking file for: '{}'", appId);
        fileLocker.lock(lockFile);

        if (!fileLocker.canLock()) {
            logger.error("Another instance is running.");
            System.exit(1);
        }
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                logger.debug("Release file locking for: '{}'", appId);
                fileLocker.release();
            }
        });
    }

    private void overridePropeties(Properties main, Properties overrider) {
        for (Object keyObj : overrider.keySet()) {
            String key = (String) keyObj;
            String value = overrider.getProperty(key);
            main.setProperty(key, value);
        }
    }

    class ApplicationTask implements Runnable {

        @Override
        public void run() {
            logger.info("Executing Application: {}", getApplicationInfo());
            ElapsedTimeUtils.start("app");
            for (Map.Entry<String, BeanExecutor> entry : beanExecutorMap.entrySet()) {
                BeanExecutor be = entry.getValue();
                try {
                    be.execute();
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
            logger.info("Application '{}' consumes {}", getApplicationInfo(),
                    ElapsedTimeUtils.showElapsedTime("app"));
        }
    }

    public static void main(String[] args) throws Throwable {

        String configFilePath = args[0];
        File configFile = ResourceUtils.getFile(configFilePath);
        EasyExecutor.instance = new EasyExecutor(configFile, args);
        if (!EasyExecutor.instance.isScheduled()) {
            EasyExecutor.instance.appTask.run();
        }
    }

}