| /* |
| * 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. |
| */ |
| package org.apache.sling.maven.kickstart.run; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.io.IOUtils; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.artifact.DefaultArtifact; |
| import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; |
| import org.apache.maven.artifact.resolver.ArtifactNotFoundException; |
| import org.apache.maven.artifact.resolver.ArtifactResolutionException; |
| import org.apache.maven.artifact.resolver.ArtifactResolver; |
| import org.apache.maven.artifact.versioning.VersionRange; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.model.Dependency; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.sling.maven.kickstart.BuildConstants; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| |
| /** |
| * Start one or multiple Kickstart instance(s). |
| */ |
| @Mojo( |
| name = "start", |
| defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, |
| threadSafe = true |
| ) |
| public class StartMojo extends AbstractStartStopMojo { |
| |
| private static Dependency defaultKickstart = new Dependency(); |
| |
| static { |
| defaultKickstart.setGroupId("org.apache.sling"); |
| defaultKickstart.setArtifactId("org.apache.sling.kickstart"); |
| defaultKickstart.setVersion("0.0.6"); |
| } |
| |
| /** |
| * Overwrites debug parameter of all server configurations (if set). |
| * Attaches a debugger to the forked JVM. If set to {@code "true"}, the process will allow a debugger to connect on port 8000. |
| * If set to some other string, that string will be appended to the server's {@code vmOpts}, allowing you to configure arbitrary debugging options. |
| */ |
| @Parameter(property = "kickstart.debug") |
| protected String debug; |
| |
| /** |
| * Ready timeout in seconds. If the kickstart has not been started in this |
| * time, it's assumed that the startup failed. |
| */ |
| @Parameter(property = "kickstart.ready.timeout", defaultValue = "600") |
| private int kickstartReadyTimeOutSec; |
| |
| /** |
| * The kickstart jar. This option has precedence over "kickstartDependency". |
| */ |
| @Parameter(property = "kickstart.jar") |
| private File kickstartJar; |
| |
| /** |
| * The kickstart jar as a dependency. This is only used if "kickstartJar" is not |
| * specified. |
| */ |
| @Parameter |
| private Dependency kickstartDependency = defaultKickstart; |
| |
| /** |
| * Clean the working directory before start. |
| */ |
| @Parameter(property = "kickstart.clean.workdir", defaultValue = "false") |
| private boolean cleanWorkingDirectory; |
| |
| /** |
| * Keep the kickstart running. |
| * @deprecated Use {@link AbstractStartStopMojo# blockUntilKeyIsPressed} instead. |
| */ |
| @Deprecated |
| @Parameter(property = "kickstart.keep.running", defaultValue = "false") |
| private boolean keepKickstartRunning; |
| |
| /** |
| * Set the execution of kickstart instances to be run in parallel (threads) |
| */ |
| @Parameter(property = "kickstart.parallelExecution", defaultValue = "true") |
| private boolean parallelExecution; |
| |
| /** |
| * The Maven project. |
| */ |
| @Parameter(property = "project", readonly = true, required = true) |
| private MavenProject project; |
| |
| /** |
| * The Maven session. |
| */ |
| @Parameter(property = "session", readonly = true, required = true) |
| private MavenSession mavenSession; |
| |
| @Component |
| private ArtifactHandlerManager artifactHandlerManager; |
| |
| /** |
| * Used to look up Artifacts in the remote repository. |
| * |
| */ |
| @Component |
| private ArtifactResolver resolver; |
| |
| /** |
| * Get a resolved Artifact from the coordinates provided |
| * |
| * @return the artifact, which has been resolved. |
| * @throws MojoExecutionException |
| */ |
| private Artifact getArtifact(final Dependency d) |
| throws MojoExecutionException { |
| final Artifact prjArtifact = new DefaultArtifact(d.getGroupId(), |
| d.getArtifactId(), |
| VersionRange.createFromVersion(d.getVersion()), |
| d.getScope(), |
| d.getType(), |
| d.getClassifier(), |
| this.artifactHandlerManager.getArtifactHandler(d.getType())); |
| try { |
| this.resolver.resolve(prjArtifact, this.project.getRemoteArtifactRepositories(), this.mavenSession.getLocalRepository()); |
| } catch (final ArtifactResolutionException e) { |
| throw new MojoExecutionException("Unable to get artifact for " + d, e); |
| } catch (ArtifactNotFoundException e) { |
| throw new MojoExecutionException("Unable to get artifact for " + d, e); |
| } |
| |
| return prjArtifact; |
| } |
| |
| |
| @Override |
| protected void doExecute() throws MojoExecutionException, MojoFailureException { |
| // delete properties |
| if ( systemPropertiesFile != null && systemPropertiesFile.exists() ) { |
| FileUtils.deleteQuietly(this.systemPropertiesFile); |
| } |
| |
| // get configurations |
| final Collection<ServerConfiguration> configurations = getKickstartConfigurations(); |
| |
| // create the common environment |
| getLog().info("Keep kickstart Running: " + this.keepKickstartRunning); |
| final KickstartEnvironment env = new KickstartEnvironment(this.findKickstartJar(), |
| this.cleanWorkingDirectory, |
| !this.keepKickstartRunning, |
| this.kickstartReadyTimeOutSec, |
| this.debug); |
| |
| // create callables |
| final Collection<LauncherCallable> tasks = new LinkedList<LauncherCallable>(); |
| |
| for (final ServerConfiguration kickstartConfiguration : configurations) { |
| validateConfiguration(kickstartConfiguration); |
| |
| tasks.add(createTask(kickstartConfiguration, env)); |
| } |
| |
| // create the kickstart runner properties |
| this.createKickstartRunnerProperties(configurations); |
| |
| if (parallelExecution) { |
| // ExecutorService for starting kickstart instances in parallel |
| final ExecutorService executor = Executors.newCachedThreadPool(); |
| try { |
| final List<Future<ProcessDescription>> resultsCollector = executor.invokeAll(tasks); |
| for (final Future<ProcessDescription> future : resultsCollector) { |
| try { |
| if (null == future.get()) { |
| throw new MojoExecutionException("Cannot start all the instances"); |
| } |
| } catch (final ExecutionException e) { |
| throw new MojoExecutionException(e.getLocalizedMessage(), e); |
| } |
| } |
| } catch ( final InterruptedException e) { |
| throw new MojoExecutionException(e.getLocalizedMessage(), e); |
| } |
| } else { |
| for (final LauncherCallable task : tasks) { |
| try { |
| if (null == task.call()) { |
| throw new MojoExecutionException("Cannot start all the instances"); |
| } |
| } catch (final Exception e) { |
| throw new MojoExecutionException(e.getLocalizedMessage(), e); |
| } |
| } |
| } |
| if (this.keepKickstartRunning) { |
| getLog().info("Press CTRL-C to stop kickstart instance(s)..."); |
| while ( true && this.isRunning(tasks)) { |
| try { |
| Thread.sleep(5000); |
| } catch (final InterruptedException ie) { |
| break; |
| } |
| } |
| } |
| blockIfNecessary(); |
| } |
| |
| /** |
| * Are all kickstarts still running? |
| */ |
| private boolean isRunning(final Collection<LauncherCallable> tasks) { |
| for(final LauncherCallable task : tasks) { |
| if ( !task.isRunning() ) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void createKickstartRunnerProperties(final Collection<ServerConfiguration> configurations) |
| throws MojoExecutionException { |
| // create properties |
| OutputStream writer = null; |
| final Properties props = new Properties(); |
| try { |
| writer = new FileOutputStream(this.systemPropertiesFile); |
| |
| // disable sling startup check |
| props.put("kickstart.skip.startupcheck", "true"); |
| |
| // write out all instances |
| int index = 0; |
| for (final ServerConfiguration kickstartConfiguration : configurations) { |
| index++; |
| props.put("kickstart.instance.id." + String.valueOf(index), kickstartConfiguration.getId()); |
| String runMode = kickstartConfiguration.getRunmode(); |
| if ( runMode == null ) { |
| runMode = ""; |
| } |
| props.put("kickstart.instance.runmode." + String.valueOf(index), runMode); |
| props.put("kickstart.instance.server." + String.valueOf(index), kickstartConfiguration.getServer()); |
| props.put("kickstart.instance.port." + String.valueOf(index), kickstartConfiguration.getPort()); |
| props.put("kickstart.instance.contextPath." + String.valueOf(index), kickstartConfiguration.getContextPath()); |
| final String url = createServerUrl(kickstartConfiguration); |
| props.put("kickstart.instance.url." + String.valueOf(index), url); |
| } |
| props.put("kickstart.instances", String.valueOf(index)); |
| |
| props.store(writer, null); |
| } catch (final IOException e) { |
| throw new MojoExecutionException(e.getLocalizedMessage(), e); |
| } finally { |
| IOUtils.closeQuietly(writer); |
| } |
| } |
| |
| private static String createServerUrl(final ServerConfiguration qc) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("http://"); |
| sb.append(qc.getServer()); |
| if ( !qc.getPort().equals("80") ) { |
| sb.append(':'); |
| sb.append(qc.getPort()); |
| } |
| final String contextPath = qc.getContextPath(); |
| if ( contextPath != null && contextPath.trim().length() > 0 && !contextPath.equals("/") ) { |
| if ( !contextPath.startsWith("/") ) { |
| sb.append('/'); |
| } |
| if ( contextPath.endsWith("/") ) { |
| sb.append(contextPath, 0, contextPath.length()-1); |
| } else { |
| sb.append(contextPath); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * @param kickstartConfiguration |
| */ |
| private LauncherCallable createTask(final ServerConfiguration kickstartConfiguration, |
| final KickstartEnvironment env) |
| throws MojoExecutionException, MojoFailureException { |
| final String id = kickstartConfiguration.getId(); |
| getLog().debug(new StringBuilder("Starting ").append(id). |
| append(" with runmode ").append(kickstartConfiguration.getRunmode()). |
| append(" on port ").append(kickstartConfiguration.getPort()). |
| append(" in folder ").append(kickstartConfiguration.getFolder().getAbsolutePath()).toString()); |
| |
| // create task |
| return new LauncherCallable(this.getLog(), kickstartConfiguration, env); |
| |
| } |
| |
| /** |
| * Validate a configuration |
| * @param kickstartConfiguration The kickstart configuration |
| * @throws MojoExecutionException |
| */ |
| private void validateConfiguration(final ServerConfiguration kickstartConfiguration) |
| throws MojoExecutionException { |
| if ( kickstartConfiguration.getPort() == null ) { |
| kickstartConfiguration.setPort(String.valueOf(PortHelper.getNextAvailablePort())); |
| } |
| |
| if ( kickstartConfiguration.getControlPort() == null ) { |
| kickstartConfiguration.setControlPort(String.valueOf(PortHelper.getNextAvailablePort())); |
| } |
| |
| // set the id of the kickstart |
| if ( kickstartConfiguration.getId() == null || kickstartConfiguration.getId().trim().length() == 0 ) { |
| String runMode = kickstartConfiguration.getRunmode(); |
| if ( runMode == null ) { |
| runMode = "_"; |
| } |
| final String id = new StringBuilder(runMode.replace(',', '_')).append('-').append(kickstartConfiguration.getPort()).toString(); |
| kickstartConfiguration.setId(id); |
| } |
| |
| // populate folder if not set |
| if (kickstartConfiguration.getFolder() == null) { |
| final File folder = new File(new StringBuilder(this.project.getBuild().getDirectory()).append('/').append(kickstartConfiguration.getId()).toString()); |
| kickstartConfiguration.setFolder(folder); |
| } |
| // context path should not be null |
| if ( kickstartConfiguration.getContextPath() == null ) { |
| kickstartConfiguration.setContextPath(""); |
| } |
| |
| if ( kickstartConfiguration.getInstances() < 0 ) { |
| kickstartConfiguration.setInstances(1); |
| } |
| } |
| |
| /** |
| * Finds the kickstart.jar artifact of the project being built. |
| * |
| * @return the kickstart.jar artifact |
| * @throws MojoFailureException if a kickstart.jar artifact was not found |
| */ |
| private File findKickstartJar() throws MojoFailureException, MojoExecutionException { |
| |
| // If a kickstart JAR is specified, use it |
| if (kickstartJar != null) { |
| getLog().info("Using kickstart jar from '" + kickstartJar + "' given as configuration parameter!"); |
| return kickstartJar; |
| } |
| |
| // If a kickstart dependency is configured, resolve it |
| if (kickstartDependency != null) { |
| getLog().info("Using kickstart dependency '" + kickstartDependency + "' given as configuration parameter!"); |
| return getArtifact(kickstartDependency).getFile(); |
| } |
| |
| // If the current project is a slingstart project, use its JAR artifact |
| if (this.project.getPackaging().equals(BuildConstants.PACKAGING_SLINGQUICKSTART)) { |
| File jarFile = project.getArtifact().getFile(); |
| if (jarFile != null && jarFile.exists()) { |
| getLog().info("Using kickstart jar being generated as this project's primary artifact: '" + jarFile + "'!"); |
| return jarFile; |
| } |
| else { |
| jarFile = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar"); |
| if (jarFile.exists()) { |
| getLog().info("Using kickstart jar being generated as this project's primary artifact: '" + jarFile + "'!"); |
| return jarFile; |
| } |
| } |
| } |
| |
| // In case there was a provisioning model found but this is not a slingstart project, the JAR might be attached already through goal "package" |
| for (Artifact attachedArtifact : project.getAttachedArtifacts()) { |
| // find the attached artifact with classifier "standalone" |
| if (BuildConstants.TYPE_JAR.equals(attachedArtifact.getType()) && BuildConstants.CLASSIFIER_APP.equals(attachedArtifact.getClassifier())) { |
| getLog().info("Using kickstart jar being attached as additional project artifact: '" + attachedArtifact.getFile() + "'!"); |
| return attachedArtifact.getFile(); |
| } |
| } |
| |
| // also check for jars in known target folders (in case the jar has been created in this project's target folder but not attached to the Maven project) |
| //AS TODO: Is Package Mojo still needed / supported ? |
| // File localJarFile = PackageMojo.getNonPrimaryBuildFile(project, ".jar"); |
| // if (localJarFile.exists()) { |
| // getLog().info("Using local kickstart jar being created in predefined directory: '" + localJarFile + "'!"); |
| // return localJarFile; |
| // } |
| |
| // Last chance: use the first declared dependency with type "slingstart" |
| final Set<Artifact> dependencies = this.project.getDependencyArtifacts(); |
| for (final Artifact dep : dependencies) { |
| if (BuildConstants.PACKAGING_SLINGQUICKSTART.equals(dep.getType())) { |
| final Dependency d = new Dependency(); |
| d.setGroupId(dep.getGroupId()); |
| d.setArtifactId(dep.getArtifactId()); |
| d.setVersion(dep.getVersion()); |
| d.setScope(Artifact.SCOPE_RUNTIME); |
| d.setType(BuildConstants.TYPE_JAR); |
| getLog().info("Using kickstart jar from first dependency of type 'slingstart': '"+ d +"'!"); |
| return getArtifact(d).getFile(); |
| } |
| } |
| |
| // kickstart has not been found, throw an exception |
| throw new MojoFailureException("Could not find the kickstart jar. " + |
| "Either specify the 'kickstartJar' configuration or use this inside a slingstart project."); |
| } |
| |
| /** |
| * Get all configurations |
| * @return Collection of configurations. |
| */ |
| private Collection<ServerConfiguration> getKickstartConfigurations() { |
| final List<ServerConfiguration> configs = new ArrayList<ServerConfiguration>(); |
| if ( this.servers != null && !this.servers.isEmpty() ) { |
| for(final ServerConfiguration config : this.servers) { |
| // if instances is set to 0, no instance is added |
| if ( config.getInstances() != 0 ) { |
| configs.add(config); |
| for(int i=2; i<=config.getInstances();i++) { |
| final ServerConfiguration replicaConfig = config.copy(); |
| replicaConfig.setPort(null); |
| final File folder = replicaConfig.getFolder(); |
| if ( folder != null ) { |
| replicaConfig.setFolder(new File(folder.getParentFile(), folder.getName() + '-' + String.valueOf(i))); |
| } |
| configs.add(replicaConfig); |
| } |
| config.setInstances(1); |
| } |
| } |
| } else { |
| // use single default instance |
| configs.add(new ServerConfiguration()); |
| } |
| return configs; |
| } |
| } |