InstallMojo.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 wcm.io
 * %%
 * 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.
 * #L%
 */
package io.wcm.maven.plugins.cq;

import java.io.File;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
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.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
import org.apache.maven.shared.invoker.MavenInvocationException;
import org.apache.maven.shared.utils.cli.CommandLineException;
import org.codehaus.plexus.configuration.PlexusConfiguration;
import org.codehaus.plexus.configuration.PlexusConfigurationException;
import org.codehaus.plexus.util.xml.Xpp3Dom;

/**
 * Executes install phase and installs an OSGi bundle jar to a running Sling instance
 * (combines goals "install" and "sling:install").
 */
@Mojo(name = "install",
    requiresDependencyResolution = ResolutionScope.COMPILE,
    requiresProject = true,
    threadSafe = true)
@Execute(phase = LifecyclePhase.INSTALL)
public class InstallMojo extends AbstractMojo {

  /**
   * Version of sling plugin
   */
  @Parameter(property = "sling.plugin.version", required = true, defaultValue = "2.4.2")
  private String slingPluginVersion;

  /**
   * The URL of osgi console
   */
  @Parameter(property = "sling.console.url", required = true, defaultValue = "http://localhost:8080/system/console")
  private String slingConsoleUrl;

  /**
   * The user name to authenticate at osgi console
   */
  @Parameter(property = "sling.console.user", required = true, defaultValue = "admin")
  private String slingConsoleUser;

  /**
   * The password to authenticate at osgi console
   */
  @Parameter(property = "sling.console.password", required = true, defaultValue = "admin")
  private String slingConsolePassword;


  @Parameter(defaultValue = "${project}", readonly = true)
  private MavenProject project;
  @Parameter(defaultValue = "${settings}", readonly = true)
  private Settings settings;
  @Parameter(defaultValue = "${session}", readonly = true)
  private MavenSession session;

  @Component(role = MavenPluginManager.class)
  private MavenPluginManager pluginManager;
  @Component(role = BuildPluginManager.class)
  private BuildPluginManager buildPluginManager;

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException {

    // detect goal to deploy current project based on packaging
    if (isBundleProject()) {
      executeSlingPluginDirectly();
    }
    else if (isContentPackageProject()) {
      getLog().info("Install content package to instance...");
      executeWithMavenInvoker("wcmio-content-package:install");
    }
    else {
      // no supported packaging - skip processing
      getLog().info("No bundle or content-package project, skip deployment.");
    }
  }

  private boolean isBundleProject() {
    // check for "bundle" packaging as used by maven-bundle-plugin
    String packaging = project.getPackaging();
    if (StringUtils.equals(packaging, "bundle")) {
      return true;
    }

    // check for active bnd-maven-plugin in current project
    return project.getBuildPlugins().stream()
        .anyMatch(this::isBndMavenPlugin);
  }

  private boolean isBndMavenPlugin(Plugin plugin) {
    return StringUtils.equals(plugin.getGroupId(), "biz.aQute.bnd")
        && StringUtils.equals(plugin.getArtifactId(), "bnd-maven-plugin");
  }

  private boolean isContentPackageProject() {
    String packaging = project.getPackaging();
    return StringUtils.equals(packaging, "content-package");
  }

  /**
   * Executes the sling-maven-plugin directly from the current project.
   */
  @SuppressWarnings({ "java:S1181", "checkstyle:IllegalCatch" }) // allow catch of throwable
  private void executeSlingPluginDirectly() throws MojoExecutionException {

    Plugin plugin = new Plugin();
    plugin.setGroupId("org.apache.sling");
    plugin.setArtifactId("sling-maven-plugin");
    plugin.setVersion(this.slingPluginVersion);

    try {
      PluginDescriptor pluginDescriptor = pluginManager.getPluginDescriptor(plugin,
          project.getRemotePluginRepositories(), session.getRepositorySession());
      MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo("install-file");
      MojoExecution mojoExecution = new MojoExecution(pluginDescriptor.getMojo("install-file"));

      Xpp3Dom config = convertConfiguration(mojoDescriptor.getMojoConfiguration());
      config.getChild("slingUrl").setValue(this.slingConsoleUrl);
      config.getChild("user").setValue(this.slingConsoleUser);
      config.getChild("password").setValue(this.slingConsolePassword);
      config.getChild("mountByFS").setValue("false");
      mojoExecution.setConfiguration(config);

      buildPluginManager.executeMojo(session, mojoExecution);
    }
    catch (Throwable ex) {
      throw new MojoExecutionException("Faild to execute plugin: " + plugin, ex);
    }

  }

  private Xpp3Dom convertConfiguration(PlexusConfiguration plexusConfig) throws PlexusConfigurationException {
    Xpp3Dom config = new Xpp3Dom(plexusConfig.getName());
    config.setValue(plexusConfig.getValue());
    for (String attribute : plexusConfig.getAttributeNames()) {
      config.setAttribute(attribute, plexusConfig.getAttribute(attribute));
    }
    for (PlexusConfiguration child : plexusConfig.getChildren()) {
      config.addChild(convertConfiguration(child));
    }
    return config;
  }

  /**
   * Invoke maven for the current project with all it's setting and the given goal.
   * @param goal Goal
   * @throws MojoExecutionException Mojo execution exception
   */
  private void executeWithMavenInvoker(String goal) throws MojoExecutionException {
    InvocationRequest invocationRequest = new DefaultInvocationRequest();
    invocationRequest.setPomFile(project.getFile());
    invocationRequest.addArg(goal);
    invocationRequest.setBatchMode(true);

    // take over all systems properties and profile settings from current maven execution
    invocationRequest.setShellEnvironmentInherited(true);
    invocationRequest.setLocalRepositoryDirectory(new File(settings.getLocalRepository()));
    invocationRequest.setProperties(session.getUserProperties());
    invocationRequest.setProfiles(settings.getActiveProfiles());

    setupInvokerLogger(invocationRequest);
    Invoker invoker = new DefaultInvoker();

    try {
      InvocationResult invocationResult = invoker.execute(invocationRequest);
      if (invocationResult.getExitCode() != 0) {
        String msg = "Execution of cq:install failed, see above.";
        if (invocationResult.getExecutionException() != null) {
          msg = invocationResult.getExecutionException().getMessage();
        }
        throw new CommandLineException(msg);
      }
    }
    catch (MavenInvocationException | CommandLineException ex) {
      throw new MojoExecutionException("Failed to execute goals", ex);
    }
  }

  /**
   * Mirror maven execution log output to current maven logger.
   * @param request Invocation request
   */
  private void setupInvokerLogger(InvocationRequest request) {
    Log log = getLog();
    request.setOutputHandler(line -> {
      if (StringUtils.startsWith(line, "[ERROR] ")) {
        log.error(StringUtils.substringAfter(line, "[ERROR] "));
      }
      else if (StringUtils.startsWith(line, "[WARNING] ")) {
        log.warn(StringUtils.substringAfter(line, "[WARNING] "));
      }
      else if (StringUtils.startsWith(line, "[INFO] ")) {
        log.info(StringUtils.substringAfter(line, "[INFO] "));
      }
      else if (StringUtils.startsWith(line, "[DEBUG] ")) {
        log.debug(StringUtils.substringAfter(line, "[DEBUG] "));
      }
      else {
        log.info(line);
      }
    });
  }

}