com.eucalyptus.simpleworkflow.common.client.WorkflowClientStandalone.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.simpleworkflow.common.client.WorkflowClientStandalone.java

Source

/*************************************************************************
 * Copyright 2009-2016 Eucalyptus Systems, Inc.
 *
 * 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; version 3 of the License.
 *
 * 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/.
 *
 * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
 * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
 * additional information or have any questions.
 ************************************************************************/
package com.eucalyptus.simpleworkflow.common.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflow;
import com.amazonaws.services.simpleworkflow.AmazonSimpleWorkflowClient;
import com.amazonaws.services.simpleworkflow.flow.annotations.Activities;
import com.amazonaws.services.simpleworkflow.flow.annotations.Workflow;
import com.eucalyptus.records.Logs;
import com.eucalyptus.system.Ats;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * @author Sang-Min Park (sangmin.park@hpe.com)
 *
 */
public class WorkflowClientStandalone {
    private static Logger LOG = Logger.getLogger(WorkflowClientStandalone.class);
    private static WorkflowClientStandalone _instance = new WorkflowClientStandalone();

    public static WorkflowClientStandalone getInstance() {
        return _instance;
    }

    private List<Class> activityClasses = Lists.newArrayList();
    private List<Class> workflowClasses = Lists.newArrayList();
    private List<WorkflowClient> clients = Lists.newArrayList();
    private String jarFile = null;
    private Set<String> allowedClassNames = Sets.newHashSet();
    private String credentialPropertyFile = null;
    private String swfEndpoint = null;
    private String taskList = null;
    private String domain = null;
    private int clientConnectionTimeout = 30000;
    private int clientMaxConnections = 100;
    private int domainRetentionPeriodInDays = 1;
    private int pollThreadCount = 1;

    private String logLevel = "DEBUG";
    private String logDir = "/var/log/eucalyptus";
    private String logAppender = "console-log";

    @SuppressWarnings("static-access")
    private static Options buildOptions() {
        final Options opts = new Options();
        opts.addOption(OptionBuilder.withLongOpt("endpoint").hasArgs(1).withDescription("SWF Service Endpoint")
                .isRequired().create('e'));

        opts.addOption(OptionBuilder.withLongOpt("domain").hasArgs(1).withDescription("SWF Domain").isRequired()
                .create('d'));

        opts.addOption(OptionBuilder.withLongOpt("tasklist").hasArgs(1).withDescription("SWF task list")
                .isRequired().create('l'));

        opts.addOption(OptionBuilder.withLongOpt("timeout").hasArgs(1)
                .withDescription("SWF client connection timeout").isRequired(false).create('o'));

        opts.addOption(OptionBuilder.withLongOpt("maxconn").hasArgs(1).withDescription("SWF client max connections")
                .isRequired(false).create('m'));

        opts.addOption(OptionBuilder.withLongOpt("retention").hasArgs(1)
                .withDescription("SWF domain retention period in days").isRequired(false).create('r'));

        opts.addOption(OptionBuilder.withLongOpt("threads").hasArgs(1).withDescription("Polling threads count")
                .isRequired(false).create('t'));

        opts.addOption(OptionBuilder.withLongOpt("jar").hasArgs(1)
                .withDescription("JAR file that implement workflows" + " and activities").isRequired(true)
                .create());

        opts.addOption(OptionBuilder.withLongOpt("classes").hasArgs(1)
                .withDescription("Limit workflow and activities classes to load (class names are separated by ':')")
                .isRequired(false).create());

        opts.addOption(OptionBuilder.withLongOpt("credential").hasArgs(1).withDescription(
                "Property file containing AWS credentials to use (default is to use session credentials from instance's metadata")
                .isRequired(false).create());

        opts.addOption(OptionBuilder.withLongOpt("loglevel").hasArgs(1)
                .withDescription("Logging level (default: DEBUG)").isRequired(false).create());

        opts.addOption(OptionBuilder.withLongOpt("logdir").hasArgs(1)
                .withDescription("Directory containing log files (default: /var/log/eucalyptus)").isRequired(false)
                .create());

        opts.addOption(OptionBuilder.withLongOpt("logappender").hasArgs(1).withDescription("Log4j appender to use")
                .isRequired(false).create());

        return opts;
    }

