All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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