package io.dropwizard.testing.junit;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import io.dropwizard.Application;
import io.dropwizard.Configuration;
import io.dropwizard.cli.ServerCommand;
import io.dropwizard.lifecycle.ServerLifecycleListener;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import net.sourceforge.argparse4j.inf.Namespace;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

import javax.annotation.Nullable;
import java.util.Enumeration;

/**
 * A JUnit rule for starting and stopping your application at the start and end of a test class.
 * <p/>
 * By default, the {@link Application} will be constructed using reflection to invoke the nullary
 * constructor. If your application does not provide a public nullary constructor, you will need to
 * override the {@link #newApplication()} method to provide your application instance(s).
 *
 * @param <C> the configuration type
 */
public class DropwizardAppRule<C extends Configuration> implements TestRule {

    private final Class<? extends Application<C>> applicationClass;
    private final String configPath;

    private C configuration;
    private Application<C> application;
    private Environment environment;
    private Server jettyServer;

    public DropwizardAppRule(Class<? extends Application<C>> applicationClass,
                             @Nullable String configPath,
                             ConfigOverride... configOverrides) {
        this.applicationClass = applicationClass;
        this.configPath = configPath;
        for (ConfigOverride configOverride: configOverrides) {
            configOverride.addToSystemProperties();
        }
    }

    @Override
    public Statement apply(final Statement base, Description description) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                startIfRequired();
                try {
                    base.evaluate();
                } finally {
                    resetConfigOverrides();
                    jettyServer.stop();
                    jettyServer = null;
                }
            }
        };
    }

    private void resetConfigOverrides() {
        for (Enumeration<?> props = System.getProperties().propertyNames(); props.hasMoreElements();) {
            String keyString = (String) props.nextElement();
            if (keyString.startsWith("dw.")) {
                System.clearProperty(keyString);
            }
        }
    }

    private void startIfRequired() {
        if (jettyServer != null) {
            return;
        }

        try {
            application = newApplication();

            final Bootstrap<C> bootstrap = new Bootstrap<C>(application) {
                @Override
                public void run(C configuration, Environment environment) throws Exception {
                    environment.lifecycle().addServerLifecycleListener(new ServerLifecycleListener() {
                                    @Override
                                    public void serverStarted(Server server) {
                                        jettyServer = server;
                                    }
                                });
                    DropwizardAppRule.this.configuration = configuration;
                    DropwizardAppRule.this.environment = environment;
                    super.run(configuration, environment);
                }
            };

            application.initialize(bootstrap);
            final ServerCommand<C> command = new ServerCommand<>(application);

            ImmutableMap.Builder<String, Object> file = ImmutableMap.builder();
            if (!Strings.isNullOrEmpty(configPath)) {
                file.put("file", configPath);
            }
            final Namespace namespace = new Namespace(file.build());

            command.run(bootstrap, namespace);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public C getConfiguration() {
        return configuration;
    }

    public int getLocalPort() {
        return ((ServerConnector) jettyServer.getConnectors()[0]).getLocalPort();
    }

    public int getAdminPort() {
        return ((ServerConnector) jettyServer.getConnectors()[1]).getLocalPort();
    }

    public Application<C> newApplication() {
        try {
            return applicationClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public <A extends Application<C>> A getApplication() {
        return (A) application;
    }

    public Environment getEnvironment() {
        return environment;
    }
}

@@ -70,16 +70,58 @@
 trings;%0A
+import com.google.common.base.Throwables;%0A
 import c
@@ -155,24 +155,24 @@
 mutableMap;%0A
-
 import io.dr
@@ -541,16 +541,57 @@
 nector;%0A
+import org.junit.rules.ExternalResource;%0A
 import o
@@ -760,16 +760,76 @@
 ation;%0A%0A
+import static com.google.common.base.Throwables.propagate;%0A%0A
 /**%0A * A
@@ -1321,26 +1321,31 @@
 on%3E 
-implements TestRul
+extends ExternalResourc
 e %7B%0A
@@ -2036,76 +2036,31 @@
    p
-ublic Statement apply(final Statement base, Description description)
+rotected void before() 
  %7B%0A 
@@ -2070,45 +2070,38 @@
     
-return new Statement() %7B
+startIfRequired();
 %0A    
-  
+%7D%0A%0A
     
-  
 @Ove
@@ -2114,89 +2114,61 @@
     
-        public void evalua
+protected void af
 te
+r
 () 
-throws Throwable %7B%0A                startIfRequired
+%7B%0A        resetConfigOverrides
 ();%0A
@@ -2175,24 +2175,16 @@
         
-        
 try %7B%0A  
@@ -2197,142 +2197,83 @@
     
-        base.evaluate();%0A                %7D finally %7B%0A                    resetConfigOverrides();%0A                    jettyServer.stop(
+jettyServer.stop();%0A        %7D catch (Exception e) %7B%0A            propagate(e
 );%0A 
@@ -2275,35 +2275,34 @@
 e);%0A        
-   
+%7D%0A
         
  jettyServer
@@ -2281,33 +2281,32 @@
       %7D%0A        
- 
 jettyServer = nu
@@ -2313,51 +2313,8 @@
 ll;%0A
-                %7D%0A            %7D%0A        %7D;%0A
     

Make DropwizardAppRule extend ExternalResource