    private void readOptions(final CommandLine cli) throws NumberFormatException {
        this.swfEndpoint = cli.getOptionValue("endpoint");
        this.domain = cli.getOptionValue("domain");
        this.taskList = cli.getOptionValue("tasklist");

        if (cli.hasOption("timeout"))
            this.clientConnectionTimeout = Integer.parseInt(cli.getOptionValue("timeout"));
        if (cli.hasOption("maxconn"))
            this.clientMaxConnections = Integer.parseInt(cli.getOptionValue("maxconn"));
        if (cli.hasOption("retention"))
            this.domainRetentionPeriodInDays = Integer.parseInt(cli.getOptionValue("retention"));
        if (cli.hasOption("threads"))
            this.pollThreadCount = Integer.parseInt(cli.getOptionValue("threads"));
        if (cli.hasOption("jar"))
            this.jarFile = cli.getOptionValue("jar");
        if (cli.hasOption("credential"))
            this.credentialPropertyFile = cli.getOptionValue("credential");
        if (cli.hasOption("loglevel"))
            this.logLevel = cli.getOptionValue("loglevel");
        if (cli.hasOption("logdir"))
            this.logDir = cli.getOptionValue("logdir");
        if (cli.hasOption("logappender"))
            this.logAppender = cli.getOptionValue("logappender");
        if (cli.hasOption("classes")) {
            final String classNames = cli.getOptionValue("classes");
            if (classNames.contains(":")) {
                for (final String cls : classNames.split(":")) {
                    this.allowedClassNames.add(cls);
                }
            } else {
                this.allowedClassNames.add(classNames);
            }
        }
    }

    private static void printHelp(final Options opts, final String error) {
        final HelpFormatter formatter = new HelpFormatter();
        formatter.setDescPadding(0);
        String header = "\n" + "Welcome to the Standalone SWF Host!\n"
                + "The program discovers and hosts SWF workflows and activities";
        final String footer = error == null ? "\n" : String.format("\n%s", error);
        formatter.printHelp(
                "java -cp jarfiles com.eucalyptus.simpleworkflow.common.client.WorkflowClientStandalone", header,
                opts, footer, true);
    }

    private static void initLogs() {
        final WorkflowClientStandalone instance = WorkflowClientStandalone.getInstance();
        System.setProperty("euca.log.level", instance.logLevel);
        System.setProperty("euca.log.dir", instance.logDir);
        System.setProperty("euca.log.appender", instance.logAppender);
        Logs.init();
    }

    private void discoverWorkflows() throws Exception {
        final File f = new File(this.jarFile);
        if (f.exists() && !f.isDirectory())
            processJar(f);
        else
            throw new Exception(String.format("No such file is found: %s", this.jarFile));
    }

    private void processJar(File f) throws Exception {
        final JarFile jar = new JarFile(f);
        final Properties props = new Properties();
        final List<JarEntry> jarList = Collections.list(jar.entries());
        LOG.trace("-> Trying to load component info from " + f.getAbsolutePath());
        for (final JarEntry j : jarList) {
            try {
                if (j.getName().matches(".*\\.class.{0,1}")) {
                    handleClassFile(f, j);
                }
            } catch (RuntimeException ex) {
                LOG.error(ex, ex);
                jar.close();
                throw ex;
            }
        }
        jar.close();
    }

    private void handleClassFile(final File f, final JarEntry j) throws IOException, RuntimeException {
        final String classGuess = j.getName().replaceAll("/", ".").replaceAll("\\.class.{0,1}", "");
        try {
            final Class candidate = ClassLoader.getSystemClassLoader().loadClass(classGuess);
            final Ats ats = Ats.inClassHierarchy(candidate);
            if ((this.allowedClassNames.isEmpty() || this.allowedClassNames.contains(candidate.getName())
                    || this.allowedClassNames.contains(candidate.getCanonicalName())
                    || this.allowedClassNames.contains(candidate.getSimpleName()))
                    && (ats.has(Workflow.class) || ats.has(Activities.class))
                    && !Modifier.isAbstract(candidate.getModifiers())
                    && !Modifier.isInterface(candidate.getModifiers()) && !candidate.isLocalClass()
                    && !candidate.isAnonymousClass()) {
                if (ats.has(Workflow.class)) {
                    this.workflowClasses.add(candidate);
                    LOG.debug("Discovered workflow implementation class: " + candidate.getName());
                } else {
                    this.activityClasses.add(candidate);
                    LOG.debug("Discovered activity implementation class: " + candidate.getName());
                }
            }
        } catch (final ClassNotFoundException e) {
            LOG.debug(e, e);
        }
    }

    private AWSCredentialsProvider getCredentialsProvider() {
        AWSCredentialsProvider provider = null;
        if (this.credentialPropertyFile != null) {
            provider = new AWSCredentialsProvider() {
                private String accessKey = null;
                private String secretAccessKey = null;

                private void readProperty() throws FileNotFoundException, IOException {
                    final FileInputStream stream = new FileInputStream(new File(credentialPropertyFile));
                    try {
                        Properties credentialProperties = new Properties();
                        credentialProperties.load(stream);

                        if (credentialProperties.getProperty("accessKey") == null
                                || credentialProperties.getProperty("secretKey") == null) {
                            throw new IllegalArgumentException("The specified file (" + credentialPropertyFile
                                    + ") doesn't contain the expected properties 'accessKey' "
                                    + "and 'secretKey'.");
                        }
                        accessKey = credentialProperties.getProperty("accessKey");
                        secretAccessKey = credentialProperties.getProperty("secretKey");
                    } finally {
                        try {
                            stream.close();
                        } catch (final IOException e) {
                        }
                    }
                }

                @Override
                public AWSCredentials getCredentials() {
                    if (this.accessKey == null || this.secretAccessKey == null) {
                        try {
                            readProperty();
                        } catch (final Exception ex) {
                            throw new RuntimeException("Failed to read credentials file", ex);
                        }
                    }
                    return new BasicAWSCredentials(accessKey, secretAccessKey);
                }

                @Override
                public void refresh() {
                    this.accessKey = null;
                }
            };
        } else {
            provider = new InstanceProfileCredentialsProvider();
        }

        return provider;
    }

    private ClientConfiguration buildClientConfig() {
        final ClientConfiguration configuration = new ClientConfiguration();
        configuration.setConnectionTimeout(this.clientConnectionTimeout);
        configuration.setMaxConnections(this.clientMaxConnections);
        return configuration;
    }

    private String buildWorkflowWorkerConfig() {
        return String.format("{ \"DomainRetentionPeriodInDays\": %d, \"PollThreadCount\": %d }",
                this.domainRetentionPeriodInDays, this.pollThreadCount);
    }

    private String buildActivityWorkerConfig() {
        return String.format("{ \"DomainRetentionPeriodInDays\": %d, \"PollThreadCount\": %d }",
                this.domainRetentionPeriodInDays, this.pollThreadCount);
    }

    private AmazonSimpleWorkflow getAWSClient() {
        final AWSCredentialsProvider provider = this.getCredentialsProvider();
        final ClientConfiguration configuration = this.buildClientConfig();
        final AmazonSimpleWorkflow client = new AmazonSimpleWorkflowClient(provider, configuration);
        client.setEndpoint(this.swfEndpoint);
        return client;
    }

    private static void addShutdownHook(final AmazonSimpleWorkflow swfClient) {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            public void run() {
                LOG.debug("Shutting down existing SWF clients");
                final WorkflowClientStandalone instance = WorkflowClientStandalone.getInstance();
                for (final WorkflowClient client : instance.clients) {
                    try {
                        client.stop();
                    } catch (final InterruptedException ex) {
                        ;
                    }
                }
                swfClient.shutdown();
            }
        }));
    }

    public static void main(String[] args) {
        final WorkflowClientStandalone instance = WorkflowClientStandalone.getInstance();
        final Options opts = buildOptions();
        final GnuParser cliParser = new GnuParser();
        CommandLine cmd = null;
        try {
            cmd = cliParser.parse(opts, args);
        } catch (final ParseException ex) {
            printHelp(opts, null);
            System.exit(1);
        }

        try {
            instance.readOptions(cmd);
        } catch (final NumberFormatException ex) {
            printHelp(opts, "Some number format arguents are not recognizable");
            System.exit(1);
        }

        initLogs();

        LOG.debug("Starting Workflow Standalone Host");
        try {
            instance.discoverWorkflows();
        } catch (final Exception ex) {
            LOG.debug("Failed to discover workflow and activities implementation");
            printHelp(opts, "Failed to discover implementation classes");
            System.exit(1);
        }

        try {
            final AmazonSimpleWorkflow swfClient = instance.getAWSClient();
            addShutdownHook(swfClient);

            final WorkflowClient workflowClient = new WorkflowClient(
                    instance.workflowClasses.toArray(new Class<?>[instance.workflowClasses.size()]),
                    instance.activityClasses.toArray(new Class<?>[instance.activityClasses.size()]), false,
                    swfClient, instance.domain, instance.taskList, instance.buildWorkflowWorkerConfig(),
                    instance.buildActivityWorkerConfig());
            workflowClient.start();
            instance.clients.add(workflowClient);
        } catch (final Exception ex) {
            LOG.debug("Failed to create workflow clients", ex);
            System.exit(1);
        }

        do {
            try {
                Thread.sleep(1000);
            } catch (final Exception ex) {
            }
        } while (true);
    }
}