diff --git a/appLoader/pom.xml b/appLoader/pom.xml index ca76d169..b78d523f 100644 --- a/appLoader/pom.xml +++ b/appLoader/pom.xml @@ -11,26 +11,26 @@ - maven-assembly-plugin + org.apache.maven.plugins + maven-jar-plugin - false appLoader com.vmware.WorkflowAppLoader + + com.vmware.WorkflowRunner + workflow-[\w_]+.jar + workflow-May_14_2021.jar + https://github.com/vmware/workflowTools/releases/download/May_13_2021/workflow.jar + - - jar-with-dependencies - make-assembly package - - single - diff --git a/appLoader/src/main/java/com/vmware/AppLauncher.java b/appLoader/src/main/java/com/vmware/AppLauncher.java new file mode 100644 index 00000000..c1bdc573 --- /dev/null +++ b/appLoader/src/main/java/com/vmware/AppLauncher.java @@ -0,0 +1,16 @@ +package com.vmware; + +import java.util.List; + +/** + * Main class for jar application + */ +public interface AppLauncher { + /** + * Launches application + * + * @param appClassLoader class loader to use for the application jar file. + * @param args command line arguments passed in + */ + void run(ClassLoader appClassLoader, List args); +} diff --git a/appLoader/src/main/java/com/vmware/WorkflowAppLoader.java b/appLoader/src/main/java/com/vmware/WorkflowAppLoader.java index 04961c8f..ef61ad5f 100644 --- a/appLoader/src/main/java/com/vmware/WorkflowAppLoader.java +++ b/appLoader/src/main/java/com/vmware/WorkflowAppLoader.java @@ -3,27 +3,35 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.lang.reflect.Method; +import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.zip.ZipEntry; public class WorkflowAppLoader { - private static final String RELEASE_DATE = "May_13_2021"; - private static final String RELEASE_URL = "https://github.com/vmware/workflowTools/releases/download/" + RELEASE_DATE + "/workflow.jar"; - private static final String RELEASE_NAME = "workflow-" + RELEASE_DATE + ".jar"; - private final String tempDirectory; + private final String releaseDirectory; private final List argValues; - private final boolean debugLog; - private final File expectedReleaseJar; + private final boolean debugLog, reset; + private final File releaseJar; + private final Map manifestAttributes; + private final File testReleaseJar; public static void main(String[] args) { WorkflowAppLoader loader = new WorkflowAppLoader(args); @@ -32,46 +40,43 @@ public static void main(String[] args) { } public WorkflowAppLoader(String[] args) { - this.tempDirectory = getTempDirectory(); - this.argValues = Arrays.asList(args); + this.argValues = new ArrayList<>(Arrays.asList(args)); this.debugLog = Stream.of("-d", "--debug", "-t", "--trace").anyMatch(argValues::contains); - this.expectedReleaseJar = new File(tempDirectory + File.separator + RELEASE_NAME); + this.reset = argValues.remove("--reset"); + this.manifestAttributes = getManifestAttributes(); + this.releaseDirectory = manifestAttributes.containsKey("releaseDirectory") + ? manifestAttributes.get("releaseDirectory") : System.getProperty("java.io.tmpdir"); + this.releaseJar = new File(this.releaseDirectory + File.separator + manifestAttributes.get("releaseJarName")); + Optional testReleaseJarPath = getArgValue("--test-release-jar"); + this.testReleaseJar = testReleaseJarPath.map(File::new).orElse(null); } public void executeWorkflowJar() { - if (debugLog) { - System.out.println("Launching workflow jar with args " + argValues); - } + debug("Launching workflow jar with args " + argValues); try { - URLClassLoader urlClassLoader = new URLClassLoader( - new URL[] {expectedReleaseJar.toURI().toURL()}, - this.getClass().getClassLoader() - ); - Class classToLoad = Class.forName("com.vmware.WorkflowRunner", true, urlClassLoader); - Method method = classToLoad.getDeclaredMethod("runWorkflow", ClassLoader.class, List.class); - Object instance = classToLoad.newInstance(); - method.invoke(instance, urlClassLoader, argValues); + + URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[] { releaseJar.toURI().toURL()}, getClass().getClassLoader()); + Class classToLoad = (Class) urlClassLoader.loadClass(manifestAttributes.get("appMainClass")); + AppLauncher launcher = classToLoad.newInstance(); + launcher.run(urlClassLoader, argValues); } catch (Exception e) { throw new RuntimeException(e); } } public void downloadJarFileIfNeeded() { - if (debugLog) { - System.out.println("Expected release jar is " + expectedReleaseJar.getPath()); - } - if (expectedReleaseJar.exists()) { - if (debugLog) { - System.out.println("Jar file " + expectedReleaseJar.getPath() + " already exists"); - } + debug("Expected release jar is " + releaseJar.getPath()); + if (releaseJar.exists() && !reset) { + debug("Jar file " + releaseJar.getPath() + " already exists"); return; } + deleteOldReleasesIfNeeded(); URL releaseURL = createReleaseUrl(); - System.out.println("Downloading workflow release jar " + releaseURL.toString() + " to temp directory " + tempDirectory); + info("Downloading workflow release jar " + releaseURL.toString() + " to " + releaseJar.getPath()); try { ReadableByteChannel readableByteChannel = Channels.newChannel(releaseURL.openStream()); - FileOutputStream fileOutputStream = new FileOutputStream(expectedReleaseJar); + FileOutputStream fileOutputStream = new FileOutputStream(releaseJar); fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE); } catch (IOException e) { throw new RuntimeException(e); @@ -79,24 +84,79 @@ public void downloadJarFileIfNeeded() { } + private void deleteOldReleasesIfNeeded() { + String deleteOldReleasesPattern = manifestAttributes.get("deleteOldReleaseJarPattern"); + if (deleteOldReleasesPattern == null) { + debug("Delete old releases pattern not set, skipping deletion of old releases"); + } + Pattern deleteJarPattern = Pattern.compile(deleteOldReleasesPattern); + File[] matchingReleases = new File(releaseDirectory).listFiles(file -> deleteJarPattern.matcher(file.getName()).matches()); + Arrays.stream(matchingReleases).forEach(release -> { + info("Deleting old release " + release.getPath()); + release.delete(); + }); + } + private URL createReleaseUrl() { + if (testReleaseJar != null) { + info("Using test release file " + testReleaseJar.getPath()); + try { + return testReleaseJar.toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } URL releaseURL; try { - releaseURL = URI.create(RELEASE_URL).toURL(); + releaseURL = URI.create(manifestAttributes.get("releaseUrl")).toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } return releaseURL; } - private String getTempDirectory() { + private void debug(String message) { + if (debugLog) { + System.out.println(message); + } + } + + private void info(String message) { + System.out.println(message); + } + + private Map getManifestAttributes() { + Optional jarFilePath = getArgValue("--loader-jar-file"); + + InputStream manifestInputStream; + if (jarFilePath.isPresent()) { + info("Loading manifest from jar file " + jarFilePath.get()); + try { + JarFile jarFile = new JarFile(jarFilePath.get()); + ZipEntry manifestEntry = jarFile.getEntry(JarFile.MANIFEST_NAME); + manifestInputStream = jarFile.getInputStream(manifestEntry); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + manifestInputStream = getClass().getClassLoader().getResourceAsStream(JarFile.MANIFEST_NAME); + } + try { - File tempFile = File.createTempFile("sample", "txt"); - String directory = tempFile.getParent(); - tempFile.delete(); - return directory; + Manifest manifest = new Manifest(manifestInputStream); + Attributes mainAttributes = manifest.getMainAttributes(); + Set attributeKeys = mainAttributes.keySet(); + Map attributeValues = attributeKeys.stream().collect(Collectors.toMap(String::valueOf, key -> mainAttributes.getValue((Attributes.Name) key))); + debug("Manifest Attribute values " + attributeValues); + return attributeValues; } catch (IOException e) { throw new RuntimeException(e); } } + + private Optional getArgValue(String argName) { + Optional argValue = argValues.stream().filter(arg -> arg.startsWith(argName + "=")).map(arg -> arg.split("=")[1]).findFirst(); + argValues.removeIf(arg -> arg.startsWith(argName + "=")); + return argValue; + } } diff --git a/config/src/main/java/com/vmware/config/WorkflowConfig.java b/config/src/main/java/com/vmware/config/WorkflowConfig.java index b0b02825..528538e3 100644 --- a/config/src/main/java/com/vmware/config/WorkflowConfig.java +++ b/config/src/main/java/com/vmware/config/WorkflowConfig.java @@ -67,7 +67,7 @@ public class WorkflowConfig { private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); private static final SimpleDateFormat timeFormat = new SimpleDateFormat("HHmmss"); - public static ClassLoader realClassLoader; + public static ClassLoader appClassLoader; @SectionConfig public LoggingConfig loggingConfig; diff --git a/core/pom.xml b/core/pom.xml index 4a355fd8..963e55c2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,6 +14,11 @@ serviceApis 1.0 + + com.vmware + appLoader + 1.0 + com.jcraft jsch @@ -62,7 +67,7 @@ maven-assembly-plugin false - workflow + workflowTools com.vmware.WorkflowRunner diff --git a/core/src/main/java/com/vmware/Workflow.java b/core/src/main/java/com/vmware/Workflow.java index 079df1f8..d1027ffe 100644 --- a/core/src/main/java/com/vmware/Workflow.java +++ b/core/src/main/java/com/vmware/Workflow.java @@ -83,8 +83,8 @@ public class Workflow { private String username = null; private final List args; - public Workflow(ClassLoader classLoader, List args) { - WorkflowConfig.realClassLoader = classLoader; + public Workflow(ClassLoader appClassLoader, List args) { + WorkflowConfig.appClassLoader = appClassLoader; this.args = args; } @@ -198,7 +198,7 @@ private ArgumentCompleter createWorkflowCompleter() { autocompleteList.add(workflow); } } - WorkflowActions workflowActions = new WorkflowActions(config, WorkflowConfig.realClassLoader); + WorkflowActions workflowActions = new WorkflowActions(config, WorkflowConfig.appClassLoader); // ! means that it won't show up if nothing is entered autocompleteList.addAll(workflowActions.getWorkflowActionClasses() .stream().map(workflowAction -> "!" + workflowAction.getSimpleName()).collect(Collectors.toList())); @@ -232,7 +232,7 @@ public void runWorkflow() { return; } - WorkflowActions workflowActions = new WorkflowActions(config, WorkflowConfig.realClassLoader); + WorkflowActions workflowActions = new WorkflowActions(config, WorkflowConfig.appClassLoader); List actions = workflowActions.determineActions(workflowToRun); // update history file after all the workflow has been determined to be valid updateWorkflowHistoryFile(); @@ -288,7 +288,7 @@ private void outputTotalExecutionTime(Date startingDate) { private void checkAllActionsCanBeInstantiated(boolean runAllHelperMethods) { log.info("Checking that each action value in the workflows is valid"); - WorkflowActions workflowActions = new WorkflowActions(config, WorkflowConfig.realClassLoader); + WorkflowActions workflowActions = new WorkflowActions(config, WorkflowConfig.appClassLoader); List actions = workflowActions.determineActions(StringUtils.join(config.workflows.keySet())); WorkflowActionValues actionValues = new WorkflowActionValues(); diff --git a/core/src/main/java/com/vmware/WorkflowRunner.java b/core/src/main/java/com/vmware/WorkflowRunner.java index afaf88e8..0297a2df 100644 --- a/core/src/main/java/com/vmware/WorkflowRunner.java +++ b/core/src/main/java/com/vmware/WorkflowRunner.java @@ -11,19 +11,20 @@ /** * Class that starts the workflow tools app. */ -public class WorkflowRunner { +public class WorkflowRunner implements AppLauncher { public static void main(String[] args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - new WorkflowRunner().runWorkflow(classLoader, Arrays.asList(args)); + new WorkflowRunner().run(classLoader, Arrays.asList(args)); } - public void runWorkflow(ClassLoader classLoader, List args) { + @Override + public void run(ClassLoader appClassLoader, List args) { LogManager.getLogManager().reset(); java.util.logging.Logger globalLogger = java.util.logging.Logger.getLogger("com.vmware"); globalLogger.addHandler(createHandler()); - Workflow workflow = new Workflow(classLoader, args); + Workflow workflow = new Workflow(appClassLoader, args); workflow.runWorkflow(); } diff --git a/core/src/main/java/com/vmware/action/info/DisplayWorkflowActions.java b/core/src/main/java/com/vmware/action/info/DisplayWorkflowActions.java index 0b00edf0..1f461d00 100644 --- a/core/src/main/java/com/vmware/action/info/DisplayWorkflowActions.java +++ b/core/src/main/java/com/vmware/action/info/DisplayWorkflowActions.java @@ -46,7 +46,7 @@ public void process() { private Map>> generateClassMap() { Map>> classes = new TreeMap>>(); - List> workflowActions = new WorkflowActions(config, WorkflowConfig.realClassLoader).getWorkflowActionClasses(); + List> workflowActions = new WorkflowActions(config, WorkflowConfig.appClassLoader).getWorkflowActionClasses(); for (Class action : workflowActions) { String[] pieces = action.getName().split("\\."); String packageName = pieces[pieces.length - 2]; diff --git a/core/src/main/java/com/vmware/mapping/ConfigValuesCompleter.java b/core/src/main/java/com/vmware/mapping/ConfigValuesCompleter.java index b7fb4fba..a136257f 100644 --- a/core/src/main/java/com/vmware/mapping/ConfigValuesCompleter.java +++ b/core/src/main/java/com/vmware/mapping/ConfigValuesCompleter.java @@ -8,7 +8,6 @@ import java.util.SortedSet; import java.util.TreeSet; -import com.vmware.Workflow; import com.vmware.action.BaseAction; import com.vmware.config.ReplacementVariables; import com.vmware.config.WorkflowAction; @@ -41,7 +40,7 @@ public class ConfigValuesCompleter extends ImprovedStringsCompleter implements C public ConfigValuesCompleter(WorkflowConfig config) { this.configMappings = new ConfigMappings(); this.config = config; - this.workflowActions = new WorkflowActions(config, WorkflowConfig.realClassLoader).getWorkflowActionClasses(); + this.workflowActions = new WorkflowActions(config, WorkflowConfig.appClassLoader).getWorkflowActionClasses(); super.values.addAll(configMappings.allConfigValues()); } diff --git a/pom.xml b/pom.xml index af445e3c..4e633bfc 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ 4.0.0 1.0 + appLoader core serviceApis config utils - appLoader diff --git a/workflow.jar b/workflow.jar index 00129421..072d9138 100644 Binary files a/workflow.jar and b/workflow.jar differ