
org.safehaus.jettyjam.utils.JettyJarResource 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.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Properties;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An external resource that start 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 JettyJarResource implements TestRule {
public static final String RESOURCE_FILE = "JettyJarResource.properties";
private static final String REMOTE_DEBUG = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=";
private static final Logger LOG = LoggerFactory.getLogger( JettyJarResource.class );
public static final String JAR_FILE_PATH_KEY = "jar.file.path";
private final String jarFilePath;
private Process process;
private PrintWriter out;
private BufferedReader in;
private String pidFilePath;
private Properties appProperties;
private String hostname;
private URL serverUrl;
private int port;
private int debugPort = -1;
/**
* Creates a new JettyJarResource.
*
* @throws RuntimeException if the {@link #RESOURCE_FILE} cannot be found.
*/
public JettyJarResource() {
try {
jarFilePath = findExecutableJar();
}
catch ( IOException e ) {
throw new RuntimeException( e );
}
}
public JettyJarResource( int debugPort ) {
this();
this.debugPort = debugPort;
}
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
* @throws IOException when
*/
private String findExecutableJar() throws IOException {
InputStream in = getClass().getClassLoader().getResourceAsStream( RESOURCE_FILE );
Properties props = new Properties();
props.load( in );
return props.getProperty( JAR_FILE_PATH_KEY );
}
protected void before() throws Exception {
File jarFile = new File( jarFilePath );
if ( ! jarFile.exists() ) {
throw new FileNotFoundException( "Cannot find jar file: " + jarFile.getCanonicalPath() );
}
String[] execArgs;
if ( debugPort > 0 ) {
execArgs = new String[] { "java", "-jar", jarFile.getCanonicalPath() };
}
else {
execArgs = new String[] { "java", REMOTE_DEBUG + debugPort, "-jar", jarFile.getCanonicalPath() };
}
process = Runtime.getRuntime().exec( execArgs );
// the path to the pidFilePath will be output from the stderr stream
in = new BufferedReader( new InputStreamReader( process.getErrorStream() ) );
Thread t = new Thread( new Runnable() {
@Override
public void run() {
try {
while ( true ) {
// this will block until we get an output line from stderr
pidFilePath = in.readLine();
if ( foundPidFile() ) {
LOG.info( "Got pidFilePath {} from application CLI", pidFilePath );
return;
}
LOG.info( "Application output: {}", pidFilePath );
}
}
catch ( IOException e ) {
LOG.error( "Failure while reading from standard input", e );
}
// once we get the pidFilePath output we exit - a one time thing!
}
});
t.start();
issuePidFileCommand( t );
if ( pidFilePath == null ) {
out.close();
process.destroy();
throw new IllegalStateException( "No results found for the pidFile path." );
}
LOG.info( "Loading properties from pidFilePath = {}", pidFilePath );
appProperties = new Properties();
appProperties.load( new FileInputStream( pidFilePath ) );
LOG.info( "Loaded properties file: {}", pidFilePath );
appProperties.list( System.out );
port = Integer.parseInt( appProperties.getProperty( JettyRunner.SERVER_PORT ) );
hostname = "localhost";
serverUrl = new URL( appProperties.getProperty( JettyRunner.SERVER_URL ) );
}
private boolean foundPidFile() {
return pidFilePath != null && pidFilePath.startsWith( "/" ) && pidFilePath.endsWith( ".pid" );
}
private void issuePidFileCommand( Thread t ) throws InterruptedException {
while ( ! foundPidFile() ) {
// issue the command to get the pid file path from application
out = new PrintWriter( process.getOutputStream() );
out.println( JettyRunner.PID_FILE );
out.flush();
// wait until the thread above completes and we get the pidFilePath path
t.join( 1000 );
}
}
protected void after() 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();
}
@Override
public Statement apply( final Statement base, final Description description ) {
return statement( base );
}
private Statement statement( final Statement base ) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
before();
try {
base.evaluate();
}
finally {
after();
}
}
};
}
public String getHostname() {
return hostname;
}
public int getPort() {
return port;
}
public URL getServerUrl() {
return serverUrl;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy