From 3ea5efa862cd531cb907283c9d87fdfdc2a20731 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 7 Oct 2016 17:20:19 +0200 Subject: [PATCH] DeployerThread refactored to extract meat into a Deployer utility The utility can then be added to the application context, and only one lookup is needed. Also switchews on autoconfiguration in the deployer app, since the deployer implementations support that now. --- .../cloud/launcher/deployer/Deployer.java | 312 ++++++++++++++++++ .../deployer/DeployerConfiguration.java | 30 +- .../launcher/deployer/DeployerThread.java | 254 +------------- .../deployer/DeployerThreadTests.java | 6 +- 4 files changed, 327 insertions(+), 275 deletions(-) create mode 100644 spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/Deployer.java diff --git a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/Deployer.java b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/Deployer.java new file mode 100644 index 0000000..9737402 --- /dev/null +++ b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/Deployer.java @@ -0,0 +1,312 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed 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.springframework.cloud.launcher.deployer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.springframework.cloud.deployer.spi.app.AppDeployer; +import org.springframework.cloud.deployer.spi.app.AppStatus; +import org.springframework.cloud.deployer.spi.app.DeploymentState; +import org.springframework.cloud.deployer.spi.core.AppDefinition; +import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; +import org.springframework.cloud.launcher.deployer.DeployerProperties.Deployable; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.OrderComparator; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.springframework.util.StringUtils.collectionToCommaDelimitedString; + +/** + * @author Dave Syer + * + */ +public class Deployer { + + private static final Logger logger = LoggerFactory.getLogger(Deployer.class); + + public static final String DIRECTORY_NAME = ".spring-cloud"; + + final AppDeployer deployer; + + final ResourceLoader resourceLoader; + + final DeployerProperties properties; + + final ConfigurableEnvironment environment; + + private Map deployed = new ConcurrentHashMap<>(); + + public Deployer(AppDeployer deployer, ResourceLoader resourceLoader, + DeployerProperties properties, ConfigurableEnvironment environment) { + this.deployer = deployer; + this.resourceLoader = resourceLoader; + this.properties = properties; + this.environment = environment; + } + + public void deploy() { + ArrayList invalid = new ArrayList<>(); + // validate that items in deploy, are valid deployables + for (String toDeploy : properties.getDeploy()) { + if (!properties.getDeployables().containsKey(toDeploy)) { + invalid.add(toDeploy); + } + } + + if (!invalid.isEmpty()) { + logger.error("Error starting 'spring cloud'." + + "\n\nThe following are not valid: '" + + collectionToCommaDelimitedString(invalid) + + "'. Please check the name(s) and try again.\n" + + "Valid choices are: " + collectionToCommaDelimitedString( + properties.getDeployables().keySet()) + + ".\n"); + return; + } + + ArrayList deployables = new ArrayList<>( + properties.getDeployables().values()); + OrderComparator.sort(deployables); + + logger.debug("Deployables {}", properties.getDeployables()); + + for (Deployable deployable : deployables) { + deployInternal(deployer, resourceLoader, deployable, + properties, environment); + } + + for (Deployable deployable : deployables) { + if (shouldDeploy(deployable, properties) + && StringUtils.hasText(deployable.getMessage())) { + System.out.println("\n\n" + deployable.getName() + ": " + + deployable.getMessage() + "\n"); + } + } + + if (this.deployed.isEmpty()) { + System.out.println( + "\n\nNothing to deploy (see spring.cloud.launcher.deploy).\n"); + return; + } + + System.out.println("\n\nType Ctrl-C to quit.\n"); + while (true) { + for (Map.Entry entry : this.deployed.entrySet()) { + String id = entry.getKey(); + DeploymentState state = entry.getValue(); + AppStatus status = deployer.status(id); + DeploymentState newState = status.getState(); + if (state != newState) { + logger.info("{} change status from {} to {}", id, state, newState); + this.deployed.put(id, newState); + } + } + try { + Thread.sleep(properties.getStatusSleepMillis()); + } + catch (InterruptedException e) { + logger.error("error sleeping", e); + Thread.currentThread().interrupt(); + } + } + + } + + @EventListener + public void shutdown(ContextClosedEvent event) { + logger.info("\n\nShutting down ...\n"); + for (String id : Deployer.this.deployed.keySet()) { + logger.info("Undeploying {}", id); + deployer.undeploy(id); + } + } + + private String deployInternal(AppDeployer deployer, ResourceLoader resourceLoader, + Deployable deployable, DeployerProperties properties, + ConfigurableEnvironment environment) { + if (!shouldDeploy(deployable, properties)) { + return null; + } + + logger.debug("getting resource {} = {}", deployable.getName(), + deployable.getCoordinates()); + Resource resource = resourceLoader.getResource(deployable.getCoordinates()); + + Map appDefProps = new HashMap<>(); + appDefProps.put("server.port", String.valueOf(deployable.getPort())); + + // For each of the other deployables, add "negative" properties that apply to all + // apps when it is disabled + for (Deployable other : properties.getDeployables().values()) { + if (!shouldDeploy(other.getName(), properties)) { + appDefProps.putAll(other.getDisabled()); + } + } + for (Deployable other : properties.getDeployables().values()) { + if (shouldDeploy(other.getName(), properties)) { + appDefProps.putAll(other.getEnabled()); + } + } + Map map = extractProperties("/" + deployable.getName() + ".yml"); + for (String key : map.keySet()) { + appDefProps.put(key, map.get(key)); + } + + AppDefinition definition = new AppDefinition(deployable.getName(), appDefProps); + + Map deploymentProperties = new LinkedHashMap<>(); + deploymentProperties.put(AppDeployer.GROUP_PROPERTY_KEY, "launcher"); + deploymentProperties.putAll(deployable.getProperties()); + + AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, + deploymentProperties); + + logger.debug("Deploying resource {} = {}", deployable.getName(), + deployable.getCoordinates()); + logger.debug("AppDefinition Properties: {}", appDefProps); + logger.debug("Deployment Properties: {}", deploymentProperties); + String id = deployer.deploy(request); + AppStatus appStatus = getAppStatus(deployer, id); + // TODO: stream stdout/stderr like docker-compose (with colors and prefix) + + if (deployable.isWaitUntilStarted()) { + try { + logger.info("\n\nWaiting for {} to start.\n", deployable.getName()); + + while (appStatus.getState() != DeploymentState.deployed + && appStatus.getState() != DeploymentState.failed) { + Thread.sleep(properties.getStatusSleepMillis()); + appStatus = getAppStatus(deployer, id); + logger.trace("State of {} = {}", id, appStatus.getState()); + } + } + catch (Exception e) { + logger.error("error updating status of " + id, e); + } + } + logger.info("Status of {}: {}", id, appStatus); + + return id; + } + + private boolean shouldDeploy(Deployable deployable, DeployerProperties properties) { + return shouldDeploy(deployable.getName(), properties); + } + + private boolean shouldDeploy(String name, DeployerProperties properties) { + boolean deploy = properties.getDeploy().contains(name); + logger.trace("shouldDeploy {} = {}", name, deploy); + return deploy; + } + + private AppStatus getAppStatus(AppDeployer deployer, String id) { + AppStatus appStatus = deployer.status(id); + this.deployed.put(id, appStatus.getState()); + return appStatus; + } + + private Map extractProperties(String path) { + PropertySource source = extractPropertySource(path); + Map map = new LinkedHashMap(); + if (source instanceof EnumerablePropertySource) { + EnumerablePropertySource enumerable = (EnumerablePropertySource) source; + for (String name : enumerable.getPropertyNames()) { + map.put(name, source.getProperty(name) == null ? null + : source.getProperty(name).toString()); + } + } + return map; + } + + private PropertySource extractPropertySource(String path) { + PropertySource source = null; + Resource resource = new ClassPathResource("config" + path, DeployerThread.class); + source = loadPropertySource(resource, path); + if (source == null) { + resource = new ClassPathResource(path, DeployerThread.class); + source = loadPropertySource(resource, path); + } + if (source == null) { + resource = new FileSystemResource("config" + path); + source = loadPropertySource(resource, path); + } + if (source == null) { + resource = new FileSystemResource("." + path); + source = loadPropertySource(resource, path); + } + if (source == null) { + File home = getHomeFolder(); + File springCloudDir = (home == null ? null : new File(home, DIRECTORY_NAME)); + if (logger.isDebugEnabled() && springCloudDir != null) { + logger.debug("~/.spring-cloud directory: {}, exists: {}, isDir: {}", + springCloudDir, springCloudDir.exists(), + springCloudDir.isDirectory()); + } + else if (logger.isDebugEnabled()) { + logger.debug("no ~/.spring-cloud directory"); + } + if (springCloudDir != null && springCloudDir.exists() + && springCloudDir.isDirectory()) { + resource = new FileSystemResource(new File(springCloudDir, path)); + source = loadPropertySource(resource, path); + } + } + return source; + } + + protected File getHomeFolder() { + String home = System.getProperty("user.home"); + if (StringUtils.hasLength(home)) { + return new File(home); + } + return null; + } + + private PropertySource loadPropertySource(Resource resource, String path) { + if (resource.exists()) { + try { + PropertySource source = new YamlPropertySourceLoader().load(path, + resource, null); + if (source != null) { + logger.info("Loaded YAML properties from: " + resource); + } + return source; + } + catch (IOException e) { + } + } + return null; + } + +} diff --git a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerConfiguration.java b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerConfiguration.java index 593fe4e..8b447c0 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerConfiguration.java +++ b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerConfiguration.java @@ -16,48 +16,38 @@ package org.springframework.cloud.launcher.deployer; -import java.util.HashMap; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.deployer.resource.maven.MavenProperties; import org.springframework.cloud.deployer.resource.maven.MavenResourceLoader; import org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader; import org.springframework.cloud.deployer.spi.app.AppDeployer; -import org.springframework.cloud.deployer.spi.local.LocalAppDeployer; -import org.springframework.cloud.deployer.spi.local.LocalDeployerProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.ResourceLoader; +import java.util.HashMap; + /** * @author Spencer Gibb */ @Configuration -@EnableConfigurationProperties +@EnableAutoConfiguration +@EnableConfigurationProperties({ DeployerProperties.class }) public class DeployerConfiguration { @Bean - public DeployerProperties deployerProperties() { - return new DeployerProperties(); - } - - @Bean - public LocalDeployerProperties localDeployerProperties() { - return new LocalDeployerProperties(); - } - - @Bean - @ConditionalOnMissingBean - public AppDeployer appDeployer() { - return new LocalAppDeployer(localDeployerProperties()); + public Deployer deployer(AppDeployer deployer, MavenResourceLoader resourceLoader, + DeployerProperties properties, ConfigurableEnvironment environment) { + return new Deployer(deployer, resourceLoader, properties, environment); } @ConfigurationProperties(prefix = "spring.cloud.maven") @Bean public MavenProperties mavenProperties() { - return new MavenProperties(); //TODO: exposed as config properties? + return new MavenProperties(); } @Bean diff --git a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java index e1f738a..04695e6 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java +++ b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/main/java/org/springframework/cloud/launcher/deployer/DeployerThread.java @@ -21,40 +21,16 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.Banner.Mode; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.logback.LogbackLoggingSystem; -import org.springframework.cloud.deployer.resource.support.DelegatingResourceLoader; -import org.springframework.cloud.deployer.spi.app.AppDeployer; -import org.springframework.cloud.deployer.spi.app.AppStatus; -import org.springframework.cloud.deployer.spi.app.DeploymentState; -import org.springframework.cloud.deployer.spi.core.AppDefinition; -import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; -import org.springframework.cloud.launcher.deployer.DeployerProperties.Deployable; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.OrderComparator; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.EnumerablePropertySource; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static org.springframework.util.StringUtils.collectionToCommaDelimitedString; /** * @author Spencer Gibb @@ -64,9 +40,6 @@ public class DeployerThread extends Thread { private static final Logger logger = LoggerFactory.getLogger(DeployerThread.class); private static final String DEFAULT_VERSION = "1.2.0.BUILD-SNAPSHOT"; - public static final String DIRECTORY_NAME = ".spring-cloud"; - - private Map deployed = new ConcurrentHashMap<>(); private String[] args; @@ -124,76 +97,6 @@ public class DeployerThread extends Thread { } } - private Map extractProperties(String path) { - PropertySource source = extractPropertySource(path); - Map map = new LinkedHashMap(); - if (source instanceof EnumerablePropertySource) { - EnumerablePropertySource enumerable = (EnumerablePropertySource) source; - for (String name : enumerable.getPropertyNames()) { - map.put(name, source.getProperty(name) == null ? null - : source.getProperty(name).toString()); - } - } - return map; - } - - private PropertySource extractPropertySource(String path) { - PropertySource source = null; - Resource resource = new ClassPathResource("config" + path, DeployerThread.class); - source = loadPropertySource(resource, path); - if (source == null) { - resource = new ClassPathResource(path, DeployerThread.class); - source = loadPropertySource(resource, path); - } - if (source == null) { - resource = new FileSystemResource("config" + path); - source = loadPropertySource(resource, path); - } - if (source == null) { - resource = new FileSystemResource("." + path); - source = loadPropertySource(resource, path); - } - if (source == null) { - File home = getHomeFolder(); - File springCloudDir = (home == null ? null : new File(home, DIRECTORY_NAME)); - if (logger.isDebugEnabled() && springCloudDir != null) { - logger.debug("~/.spring-cloud directory: {}, exists: {}, isDir: {}", springCloudDir, - springCloudDir.exists(), springCloudDir.isDirectory()); - } else if (logger.isDebugEnabled()){ - logger.debug("no ~/.spring-cloud directory"); - } - if (springCloudDir != null && springCloudDir.exists() && springCloudDir.isDirectory()) { - resource = new FileSystemResource(new File(springCloudDir, path)); - source = loadPropertySource(resource, path); - } - } - return source; - } - - protected File getHomeFolder() { - String home = System.getProperty("user.home"); - if (StringUtils.hasLength(home)) { - return new File(home); - } - return null; - } - - private PropertySource loadPropertySource(Resource resource, String path) { - if (resource.exists()) { - try { - PropertySource source = new YamlPropertySourceLoader().load(path, - resource, null); - if (source != null) { - logger.info("Loaded YAML properties from: " + resource); - } - return source; - } - catch (IOException e) { - } - } - return null; - } - private String getVersion() { Package pkg = DeployerThread.class.getPackage(); return (pkg != null ? pkg.getImplementationVersion() : DEFAULT_VERSION); @@ -209,162 +112,9 @@ public class DeployerThread extends Thread { "launcher.version=" + getVersion()) .run(this.args); - final AppDeployer deployer = context.getBean(AppDeployer.class); + final Deployer deployer = context.getBean(Deployer.class); + deployer.deploy(); - ResourceLoader resourceLoader = context.getBean(DelegatingResourceLoader.class); - - DeployerProperties properties = context.getBean(DeployerProperties.class); - - ArrayList invalid = new ArrayList<>(); - // validate that items in deploy, are valid deployables - for (String toDeploy : properties.getDeploy()) { - if (!properties.getDeployables().containsKey(toDeploy)) { - invalid.add(toDeploy); - } - } - - if (!invalid.isEmpty()) { - logger.error("Error starting 'spring cloud'."+"\n\nThe following are not valid: '" + - collectionToCommaDelimitedString(invalid) + "'. Please check the name(s) and try again.\n" + - "Valid choices are: "+ collectionToCommaDelimitedString(properties.getDeployables().keySet())+".\n"); - return; - } - - ArrayList deployables = new ArrayList<>( - properties.getDeployables().values()); - OrderComparator.sort(deployables); - - logger.debug("Deployables {}", properties.getDeployables()); - - for (Deployable deployable : deployables) { - deploy(deployer, resourceLoader, deployable, properties, - context.getEnvironment()); - } - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - logger.info("\n\nShutting down ...\n"); - for (String id : DeployerThread.this.deployed.keySet()) { - logger.info("Undeploying {}", id); - deployer.undeploy(id); - } - context.close(); - } - }); - - for (Deployable deployable : deployables) { - if (shouldDeploy(deployable, properties) - && StringUtils.hasText(deployable.getMessage())) { - System.out.println("\n\n" + deployable.getName() + ": " - + deployable.getMessage() + "\n"); - } - } - - System.out.println("\n\nType Ctrl-C to quit.\n"); - - while (true) { - for (Map.Entry entry : this.deployed.entrySet()) { - String id = entry.getKey(); - DeploymentState state = entry.getValue(); - AppStatus status = deployer.status(id); - DeploymentState newState = status.getState(); - if (state != newState) { - logger.info("{} change status from {} to {}", id, state, newState); - this.deployed.put(id, newState); - } - } - try { - Thread.sleep(properties.getStatusSleepMillis()); - } - catch (InterruptedException e) { - logger.error("error sleeping", e); - } - } - } - - private String deploy(AppDeployer deployer, ResourceLoader resourceLoader, - Deployable deployable, DeployerProperties properties, - ConfigurableEnvironment environment) { - if (!shouldDeploy(deployable, properties)) { - return null; - } - - logger.debug("getting resource {} = {}", deployable.getName(), - deployable.getCoordinates()); - Resource resource = resourceLoader.getResource(deployable.getCoordinates()); - - Map appDefProps = new HashMap<>(); - appDefProps.put("server.port", String.valueOf(deployable.getPort())); - - // For each of the other deployables, add "negative" properties that apply to all - // apps when it is disabled - for (Deployable other : properties.getDeployables().values()) { - if (!shouldDeploy(other.getName(), properties)) { - appDefProps.putAll(other.getDisabled()); - } - } - for (Deployable other : properties.getDeployables().values()) { - if (shouldDeploy(other.getName(), properties)) { - appDefProps.putAll(other.getEnabled()); - } - } - Map map = extractProperties("/" + deployable.getName() + ".yml"); - for (String key : map.keySet()) { - appDefProps.put(key, map.get(key)); - } - - AppDefinition definition = new AppDefinition(deployable.getName(), appDefProps); - - Map deploymentProperties = new LinkedHashMap<>(); - deploymentProperties.put(AppDeployer.GROUP_PROPERTY_KEY, "launcher"); - deploymentProperties.putAll(deployable.getProperties()); - - AppDeploymentRequest request = new AppDeploymentRequest(definition, resource, - deploymentProperties); - - logger.debug("Deploying resource {} = {}", deployable.getName(), - deployable.getCoordinates()); - logger.debug("AppDefinition Properties: {}", appDefProps); - logger.debug("Deployment Properties: {}", deploymentProperties); - String id = deployer.deploy(request); - AppStatus appStatus = getAppStatus(deployer, id); - // TODO: stream stdout/stderr like docker-compose (with colors and prefix) - - if (deployable.isWaitUntilStarted()) { - try { - logger.info("\n\nWaiting for {} to start.\n", deployable.getName()); - - while (appStatus.getState() != DeploymentState.deployed - && appStatus.getState() != DeploymentState.failed) { - Thread.sleep(properties.getStatusSleepMillis()); - appStatus = getAppStatus(deployer, id); - logger.trace("State of {} = {}", id, appStatus.getState()); - } - } - catch (Exception e) { - logger.error("error updating status of " + id, e); - } - } - logger.info("Status of {}: {}", id, appStatus); - - return id; - } - - private boolean shouldDeploy(Deployable deployable, DeployerProperties properties) { - return shouldDeploy(deployable.getName(), properties); - } - - private boolean shouldDeploy(String name, DeployerProperties properties) { - boolean deploy = properties.getDeploy().contains(name); - logger.trace("shouldDeploy {} = {}", name, deploy); - return deploy; - } - - private AppStatus getAppStatus(AppDeployer deployer, String id) { - AppStatus appStatus = deployer.status(id); - this.deployed.put(id, appStatus.getState()); - return appStatus; } } diff --git a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/test/java/org/springframework/cloud/launcher/deployer/DeployerThreadTests.java b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/test/java/org/springframework/cloud/launcher/deployer/DeployerThreadTests.java index 14217f2..7d25070 100644 --- a/spring-cloud-launcher/spring-cloud-launcher-deployer/src/test/java/org/springframework/cloud/launcher/deployer/DeployerThreadTests.java +++ b/spring-cloud-launcher/spring-cloud-launcher-deployer/src/test/java/org/springframework/cloud/launcher/deployer/DeployerThreadTests.java @@ -16,12 +16,12 @@ package org.springframework.cloud.launcher.deployer; -import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; - import org.junit.Rule; import org.junit.Test; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; + /** * @author Spencer Gibb */