/* * Copyright 2010 Outerthought bvba * * Licensed 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. */ package org.lilyproject.tools.mavenplugin.genscript; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.handler.ArtifactHandler; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout; import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.model.Dependency; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; /** * @requiresDependencyResolution runtime * @goal genscript */ public class GenScriptMojo extends AbstractMojo { /** * A short name of the application without special characters. Casing does not matter, it will be * uppercased or lowercased depending on the context. * * @parameter * @required */ private String applicationName; /** * @parameter */ private List<Script> scripts; /** * @parameter */ private List<Dependency> alternativeClasspath; /** * @parameter */ private boolean includeProjectInClasspath = true; /** * @parameter */ private List<Parameter> defaultCliArgs; /** * @parameter */ private List<Parameter> defaultJvmArgs; /** * @parameter */ private List<Parameter> classPathPrefix; /** * @parameter */ private List<Parameter> beforeJavaHook; /** * @parameter default-value="${project.build.directory}" */ private File devOutputDirectory; /** * @parameter default-value="${project.build.directory}/dist-scripts" */ private File distOutputDirectory; private Map<Mode, File> outputDirectories = new EnumMap<Mode, File>(Mode.class); /** * @parameter default-value="${settings}" * @readonly * @required */ private Settings settings; /** * @parameter default-value="${project}" * @readonly * @required */ private MavenProject project; /** * Maven Artifact Factory component. * * @component */ protected ArtifactFactory artifactFactory; /** * Artifact Resolver component. * * @component */ protected ArtifactResolver resolver; /** * Remote repositories used for the project. * * @parameter default-value="${project.remoteArtifactRepositories}" * @required * @readonly */ protected List remoteRepositories; /** * Local Repository. * * @parameter default-value="${localRepository}" * @required * @readonly */ protected ArtifactRepository localRepository; private ArtifactRepositoryLayout m2layout = new DefaultRepositoryLayout(); enum Platform { UNIX("/", ":", "$", "", ""), WINDOWS("\\", ";", "%", "%", ".bat"); Platform(String fileSeparator, String pathSeparator, String envPrefix, String envSuffix, String extension) { this.fileSeparator = fileSeparator; this.pathSeparator = pathSeparator; this.envPrefix = envPrefix; this.envSuffix = envSuffix; this.extension = extension; } private String fileSeparator; private String pathSeparator; private String envPrefix; private String envSuffix; private String extension; } enum Mode { DEV("-dev"), DIST(""); Mode(String templateSuffix) { this.templateSuffix = templateSuffix; } private String templateSuffix; } @Override public void execute() throws MojoExecutionException, MojoFailureException { outputDirectories.put(Mode.DEV, devOutputDirectory); outputDirectories.put(Mode.DIST, distOutputDirectory); try { for (Script script: scripts) { generateScripts(script); } } catch (IOException ioe) { throw new MojoFailureException("Failed to generate script ", ioe); } } private void generateScripts(Script script) throws IOException, MojoExecutionException { devOutputDirectory.mkdirs(); distOutputDirectory.mkdirs(); for (Mode mode : Mode.values()) { for (Platform platform : Platform.values()) { String cp = generateClassPath(mode == Mode.DEV, platform); File scriptDir = outputDirectories.get(mode); scriptDir.mkdirs(); File scriptFile = new File(scriptDir, script.getBasename().concat(platform.extension)); generateScript(scriptFile, platform.name().toLowerCase() + mode.templateSuffix + ".template", script.getMainClass(), cp, platform, mode); if (new File("/bin/chmod").exists()) { Runtime.getRuntime().exec("/bin/chmod a+x " + scriptFile.getAbsolutePath()); } } } } private void generateScript(File outputFile, String template, String mainClass, String classPath, Platform platform, Mode mode) throws IOException { InputStream is = getClass().getResourceAsStream("/org/lilyproject/tools/mavenplugin/genscript/".concat(template)); String result = streamToString(is); String defaultCliArgs = getParameter(this.defaultCliArgs, platform, mode, ""); String defaultJvmArgs = getParameter(this.defaultJvmArgs, platform, mode, ""); String beforeJavaHook = getParameter(this.beforeJavaHook, platform, mode, ""); String classPathPrefix = getParameter(this.classPathPrefix, platform, mode, ""); String separator = "**"; result = result.replaceAll(Pattern.quote(separator.concat("CLASSPATH").concat(separator)), Matcher.quoteReplacement(classPath)). replaceAll(Pattern.quote(separator.concat("CLASSPATH_PREFIX").concat(separator)), Matcher.quoteReplacement(classPathPrefix)). replaceAll(Pattern.quote(separator.concat("MAINCLASS").concat(separator)), Matcher.quoteReplacement(mainClass)). replaceAll(Pattern.quote(separator.concat("DEFAULT_CLI_ARGS").concat(separator)), Matcher.quoteReplacement(defaultCliArgs)). replaceAll(Pattern.quote(separator.concat("DEFAULT_JVM_ARGS").concat(separator)), Matcher.quoteReplacement(defaultJvmArgs)). replaceAll(Pattern.quote(separator.concat("BEFORE_JAVA_HOOK").concat(separator)), Matcher.quoteReplacement(beforeJavaHook)). replaceAll(Pattern.quote(separator.concat("APPNAME").concat(separator)), Matcher.quoteReplacement(applicationName.toUpperCase())). replaceAll(Pattern.quote(separator.concat("APPNAME_LC").concat(separator)), Matcher.quoteReplacement(applicationName.toLowerCase())); BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); writer.write(result); writer.close(); } private String streamToString(InputStream in) throws IOException { StringBuilder out = new StringBuilder(); byte[] b = new byte[4096]; for (int n; (n = in.read(b)) != -1;) { out.append(new String(b, 0, n)); } return out.toString(); } private String generateClassPath(boolean isDevelopment, Platform platform) throws MojoExecutionException { // The classpath is generated such that there is no trailing path separator: otherwise this can cause // the current working directory to be added to the classpath unintentionally StringBuilder result = new StringBuilder(); ArtifactRepositoryLayout layout = m2layout; String basePath = isDevelopment ? settings.getLocalRepository() : platform.envPrefix.concat(applicationName.toUpperCase()).concat("_HOME").concat(platform.envSuffix).concat(platform.fileSeparator).concat("lib"); for (Artifact artifact: getClassPath()) { if (result.length() > 0) { result.append(platform.pathSeparator); } result.append(basePath).append(platform.fileSeparator).append(artifactPath(artifact, platform)); } if (includeProjectInClasspath) { if (result.length() > 0) { result.append(platform.pathSeparator); } // Disabled the isDevelopment treatment: otherwise in development, META-INF does not exist which // is used by Lily's CLI tools to read their version. Not sure if there is a good reason for this // behavior: it was probably inherited from Lily Runtime where it made sense to allow resources changes // to be picked up immediately. //if (isDevelopment) { // result.append(project.getBuild().getOutputDirectory()); //} else { result.append(basePath).append(platform.fileSeparator).append(artifactPath(project.getArtifact(), platform)); //} } return result.toString(); } private String artifactPath(Artifact artifact, Platform platform) { // pathOf always creates a path with slashes, irrespective of the current platform String artifactPath = pathOf(artifact); artifactPath = artifactPath.replaceAll("/", Matcher.quoteReplacement(platform.fileSeparator)); return artifactPath; } private List<Artifact> getClassPath() throws MojoExecutionException { if (alternativeClasspath != null && alternativeClasspath.size() > 0) { return getAlternateClassPath(); } else { return (List<Artifact>)project.getRuntimeArtifacts(); } } private List<Artifact> getAlternateClassPath() throws MojoExecutionException { List<Artifact> result = new ArrayList<Artifact>(); for (Dependency dependency : alternativeClasspath) { Artifact artifact = artifactFactory.createArtifactWithClassifier(dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), "jar", dependency.getClassifier()); try { resolver.resolve(artifact, remoteRepositories, localRepository); } catch (Exception e) { throw new MojoExecutionException("Error resolving artifact: " + artifact, e); } result.add(artifact); } return result; } private String getParameter(List<Parameter> parameters, Platform platform, Mode mode, String defaultValue) { if (parameters == null) { return defaultValue; } for (Parameter parameter : parameters) { if (parameter.platform.toUpperCase().equals(platform.name()) && parameter.mode.toUpperCase().equals(mode.name())) { return parameter.value; } } return defaultValue; } private static final char PATH_SEPARATOR = '/'; private static final char GROUP_SEPARATOR = '.'; private static final char ARTIFACT_SEPARATOR = '-'; /** * disclaimer: this method has been copied from the DefaultRepositoryLayout of the Maven * source tree and is Apache licensed. It was changed to use getBaseVersion() instead * of getVersion() on the artifact. */ private static String pathOf(Artifact artifact) { ArtifactHandler artifactHandler = artifact.getArtifactHandler(); StringBuilder path = new StringBuilder(); path.append(formatAsDirectory(artifact.getGroupId())).append(PATH_SEPARATOR); path.append(artifact.getArtifactId()).append(PATH_SEPARATOR); path.append(artifact.getBaseVersion()).append(PATH_SEPARATOR); // Lily change: Here we call getBaseVersion() instead of getVersion() because otherwise for snapshot artifacts // a timestamp suffix is included in the version (and our shell scripts & runtime classloader.xml's // refer to the plain snapshot version) path.append(artifact.getArtifactId()).append(ARTIFACT_SEPARATOR).append(artifact.getBaseVersion()); if (artifact.hasClassifier()) { path.append(ARTIFACT_SEPARATOR).append(artifact.getClassifier()); } if (artifactHandler.getExtension() != null && artifactHandler.getExtension().length() > 0) { path.append(GROUP_SEPARATOR).append(artifactHandler.getExtension()); } return path.toString(); } private static String formatAsDirectory( String directory ) { return directory.replace(GROUP_SEPARATOR, PATH_SEPARATOR); } }