org.codehaus.mojo.cassandra.AbstractCassandraMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-maven-plugin Show documentation
Show all versions of cassandra-maven-plugin Show documentation
The Mojo Projects plugin for Apache Maven to allow launching a local Apache Cassandra instance from a Maven build
/*
* 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.codehaus.mojo.cassandra;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.OS;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.project.MavenProject;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineUtils;
/**
* Base class for all the Cassandra Maven Plugin goals.
*
* @author stephenc
*/
public abstract class AbstractCassandraMojo
extends AbstractMojo
{
/**
* The directory to hold cassandra's database.
*
* @parameter default-value="${project.build.directory}/cassandra"
* @required
*/
protected File cassandraDir;
/**
* The enclosing project.
*
* @parameter default-value="${project}"
* @required
* @readonly
*/
protected MavenProject project;
/**
* The directory containing generated classes.
*
* @parameter expression="${project.build.outputDirectory}"
* @required
*/
private File classesDirectory;
/**
* The directory containing generated test classes.
*
* @parameter expression="${project.build.testOutputDirectory}"
* @required
*/
private File testClassesDirectory;
/**
* Adds the test classpath to cassandra (for example you could use this when you have a custom comparator on your
* test classpath.
*
* @parameter default-value="false"
*/
protected boolean addTestClasspath;
/**
* Adds the main classpath to cassandra (for example you could use this when you have a custom comparator on your
* main classpath.
*
* @parameter default-value="false"
*/
protected boolean addMainClasspath;
/**
* Skip the execution.
*
* @parameter expression="${cassandra.skip}" default-value="false"
*/
protected boolean skip;
/**
* @parameter default-value="${plugin.artifacts}"
* @readonly
*/
private List pluginDependencies;
/**
* @parameter default-value="${plugin.pluginArtifact}"
* @readonly
*/
private Artifact pluginArtifact;
/**
* The current build session instance. This is used for toolchain manager API calls.
*
* @parameter default-value="${session}"
* @required
* @readonly
*/
protected MavenSession session;
/**
* In yaml format any overrides or additional configuration that you want to apply to the server.
*
* @parameter
*/
private String yaml;
/**
* Address to use for the RPC interface. Do not change this unless you really know what you are doing.
*
* @parameter default-value="127.0.0.1"
*/
protected String rpcAddress;
/**
* Port to listen to for the RPC interface.
*
* @parameter expression="${cassandra.rpcPort}" default-value="9160"
*/
protected int rpcPort;
/**
* Port to listen to for the JMX interface.
*
* @parameter expression="${cassandra.jmxPort}" default-value="7199"
*/
protected int jmxPort;
/**
* Port on which the CQL native transport listens for clients.
*
* @parameter expression="${cassandra.nativeTransportPort}" default-value="9042"
*
* @since 2.0.0-1
*/
protected int nativeTransportPort;
/**
* Enable or disable the native transport server. Currently, only the Thrift
* server is started by default because the native transport is considered beta.
*
* @parameter expression="${cassandra.startNativeTransport}" default-value="false"
*
* @since 2.0.0-1
*/
protected boolean startNativeTransport;
/**
* Address to bind to and tell other Cassandra nodes to connect to. You
* must change this if you want multiple nodes to be able to
* communicate!
*
* Leaving it blank leaves it up to InetAddress.getLocalHost(). This
* will always do the Right Thing if the node is properly configured
* (hostname, name resolution, etc), and the Right Thing is to use the
* address associated with the hostname (it might not be).
*
* Setting this to 0.0.0.0 is always wrong.
* Do not change this unless you really know what you are doing.
*
* @parameter default-value="127.0.0.1"
*/
protected String listenAddress;
/**
* Port to listen to for the Storage interface.
*
* @parameter expression="${cassandra.storagePort}" default-value="7000"
*/
protected int storagePort;
/**
* Port to listen to for receiving the stop command over
*
* @parameter expression="${cassandra.stopPort}" default-value="8081"
*/
protected int stopPort;
/**
* Key to be provided when stopping cassandra
*
* @parameter expression="${cassandra.stopKey}" default-value="cassandra-maven-plugin"
*/
protected String stopKey;
/**
* Number of megabytes to limit the cassandra JVM to.
*
* @parameter expression="${cassandra.maxMemory}" default-value="512"
*/
protected int maxMemory;
/**
* The keyspace against which individual operations will be executed
*
* @parameter expression="${cassandra.keyspace}"
*/
protected String keyspace;
/**
* List of System properties to pass to the JUnit tests.
*
* @parameter
* @since 1.2.1-2
*/
protected Map systemPropertyVariables = new HashMap(0);
/**
* Create a jar with just a manifest containing a Main-Class entry for SurefireBooter and a Class-Path entry for
* all classpath elements. Copied from surefire (ForkConfiguration#createJar())
*
* @param jarFile The jar file to create/update
* @param mainClass The main class to run.
* @throws java.io.IOException if something went wrong.
*/
protected void createCassandraJar( File jarFile, String mainClass )
throws IOException
{
createCassandraJar( jarFile, mainClass, cassandraDir );
}
/**
* Create a jar with just a manifest containing a Main-Class entry for SurefireBooter and a Class-Path entry for
* all classpath elements. Copied from surefire (ForkConfiguration#createJar())
*
* @param jarFile The jar file to create/update
* @param mainClass The main class to run.
* @throws java.io.IOException if something went wrong.
*/
protected void createCassandraJar( File jarFile, String mainClass, File cassandraDir )
throws IOException
{
File conf = new File( cassandraDir, "conf" );
FileOutputStream fos = null;
JarOutputStream jos = null;
try
{
fos = new FileOutputStream( jarFile );
jos = new JarOutputStream( fos );
jos.setLevel( JarOutputStream.STORED );
jos.putNextEntry( new JarEntry( "META-INF/MANIFEST.MF" ) );
Manifest man = new Manifest();
// we can't use StringUtils.join here since we need to add a '/' to
// the end of directory entries - otherwise the jvm will ignore them.
StringBuilder cp = new StringBuilder();
cp.append( new URL( conf.toURI().toASCIIString() ).toExternalForm() );
cp.append( ' ' );
getLog().debug( "Adding plugin artifact: " + ArtifactUtils.versionlessKey( pluginArtifact ) +
" to the classpath" );
cp.append( new URL( pluginArtifact.getFile().toURI().toASCIIString() ).toExternalForm() );
cp.append( ' ' );
for ( Artifact artifact : this.pluginDependencies )
{
getLog().debug( "Adding plugin dependency artifact: " + ArtifactUtils.versionlessKey( artifact ) +
" to the classpath" );
// NOTE: if File points to a directory, this entry MUST end in '/'.
cp.append( new URL( artifact.getFile().toURI().toASCIIString() ).toExternalForm() );
cp.append( ' ' );
}
if ( addMainClasspath || addTestClasspath )
{
if ( addTestClasspath )
{
getLog().debug( "Adding: " + testClassesDirectory + " to the classpath" );
cp.append( new URL( testClassesDirectory.toURI().toASCIIString() ).toExternalForm() );
cp.append( ' ' );
}
if ( addMainClasspath )
{
getLog().debug( "Adding: " + classesDirectory + " to the classpath" );
cp.append( new URL( classesDirectory.toURI().toASCIIString() ).toExternalForm() );
cp.append( ' ' );
}
for ( Artifact artifact : (Set) this.project.getArtifacts() )
{
if ( "jar".equals( artifact.getType() ) && !Artifact.SCOPE_PROVIDED.equals( artifact.getScope() )
&& ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) || addTestClasspath ) )
{
getLog().debug( "Adding dependency: " + ArtifactUtils.versionlessKey( artifact ) +
" to the classpath" );
// NOTE: if File points to a directory, this entry MUST end in '/'.
cp.append( new URL( artifact.getFile().toURI().toASCIIString() ).toExternalForm() );
cp.append( ' ' );
}
}
}
man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
man.getMainAttributes().putValue( "Class-Path", cp.toString().trim() );
man.getMainAttributes().putValue( "Main-Class", mainClass );
man.write( jos );
}
finally
{
IOUtil.close( jos );
IOUtil.close( fos );
}
}
/**
* Creates the cassandra home directory.
*
* @throws IOException if something goes wrong.
*/
protected void createCassandraHome()
throws IOException
{
createCassandraHome( cassandraDir, listenAddress, rpcAddress, null, new String[]{ listenAddress } );
}
/**
* Creates the cassandra home directory.
*
* @param cassandraDir the cassandra home directory.
* @throws IOException if something goes wrong.
*/
protected void createCassandraHome( File cassandraDir, String listenAddress, String rpcAddress,
BigInteger initialToken, String[] seeds )
throws IOException
{
File bin = new File( cassandraDir, "bin" );
File conf = new File( cassandraDir, "conf" );
File data = new File( cassandraDir, "data" );
File commitlog = new File( cassandraDir, "commitlog" );
File savedCaches = new File( cassandraDir, "saved_caches" );
for ( File dir : Arrays.asList( cassandraDir, bin, conf, data, commitlog, savedCaches ) )
{
if ( dir.isFile() )
{
getLog().debug( "Deleting file " + dir + " as we need to create a directory with the same name." );
if ( !dir.delete() )
{
getLog().warn( "Could not delete file " + dir );
}
}
if ( !dir.isDirectory() )
{
getLog().debug( "Creating directory " + dir + " as it does not exist." );
if ( !dir.mkdirs() )
{
getLog().warn( "Could not create directory " + dir );
}
}
}
File cassandraYaml = new File( conf, "cassandra.yaml" );
if ( Utils.shouldGenerateResource( project, cassandraYaml ) )
{
getLog().debug( ( cassandraYaml.isFile() ? "Updating " : "Creating " ) + cassandraYaml );
createCassandraYaml( cassandraYaml, data, commitlog, savedCaches, listenAddress, rpcAddress, initialToken,
seeds );
}
File log4jServerProperties = new File( conf, "log4j-server.properties" );
if ( Utils.shouldGenerateResource( project, log4jServerProperties ) )
{
getLog().debug( ( log4jServerProperties.isFile() ? "Updating " : "Creating " ) + log4jServerProperties );
FileUtils.copyURLToFile( getClass().getResource( "/log4j.properties" ), log4jServerProperties );
}
File log4jClientProperties = new File( conf, "log4j-client.properties" );
if ( Utils.shouldGenerateResource( project, log4jClientProperties ) )
{
getLog().debug( ( log4jClientProperties.isFile() ? "Updating " : "Creating " ) + log4jClientProperties );
FileUtils.copyURLToFile( getClass().getResource( "/log4j.properties" ), log4jClientProperties );
}
File cassandraJar = new File( bin, "cassandra.jar" );
if ( Utils.shouldGenerateResource( project, cassandraJar ) )
{
getLog().debug( ( cassandraJar.isFile() ? "Updating " : "Creating " ) + cassandraJar );
createCassandraJar( cassandraJar, CassandraMonitor.class.getName(), cassandraDir );
}
/*
File nodetoolJar = new File( bin, "nodetool.jar" );
if ( Utils.shouldGenerateResource( project, nodetoolJar ) )
{
getLog().debug( ( nodetoolJar.isFile() ? "Updating " : "Creating " ) + nodetoolJar );
createCassandraJar( nodetoolJar, NodeCmd.class.getName(), cassandraDir );
}
*/
}
/**
* Generates the {@code cassandra.yaml} file.
*
* @param cassandraYaml the {@code cassandra.yaml} file.
* @param data The data directory.
* @param commitlog The commitlog directory.
* @param savedCaches The saved caches directory.
* @throws IOException If something went wrong.
*/
private void createCassandraYaml( File cassandraYaml, File data, File commitlog, File savedCaches )
throws IOException
{
createCassandraYaml( cassandraYaml, data, commitlog, savedCaches, listenAddress, rpcAddress, null,
new String[]{ listenAddress } );
}
/**
* Generates the {@code cassandra.yaml} file.
*
* @param cassandraYaml the {@code cassandra.yaml} file.
* @param data The data directory.
* @param commitlog The commitlog directory.
* @param savedCaches The saved caches directory.
* @param listenAddress The address to listen on for storage and other cassandra servers.
* @param rpcAddress The address to listen on for clients.
* @param seeds The seeds.
* @throws IOException If something went wrong.
*/
private void createCassandraYaml( File cassandraYaml, File data, File commitlog, File savedCaches,
String listenAddress, String rpcAddress, BigInteger initialToken, String[] seeds )
throws IOException
{
String defaults = IOUtil.toString( getClass().getResourceAsStream( "/cassandra.yaml" ) );
StringBuilder config = new StringBuilder();
config.append( "data_file_directories:\n" ).append( " - " ).append( data.getAbsolutePath() ).append( "\n" );
config.append( "commitlog_directory: " ).append( commitlog ).append( "\n" );
config.append( "saved_caches_directory: " ).append( savedCaches ).append( "\n" );
config.append( "initial_token: " ).append(
initialToken == null || "null".equals( initialToken ) ? "" : initialToken.toString() ).append( "\n" );
config.append( "listen_address: " ).append( listenAddress ).append( "\n" );
config.append( "storage_port: " ).append( storagePort ).append( "\n" );
config.append( "rpc_address: " ).append( rpcAddress ).append( "\n" );
config.append( "rpc_port: " ).append( rpcPort ).append( "\n" );
config.append( "native_transport_port: " ).append( nativeTransportPort ).append( "\n" );
config.append( "start_native_transport: " ).append( startNativeTransport ).append( "\n" );
if ( seeds != null )
{
config.append( "seed_provider: " ).append( "\n" );
config.append( " - class_name: org.apache.cassandra.locator.SimpleSeedProvider" ).append( "\n" );
config.append( " parameters:" ).append( "\n" );
String sep = " - seeds: \"";
for ( int i = 0; i < seeds.length; i++ )
{
config.append( sep ).append( seeds[i] );
sep = ", ";
}
if ( sep.length() == 2 )
{
config.append( "\"" ).append( "\n" );
}
}
FileUtils.fileWrite( cassandraYaml.getAbsolutePath(),
Utils.merge( Utils.merge( defaults, yaml ), config.toString() ) );
}
/**
* Gets the Java toolchain.
*
* @return the Java toolchain.
*/
protected Toolchain getToolchain()
{
Toolchain tc = null;
try
{
if ( session != null ) // session is null in tests..
{
ToolchainManager toolchainManager =
(ToolchainManager) session.getContainer().lookup( ToolchainManager.ROLE );
if ( toolchainManager != null )
{
tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
}
}
}
catch ( ComponentLookupException componentLookupException )
{
// just ignore, could happen in pre-2.0.9 builds..
}
return tc;
}
/**
* Create a {@link CommandLine} to launch Java.
*
* @return a {@link CommandLine} to launch Java.
*/
protected CommandLine newJavaCommandLine()
{
String exec = null;
Toolchain tc = getToolchain();
// if the file doesn't exist & toolchain is null, java is probably in the PATH...
// we should probably also test for isFile and canExecute, but the second one is only
// available in SDK 6.
if ( tc != null )
{
getLog().info( "Toolchain in cassandra-maven-plugin: " + tc );
exec = tc.findTool( "java" );
}
else
{
if ( OS.isFamilyWindows() )
{
String ex = "java.exe";
// now try to figure the path from PATH, PATHEXT env vars
// if bat file, wrap in cmd /c
String path = System.getenv( "PATH" );
if ( path != null )
{
for ( String elem : StringUtils.split( path, File.pathSeparator ) )
{
File f = new File( new File( elem ), ex );
if ( f.exists() )
{
exec = ex;
break;
}
}
}
}
}
if ( exec == null )
{
exec = "java";
}
return new CommandLine( exec );
}
/**
* Creates the environment required when launching Cassandra or the CLI tools.
*
* @return the environment required when launching Cassandra or the CLI tools.
*/
protected Map createEnvironmentVars()
{
Map enviro = new HashMap();
try
{
Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
for ( Map.Entry entry : systemEnvVars.entrySet() )
{
enviro.put( (String) entry.getKey(), (String) entry.getValue() );
}
}
catch ( IOException x )
{
getLog().error( "Could not assign default system enviroment variables.", x );
}
enviro.put( "CASSANDRA_CONF", new File( cassandraDir, "conf" ).getAbsolutePath() );
return enviro;
}
/**
* Creates the command line to launch the cassandra server.
*
* @return the command line to launch the cassandra server.
* @throws IOException if there are issues creating the cassandra home directory.
*/
protected CommandLine newServiceCommandLine()
throws IOException
{
return newServiceCommandLine( cassandraDir, listenAddress, rpcAddress, null, new String[]{ listenAddress },
true, jmxPort );
}
/**
* Creates the command line to launch the cassandra server.
*
* @return the command line to launch the cassandra server.
* @throws IOException if there are issues creating the cassandra home directory.
*/
protected CommandLine newServiceCommandLine( File cassandraDir, String listenAddress, String rpcAddress,
BigInteger initialToken, String[] seeds, boolean jmxRemoteEnabled,
int jmxPort )
throws IOException
{
createCassandraHome( cassandraDir, listenAddress, rpcAddress, initialToken, seeds );
CommandLine commandLine = newJavaCommandLine();
commandLine.addArgument( "-Xmx" + maxMemory + "m" );
if ( stopKey != null && stopPort > 0 && stopPort < 65536 )
{
commandLine.addArgument( "-D" + CassandraMonitor.KEY_PROPERTY_NAME + "=" + stopKey );
commandLine.addArgument( "-D" + CassandraMonitor.PORT_PROPERTY_NAME + "=" + stopPort );
commandLine.addArgument( "-D" + CassandraMonitor.HOST_PROPERTY_NAME + "=" + listenAddress );
}
commandLine.addArgument( "-Dlog4j.configuration=" + new File( new File( cassandraDir, "conf" ),
"log4j-server.properties" ).toURI().toURL().toString() );
commandLine.addArgument( "-Dcom.sun.management.jmxremote=" + jmxRemoteEnabled );
if ( jmxRemoteEnabled )
{
commandLine.addArgument( "-Dcom.sun.management.jmxremote.port=" + jmxPort );
commandLine.addArgument( "-Dcom.sun.management.jmxremote.ssl=false" );
commandLine.addArgument( "-Dcom.sun.management.jmxremote.authenticate=false" );
}
systemPropertyVariables.put("jna.nosys", "true");
if ( systemPropertyVariables != null && !systemPropertyVariables.isEmpty() )
{
for ( Map.Entry entry : systemPropertyVariables.entrySet() )
{
commandLine.addArgument( "-D" + entry.getKey() + "=" + entry.getValue() );
}
}
commandLine.addArgument( "-jar" );
// It seems that java cannot handle quoted jar file names...
commandLine.addArgument( new File( new File( cassandraDir, "bin" ), "cassandra.jar" ).getAbsolutePath(),
false );
return commandLine;
}
/**
* Creates the command line to launch the {@code nodetool} utility.
*
* @param args the command line arguments to pass to the {@code nodetool} utility.
* @return the {@link CommandLine} to launch {@code nodetool} with the supplied arguments.
* @throws IOException if there are issues creating the cassandra home directory.
*/
protected CommandLine newNodetoolCommandLine( String... args )
throws IOException
{
createCassandraHome();
CommandLine commandLine = newJavaCommandLine();
commandLine.addArgument( "-jar" );
// It seems that java cannot handle quoted jar file names...
commandLine.addArgument( new File( new File( cassandraDir, "bin" ), "nodetool.jar" ).getAbsolutePath(), false );
commandLine.addArgument( "--host" );
commandLine.addArgument( "127.0.0.1" );
commandLine.addArgument( "--port" );
commandLine.addArgument( Integer.toString( jmxPort ) );
commandLine.addArguments( args );
return commandLine;
}
/**
* Turns a file into a path string that is quoted (and escaped) if necessary
*
* @param file the file to convert to a path string.
* @return the path string.
*/
private static String toPathString( File file )
{
String path = file.getAbsolutePath();
boolean hasSpaces = path.indexOf( ' ' ) != -1;
boolean hasDoubleQuotes = path.indexOf( '\"' ) != -1;
boolean hasSingleQuotes = path.indexOf( '\'' ) != -1;
if ( !( hasSpaces || hasDoubleQuotes || hasSingleQuotes ) )
{
return path;
}
if ( !hasDoubleQuotes )
{
return '\"' + path + '\"';
}
if ( !hasSingleQuotes )
{
return '\'' + path + '\'';
}
return '\"' + StringUtils.escape( path, new char[]{ '\"' }, '\'' ) + '\"';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy