org.safehaus.jettyjam.utils.JettyIntegResource Maven / Gradle / Ivy
package org.safehaus.jettyjam.utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.junit.runner.Description;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An external resource that starts and stops the embedded Jetty application in an
* executable jar file generated by the build for conducting integration tests with
* the maven failsafe plugin.
*/
public class JettyIntegResource extends AbstractJettyResource {
public static final String RESOURCE_FILE = "jettyjam.properties";
public static final String JAR_FILE_PATH_KEY = "jar.file.path";
private static final String REMOTE_DEBUG = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=";
private final Logger LOG = LoggerFactory.getLogger( JettyIntegResource.class );
private String jarFilePath;
private final Object lock = new Object();
private Process process;
private PrintWriter out;
private BufferedReader inErr;
private String pidFilePath;
private String pidFileContents;
private Properties appProperties;
private Properties systemProperties = new Properties();
private String[] args = new String[0];
private int debugPort = -1;
/**
* Creates a new JettyIntegResource using the default resource file for configuration.
*
* @throws RuntimeException if the {@link #RESOURCE_FILE} cannot be found.
*/
public JettyIntegResource( Class testClass ) {
super( testClass, TestMode.INTEG );
jarFilePath = findExecutableJar( null );
}
/**
* Creates a new JettyIntegResource using the default resource file for configuration.
*
* @throws RuntimeException if the {@link #RESOURCE_FILE} cannot be found.
*/
public JettyIntegResource( Object testInstance ) {
super( testInstance, TestMode.INTEG );
jarFilePath = findExecutableJar( null );
}
/**
* Creates a new JettyIntegResource using a user specified resource file for configuration.
*
* @throws RuntimeException if the {@link #RESOURCE_FILE} cannot be found.
*/
public JettyIntegResource( Class testClass, String resourceFile ) {
super( testClass, TestMode.INTEG );
jarFilePath = findExecutableJar( resourceFile );
}
/**
* Creates a new JettyIntegResource using a user specified resource file for configuration.
*
* @throws RuntimeException if the {@link #RESOURCE_FILE} cannot be found.
*/
public JettyIntegResource( Object testInstance, String resourceFile ) {
super( testInstance, TestMode.INTEG );
jarFilePath = findExecutableJar( resourceFile );
}
public JettyIntegResource( Class testClass, String resourceFile, String[] args, Properties properties ) {
this( testClass, resourceFile );
this.args = args;
this.systemProperties = properties;
}
public JettyIntegResource( Object testInstance, String resourceFile, String[] args, Properties properties ) {
this( testInstance, resourceFile );
this.args = args;
this.systemProperties = properties;
}
public JettyIntegResource( Class testClass, String[] args ) {
this( testClass );
this.args = args;
}
public JettyIntegResource( Object testInstance, String[] args ) {
this( testInstance );
this.args = args;
}
public JettyIntegResource( Class testClass, String resourceFile, String[] args ) {
this( testClass, resourceFile );
this.args = args;
}
public JettyIntegResource( Object testInstance, String resourceFile, String[] args ) {
this( testInstance, resourceFile );
this.args = args;
}
public JettyIntegResource( Class testClass, Properties systemProperties ) {
this( testClass );
this.systemProperties = systemProperties;
}
public JettyIntegResource( Object testInstance, Properties systemProperties ) {
this( testInstance );
this.systemProperties = systemProperties;
}
public JettyIntegResource( Class testClass, String resourceFile, int debugPort, String[] args, Properties systemProperties ) {
this( testClass, resourceFile );
this.debugPort = debugPort;
this.args = args;
this.systemProperties = systemProperties;
}
public JettyIntegResource( Object testInstance, String resourceFile, int debugPort, String[] args, Properties systemProperties ) {
this( testInstance, resourceFile );
this.debugPort = debugPort;
this.args = args;
this.systemProperties = systemProperties;
}
public Properties getAppProperties() {
return appProperties;
}
/**
* Finds the path to the executable jar file with the embedded Jetty application.
* There are a number of approaches that could be taken to discover the executable
* jar file produced by the build. The most reliable, which will work both on the
* command line via Maven and in IDE environments is to load a properties
* resource file containing the property for the executable jar file path. The
* properties can have variable substitutions applied to it. The down side to this
* approach is that every project that uses this external resource must have that
* resource file present in order to run IT tests on the generated executable jar
* file.
*
* @return the path to the executable jar
*/
private String findExecutableJar( String resourceFile ) {
String finalConfig = null;
if ( resourceFile == null ) {
LOG.info( "Using default resource file for configuration: {}", RESOURCE_FILE );
finalConfig = RESOURCE_FILE;
}
else
{
LOG.info( "Using user specified resource file for configuration: {}", resourceFile );
finalConfig = resourceFile;
}
InputStream in = getClass().getClassLoader().getResourceAsStream( finalConfig );
if ( in != null ) {
Properties props = new Properties();
try {
props.load( in );
}
catch ( IOException e ) {
return null;
}
return props.getProperty( JAR_FILE_PATH_KEY );
}
LOG.warn( "Resource file for finding the executable jar {} not found.", finalConfig );
return null;
}
@Override
public void start( Description description ) throws Exception {
super.start( description );
if ( started ) {
return;
}
File jarFile = new File( jarFilePath );
if ( ! jarFile.exists() ) {
throw new FileNotFoundException( "Cannot find jar file: " + jarFile.getCanonicalPath() );
}
List cmd = new ArrayList( 4 + args.length + systemProperties.size() );
cmd.add( "java" );
cmd.add( "-jar" );
if ( debugPort > 0 ) {
cmd.add( REMOTE_DEBUG + debugPort );
}
for ( Object key : systemProperties.keySet() ) {
String propName = ( String ) key;
if ( systemProperties.getProperty( propName ) == null ) {
cmd.add( "-D" + propName );
}
else {
cmd.add( "-D" + propName + "=" + systemProperties.getProperty( propName ) );
}
}
cmd.add( jarFile.getCanonicalPath() );
// don't know if add is append so not using Collection copy
//noinspection ManualArrayToCollectionCopy
for ( String arg : args ) {
cmd.add( arg );
}
String[] execArgs = cmd.toArray( new String [ cmd.size() ] );
process = Runtime.getRuntime().exec( execArgs );
// the path to the pidFilePath will be output from the stderr stream
inErr = new BufferedReader( new InputStreamReader( process.getErrorStream() ) );
final BufferedReader inOut = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
/*
* ------------------------------------------------------------------------------------
* Thread reads from stdout of the external process and dumps to our stdout
* ------------------------------------------------------------------------------------
*/
new Thread( new Runnable() {
@Override
public void run() {
String line;
try {
while ( ( line = inOut.readLine() ) != null ) {
System.out.println( line );
}
}
catch ( IOException e ) {
if ( e.getMessage().trim().equalsIgnoreCase( "Stream closed" ) ) {
LOG.info( "External process output stream closed." );
return;
}
e.printStackTrace();
LOG.info( "Exception causing stoppage of external process output printing." );
}
}
} ).start();
/*
* ------------------------------------------------------------------------------------
* Thread reads from stderr of the external process and dumps to our stderr if and
* only if the output does not correspond to a pid file dump. Once the pid file is
* found it just continues reading and dumping.
* ------------------------------------------------------------------------------------
*/
new Thread( new Runnable() {
@Override
public void run() {
String line;
try {
while ( ( line = inErr.readLine() ) != null ) {
if ( pidFilePath == null && foundPidFile( line ) ) {
pidFilePath = line;
LOG.info( "Got pidFilePath {} from application CLI", pidFilePath );
synchronized ( lock ) { lock.notifyAll(); }
}
else {
System.err.println( line );
}
}
}
catch ( IOException e ) {
LOG.error( "Failure while reading from stderr", e );
}
}
}).start();
issuePidFileCommand();
if ( pidFilePath == null ) {
out.close();
process.destroy();
throw new IllegalStateException( "No results found for the pidFile path." );
}
readPidFile();
started = true;
}
private void readPidFile() throws IOException {
LOG.info( "Loading properties from pidFilePath = {}", pidFilePath );
File pidFile = new File( pidFilePath );
if ( ! pidFile.exists() ) {
throw new IllegalStateException( "The pidFile " + pidFile.getAbsolutePath()
+ " MUST exist but it does not." );
}
int lineCount = 0;
String line;
StringBuilder sb = new StringBuilder();
BufferedReader pidIn = new BufferedReader( new FileReader( pidFile ) );
while( ( line = pidIn.readLine() ) != null ) {
lineCount++;
sb.append( "\t" ).append( lineCount ).append( '\t' ).append( line ).append( '\n' );
}
pidFileContents = sb.toString();
appProperties = new Properties();
appProperties.load( new FileInputStream( pidFilePath ) );
LOG.info( "Loaded properties file: {}", pidFilePath );
LOG.info( "Pid file contents: \n{}", pidFileContents );
port = Integer.parseInt( appProperties.getProperty( JettyRunner.SERVER_PORT ) );
hostname = "localhost";
serverUrl = new URL( appProperties.getProperty( JettyRunner.SERVER_URL ) );
secure = Boolean.parseBoolean( appProperties.getProperty( JettyRunner.IS_SECURE ) );
}
private boolean foundPidFile( String line ) {
return line != null && line.endsWith( ".pid" );
}
private void issuePidFileCommand() throws InterruptedException {
while ( ! foundPidFile( pidFilePath ) ) {
// issue the command to get the pid file path from application
out = new PrintWriter( process.getOutputStream() );
out.println( JettyRunner.PID_FILE );
out.flush();
// now block for a while until the app responds in thread above
synchronized ( lock ) {
lock.wait( 2000 );
}
}
}
@Override
public void stop( Description description ) throws Exception {
if ( pidFilePath != null ) {
File pidFile = new File( pidFilePath );
if ( pidFile.exists() && ! pidFile.delete() ) pidFile.deleteOnExit();
}
out.println( JettyRunner.SHUTDOWN );
out.flush();
out.close();
process.destroy();
started = false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append( getClass().getSimpleName() ).append( ":\n{" );
sb.append( "\n\tjarFilePath = \t\t" ).append( jarFilePath );
sb.append( "\n\tpidFilePath = \t\t" ).append( pidFilePath );
sb.append( "\n\tmode = \t\t\t\t" ).append( getMode() );
sb.append( "\n\thostname = \t\t\t" ).append( getHostname() );
sb.append( "\n\tport = \t\t\t\t" ).append( getPort() );
sb.append( "\n\tserverUrl = \t\t" ).append( getServerUrl() );
sb.append( "\n\targs = \t\t\t\t" ).append( Arrays.deepToString( args ) );
sb.append( "\n\tdebugPort = \t\t" ).append( debugPort );
sb.append( "\n\tsecure = \t\t\t" ).append( isSecure() );
sb.append( "\n\tstarted = \t\t\t" ).append( isStarted() );
sb.append( "\n\tsystemProperties = \t" ).append( systemProperties );
sb.append( "\n\tappProperties = \t" ).append( appProperties );
if ( pidFileContents != null ) {
sb.append( "------------------------------------------------------------------------------------------\n" );
sb.append( "\n\tpidFileContents\n" );
sb.append( pidFileContents );
sb.append( "------------------------------------------------------------------------------------------\n" );
}
sb.append( "\n}" );
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy