Java tutorial
/************************************************************************* * 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); } }