PreDeployMojo.java

package com.sap.prd.mobile.ios.mios;

/*
 * #%L
 * Xcode Maven Plugin
 * %%
 * Copyright (C) 2012 SAP AG
 * %%
 * 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%
 */

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
import org.sonatype.aether.transfer.TransferCancelledException;
import org.sonatype.aether.transfer.TransferEvent;
import org.sonatype.aether.transfer.TransferListener;

/**
 * Registers a TransferListener on Aether that reacts on successful deployments. For each ipa file
 * deployed successfully a pointer file is written. This pointer file redirects to XXX
 * 
 * 
 * @goal pre-deploy
 * 
 */
public class PreDeployMojo extends AbstractDeployMojo
{

  /**
   * The folder used for hudson archiving
   * 
   * @parameter expression="${archive.dir}" default-value="${project.build.directory}"
   */
  private File archiveFolder;

  private final static String HTML_TEMPLATE = "<html><head><meta http-equiv=\"refresh\" content=\"0; URL=$LOCATION\">" +
        "<body>You will be redirected within the next few seconds.<br />" +
        "In case this does not work click <a href=\"$LOCATION\">here</a></body></html>";

  /**
   * Returns the unique archive folder for this specific project (containing folders with groupId
   * and artifactId)
   * 
   * @param rootArchiveFolder
   * @param project
   * @return
   */
  static File getProjectArchiveFolder(File rootArchiveFolder, MavenProject project)
  {
    return new File(new File(new File(rootArchiveFolder, "artifacts"), project.getGroupId()), project.getArtifactId());
  }

  @Override
  public void execute() throws MojoExecutionException, MojoFailureException
  {
    try {
      archiveFolder = archiveFolder.getCanonicalFile();
    }
    catch (IOException ex) {
      throw new MojoExecutionException(ex.getMessage(), ex);
    }

    createArchiveFolder(archiveFolder);

    getLog().info("Using archive directory: '" + archiveFolder + "'.");

    try {

      //
      // [Q] Why do we use reflection here? What about a simple downcast to DefaultRepositorySystemSession?
      // [A] We cannot downcast since the downcast fails. The DefaultRepositorySystemSession that is available
      //     here is loaded by another class loader than the DefaultRepositorySystemSession used by the actual class.
      //     Hence we are in different runtime packages and the downcast is not possible.

      final TransferListener transferListener = getTransferListener();

      final TransferListener prepareIpaPointerFileTransferListener = new PrepareIpaPointerFileTransferListener(
            transferListener);

      setTransferListener(prepareIpaPointerFileTransferListener);

      getLog().info(
            "The previously used transfer listener was: '" + toString(transferListener) + "'.");
    }
    catch (RuntimeException e) {
      throw e;
    }
    catch (Exception e) {
      throw new MojoExecutionException(
            "Cannot set transfer listener for creating artifact redirect HTML files: " + e.getMessage(), e);
    }
  }

  private static void createArchiveFolder(File archiveFolder) throws MojoExecutionException
  {
    if (archiveFolder.isFile())
      throw new MojoExecutionException("Archive folder '" + archiveFolder + "' is a file rather than a directory.");

    if (!archiveFolder.exists() && !archiveFolder.mkdirs())
      throw new MojoExecutionException("Cannot create archive folder '" + archiveFolder + "'.");
  }

  class PrepareIpaPointerFileTransferListener implements TransferListener
  {
    private TransferListener forward;

    PrepareIpaPointerFileTransferListener(TransferListener forward)
    {
      this.forward = forward;
    }

    TransferListener getForward()
    {
      return this.forward;
    }

    @Override
    public void transferSucceeded(TransferEvent event)
    {
      if (forward != null)
        forward.transferSucceeded(event);

      if (event.getRequestType().equals(TransferEvent.RequestType.GET)) return;

      if (event.getResource().getResourceName().endsWith("maven-metadata.xml")) {

        getLog().debug("No redirect html file will be created for '" + event.getResource().getResourceName() + "'.");
        return;
      }

      try {

        final String url = event.getResource().getRepositoryUrl() + event.getResource().getResourceName();
        final String html = HTML_TEMPLATE.replaceAll("\\$LOCATION", url);

        final File projectArchiveFolder = getProjectArchiveFolder(archiveFolder, project);
        final File redirect = new File(projectArchiveFolder, getArtifactRedirectHtmlFileName(url));

        writeArtifactRedirectHtmlFile(redirect, url, html);

        getLog().info("Redirect file '" + redirect + "' written for '" + url + "'.");

      }
      catch (RuntimeException ex) {

        getLog()
          .error("Could not create artifact redirect HTML file for '" + event.getResource().getResourceName(), ex);
      }
    }

    @Override
    public void transferStarted(TransferEvent event) throws TransferCancelledException
    {
      if (forward != null)
        forward.transferStarted(event);
    }

    @Override
    public void transferProgressed(TransferEvent event) throws TransferCancelledException
    {
      if (forward != null)
        forward.transferProgressed(event);
    }

    @Override
    public void transferInitiated(TransferEvent event) throws TransferCancelledException
    {
      if (forward != null)
        forward.transferInitiated(event);
    }

    @Override
    public void transferFailed(TransferEvent event)
    {
      if (forward != null)
        forward.transferFailed(event);
    }

    @Override
    public void transferCorrupted(TransferEvent event) throws TransferCancelledException
    {
      if (forward != null)
        forward.transferCorrupted(event);
    }

    private void writeArtifactRedirectHtmlFile(final File redirect, final String url, final String html)
    {
      FileOutputStream fos = null;

      try {

        if (!redirect.getParentFile().exists() && !redirect.getParentFile().mkdirs())
          throw new IOException("Cannot create folder '" + redirect.getParentFile() + "'.");

        fos = new FileOutputStream(redirect);

        IOUtils.write(html, fos, "UTF-8");
      }
      catch (IOException ex) {
        throw new RuntimeException(ex);
      }
      finally {
        IOUtils.closeQuietly(fos);
      }
    }

    private String getArtifactRedirectHtmlFileName(final String url)
    {
      //E.g. MyApp-1.0.0-20120821.132955-1-Release-iphoneos-ota.htm 
      String artifactFileName = url.substring(url.lastIndexOf("/") + 1);
      return getRedirectHtmlFilename(artifactFileName, project.getArtifactId());
    }

  }

  static String getRedirectHtmlFilename(String artifactFileName, String artifactId)
  {
    String search = "-";
    int nthElement = 0;
    if (artifactFileName.endsWith(XCodeOtaHtmlGeneratorMojo.OTA_CLASSIFIER_APPENDIX + "."
          + XCodeOtaHtmlGeneratorMojo.OTA_HTML_FILE_APPENDIX)) {
      nthElement = 3;
    }
    else if (artifactFileName.endsWith("-AppStoreMetaData.zip")) {
      nthElement = 1;
    }
    else if (artifactFileName.endsWith("-app.dSYM.zip")) {
      nthElement = 3;
    }
    else if (artifactFileName.endsWith("-app.zip")) {
      nthElement = 3;
    }
    else if (artifactFileName.endsWith(".ipa")) {
      nthElement = 2;
    }
    else if (artifactFileName.endsWith("versions.xml")) {
      nthElement = 1;
    }
    else if (artifactFileName.endsWith(".pom")) {
      nthElement = 1;
      search = ".";
    }
    else if (artifactFileName.endsWith("-fat-binary.a")) {
      nthElement = 3;
    }
    else if (artifactFileName.endsWith(".a")) {
      nthElement = 2;
    }
    else if (artifactFileName.endsWith(".headers.tar")) {
      nthElement = 2;
    }
    else if (artifactFileName.endsWith(".tar")) {
      nthElement = 1;
      search = ".";
    }

    int idx = getNthIndexFromBack(artifactFileName, search, nthElement);
    if (idx >= 0) {
      String name = artifactId + artifactFileName.substring(idx);
      if (!name.endsWith(".htm")) {
        name = name + ".htm";
      }
      return name;
    }
    return artifactId + "-" + artifactFileName + ".htm";
  }

  static int getNthIndexFromBack(String string, String searchString, int countFromBack)
  {
    if (countFromBack <= 0) return -1;
    int idx = string.length();
    for (int i = 0; i < countFromBack; i++) {
      idx = string.substring(0, idx).lastIndexOf(searchString);
      if (idx <= 0) return idx;
    }
    return idx;
  }

}