/* Copyright 2009 Kindleit.net Software Development
*
* 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 net.kindleit.gae;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.repository.ArtifactRepository;
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.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Settings;
import org.codehaus.plexus.archiver.ArchiverException;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import sun.misc.JarFilter;
/** Base MOJO class for working with the Google App Engine SDK.
*
* @author rhansen@kindleit.net
* @requiresProject true
*/
public abstract class EngineGoalBase extends AbstractMojo {
public static final String PLUGIN_VERSION="0.9";
protected static final String[] ARG_TYPE = new String[0];
protected static final String APPENGINE_ARTIFACTID = "appengine-java-sdk";
protected static final String APPENGINE_GROUPID = "com.google.appengine";
protected static final String APPCFG_CLASS =
"com.google.appengine.tools.admin.AppCfg";
protected static final String KICKSTART_CLASS =
"com.google.appengine.tools.admin.KickStart";
protected static final String SDK_LIBS_NOTFOUND =
"AppEngine tools SDK could not be found";
protected static final String SDK_APPCFG_NOTFOUND =
"AppCfg Class not found at: " + APPCFG_CLASS;
protected static final String SDK_KICKSTART_NOTFOUND =
"KickStart Class not found at: " + KICKSTART_CLASS;
private static URLClassLoader classLoader;
/** The Maven project reference.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
protected MavenProject project;
/** The Maven settings reference.
*
* @parameter expression="${settings}"
* @required
* @readonly
*/
protected Settings settings;
/**
* Location of the local repository.
*
* @parameter expression="${localRepository}"
* @readonly
* @required
*/
protected ArtifactRepository local;
/**
* List of Remote Repositories used by the resolver
*
* @parameter expression="${project.remoteArtifactRepositories}"
* @readonly
* @required
*/
protected List<?> remoteRepos;
/**
* Used to look up Artifacts in the remote repository.
*
* @component
*/
protected org.apache.maven.artifact.factory.ArtifactFactory factory;
/**
* Used to look up Artifacts in the remote repository.
*
* @component
*/
protected ArtifactResolver resolver;
/**
* To look up Archiver/UnArchiver implementations
*
* @component
*/
protected ArchiverManager archiverManager;
/** Overrides where the Project War Directory is located.
*
* @parameter expression="${project.build.directory}/${project.build.finalName}"
* @required
*/
protected String appDir;
/** Specifies where the Google App Engine SDK is located.
*
* @parameter expression="${appengine.sdk.root}" default-value="${project.build.directory}/appEngine"
* @required
*/
protected String sdkDir;
/** Version of the Google App Engine to use.
*
* @parameter expression="${gae.version}" default-value="1.2.6"
* @required
*/
protected String gaeVersion;
/** Split large jar files (> 10M) into smaller fragments.
*
* @parameter expression="${gae.deps.split}" default-value="false"
*/
protected boolean splitJars;
/** The username to use. Will prompt if omitted.
*
* @parameter expression="${gae.email}"
*/
protected String emailAccount;
/** The server to connect to.
*
* @parameter expression="${gae.server}"
*/
protected String uploadServer;
/** Overrides the Host header sent with all RPCs.
*
* @parameter expression="${gae.host}"
*/
protected String hostString;
/** Do not delete temporary directory used in uploading.
*
* @parameter expression="${gae.keepTemps}" default-value="false"
*/
protected boolean keepTempUploadDir;
/** Always read the login password from stdin.
*
* @parameter expression="${gae.passin}" default-value="false"
*/
protected boolean passIn;
/**
* @return Returns the project.
*/
public MavenProject getProject ()
{
return project;
}
/** Passes command to the Google App Engine AppCfg runner.
*
* @param command command to run through AppCfg
* @param commandArguments arguments to the AppCfg command.
* @throws MojoExecutionException
*/
protected final void runAppCfg(final String command,
final String ... commandArguments) throws MojoExecutionException {
final List<String> args = new ArrayList<String>();
args.addAll(getAppCfgArgs());
args.add(command);
args.addAll(Arrays.asList(commandArguments));
try {
getEngineClass(APPCFG_CLASS, SDK_APPCFG_NOTFOUND)
.getMethod("main", String[].class)
.invoke(null, (Object) args.toArray(ARG_TYPE));
} catch (final Exception e) {
throw new MojoExecutionException("Unable to execute AppCfg command: " + command, e);
}
}
/** Passes command to the Google App Engine KickStart runner.
*
* @param startClass command to run through KickStart
* @param commandArguments arguments to the KickStart command.
* @throws MojoExecutionException
*/
protected final void runKickStart(final String startClass,
final String ... commandArguments) throws MojoExecutionException {
final List<String> args = new ArrayList<String>();
args.add(startClass);
args.addAll(getCommonArgs());
args.addAll(Arrays.asList(commandArguments));
try {
getEngineClass(KICKSTART_CLASS, SDK_KICKSTART_NOTFOUND)
.getMethod("main", String[].class)
.invoke(null, (Object) args.toArray(ARG_TYPE));
} catch (final Exception e) {
throw new MojoExecutionException("Unable to execute Kickstart class: " + startClass, e);
}
}
/** Generate all common Google AppEngine Task Parameters for use in all the
* goals.
*
* @return List of arguments to add.
*/
protected final List<String> getAppCfgArgs () {
final List<String> args = getCommonArgs();
addBooleanOption(args, "--disable_prompt", !settings.getInteractiveMode());
addStringOption(args, "--email=", emailAccount);
addStringOption(args, "--host=", hostString);
addBooleanOption(args, "--passin", passIn);
addBooleanOption(args, "--enable_jar_splitting", splitJars);
addBooleanOption(args, "--retain_upload_dir", keepTempUploadDir);
return args;
}
protected final List<String> getCommonArgs() {
final List<String> args = new ArrayList<String>(8);
args.add("--sdk_root=" + sdkDir);
addStringOption(args, "--server=", uploadServer);
return args;
}
private final void addBooleanOption(final List<String> args, final String key,
final boolean var) {
if (var) {
args.add(key);
}
}
private final void addStringOption(final List<String> args, final String key,
final String var) {
if (var != null && var.length() > 0) {
args.add(key + var);
}
}
protected Class<?> getEngineClass(final String clsName,
final String clsErrorMsg) throws MojoExecutionException {
try {
return getClassLoader().loadClass(clsName);
} catch (final ClassNotFoundException e) {
throw new MojoExecutionException(clsErrorMsg, e);
}
}
private ClassLoader getClassLoader() throws MojoExecutionException {
synchronized (EngineGoalBase.class) {
if (classLoader != null) {
return classLoader;
}
}
synchronized (EngineGoalBase.class) {
try {
final List<URL> urls = new ArrayList<URL>();
for(final File jar : getSdkLibDir().listFiles(new JarFilter())) {
urls.add(jar.toURI().toURL());
}
classLoader = new URLClassLoader(urls.toArray(new URL[0]));
} catch (final MalformedURLException e) {
throw new MojoExecutionException(SDK_LIBS_NOTFOUND, e);
}
return classLoader;
}
}
private File getSdkLibDir() throws MojoExecutionException {
final File sdkLibDir = new File(sdkDir);
if (!sdkLibDir.exists()) {
unpackSDK(sdkLibDir);
}
return sdkLibDir;
}
/**
* This method gets the Artifact object and calls DependencyUtil.unpackFile.
*
* @param artifactItem
* containing the information about the Artifact to unpack.
*
* @throws MojoExecutionException
* with a message if an error occurs.
*
* @see #getArtifact
* @see DependencyUtil#unpackFile(Artifact, File, File, ArchiverManager,
* Log)
*/
private void unpackSDK (final File sdkLibDir) throws MojoExecutionException {
Artifact sdkArtifact;
try {
sdkArtifact =
factory.createArtifact(APPENGINE_GROUPID, APPENGINE_ARTIFACTID,
gaeVersion, "compile", "zip");
resolver.resolveAlways(sdkArtifact, remoteRepos, local);
} catch ( final ArtifactResolutionException e ) {
throw new MojoExecutionException( "Unable to resolve artifact.", e );
} catch ( final ArtifactNotFoundException e ) {
throw new MojoExecutionException( "Unable to find artifact.", e );
}
unpack(sdkArtifact.getFile(), sdkLibDir);
}
/** Unpacks the archive file.
*
* @param file File to be unpacked.
* @param location Location where to put the unpacked files.
*/
private void unpack (final File file, final File location)
throws MojoExecutionException {
try {
getLog().info("Unpacking " + file.getPath()
+ " to\n " + location.getPath());
location.mkdirs();
UnArchiver unArchiver;
unArchiver = archiverManager.getUnArchiver(file);
unArchiver.setSourceFile(file);
unArchiver.setDestDirectory(location);
unArchiver.extract();
} catch (final NoSuchArchiverException e) {
throw new MojoExecutionException("Unknown archiver type", e);
} catch (final ArchiverException e) {
e.printStackTrace();
throw new MojoExecutionException("Error unpacking file: " + file
+ " to: " + location + "\r\n"
+ e.toString(), e);
}
}
}