Automatically detect 'development' profile

Detect when an application is running in development (by the presence
of a build file) and automatically add a 'development' profile.

Additional detectors can be developed by implementing the
`ProfileDetector` interface and registering with the `SpringApplication`

Fixes gh-296
This commit is contained in:
Phillip Webb 2014-01-31 21:56:21 -08:00
parent 643295cc3c
commit a97bcfe3cd
8 changed files with 330 additions and 11 deletions

View File

@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ -43,6 +44,8 @@ public class SampleSimpleApplication implements CommandLineRunner {
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SampleSimpleApplication.class, args);
ConfigurableApplicationContext context = SpringApplication.run(
SampleSimpleApplication.class, args);
System.out.println(context.getEnvironment().acceptsProfiles("development"));
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2012-2014 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.boot;
import java.io.File;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* {@link ProfileDetector} that attempts to detect when the application is being developed
* and adds a 'development' profile.
*
* @author Phillip Webb
*/
public class DevelopmentProfileDetector implements ProfileDetector {
private final Log logger = LogFactory.getLog(getClass());
private static final String DEFAULT_PROFILE_NAME = "development";
private static final String EXECUTABLE_JAR_CLASS = "org.springframework.boot.loader.Launcher";
private static final String[] DEVELOPMENT_TIME_FILES = { "pom.xml", "build.gradle",
"build.xml" };
private final String profileName;
public DevelopmentProfileDetector() {
this(DEFAULT_PROFILE_NAME);
}
public DevelopmentProfileDetector(String profileName) {
Assert.notNull(profileName, "ProfileName must not be null");
this.profileName = profileName;
}
@Override
public void addDetectedProfiles(ConfigurableEnvironment environment) {
if (!isPackageAsJar() && isRunningInDevelopmentDirectory()) {
environment.addActiveProfile(this.profileName);
}
}
protected boolean isPackageAsJar() {
if (ClassUtils.isPresent(EXECUTABLE_JAR_CLASS, null)) {
this.logger.debug("Development profile not detected: "
+ "running inside executable jar");
return true;
}
String command = System.getProperty("sun.java.command");
if (StringUtils.hasLength(command) && command.toLowerCase().contains(".jar")) {
this.logger.debug("Development profile not detected: started from a jar");
return true;
}
return false;
}
private boolean isRunningInDevelopmentDirectory() {
File userDir = getUserDir();
if (userDir != null && userDir.exists()) {
for (String developementTimeFile : DEVELOPMENT_TIME_FILES) {
if (new File(userDir, developementTimeFile).exists()) {
this.logger.debug("Development profile detected: file "
+ developementTimeFile + " present");
return true;
}
}
}
return false;
}
protected File getUserDir() {
String userDir = System.getProperty("user.dir");
return (userDir == null ? null : new File(userDir));
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2012-2014 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.boot;
import org.springframework.core.env.ConfigurableEnvironment;
/**
* Strategy interface that can be used to detect active profiles.
*
* @author Phillip Webb
*/
public interface ProfileDetector {
/**
* Add any detected profiles to the specified environment.
* @param environment the environment
*/
void addDetectedProfiles(ConfigurableEnvironment environment);
}

View File

@ -158,6 +158,9 @@ public class SpringApplication {
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final List<ProfileDetector> DEFAULT_PROFILE_DETECTORS = Arrays
.<ProfileDetector> asList(new DevelopmentProfileDetector());
private final Log log = LogFactory.getLog(getClass());
private final Set<Object> sources = new LinkedHashSet<Object>();
@ -190,6 +193,8 @@ public class SpringApplication {
private Set<String> profiles = new HashSet<String>();
private List<ProfileDetector> profileDetectors = DEFAULT_PROFILE_DETECTORS;
/**
* Crate a new {@link SpringApplication} instance. The application context will load
* beans from the specified sources (see {@link SpringApplication class-level}
@ -308,9 +313,7 @@ public class SpringApplication {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
addPropertySources(environment, args);
for (String profile : this.profiles) {
environment.addActiveProfile(profile);
}
setupProfiles(environment);
// Notify listeners of the environment creation
multicaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this,
@ -461,6 +464,15 @@ public class SpringApplication {
}
}
protected void setupProfiles(ConfigurableEnvironment environment) {
for (String profile : this.profiles) {
environment.addActiveProfile(profile);
}
for (ProfileDetector profileDetector : this.profileDetectors) {
profileDetector.addDetectedProfiles(environment);
}
}
/**
* Print a simple banner message to the console. Subclasses can override this method
* to provide additional or alternative banners.
@ -740,11 +752,23 @@ public class SpringApplication {
/**
* Set additional profile values to use (on top of those set in system or command line
* properties).
*
* @param profiles the additional profiles to set
*/
public void setAdditionalProfiles(Collection<String> profiles) {
this.profiles = new LinkedHashSet<String>(profiles);
public void setAdditionalProfiles(String... profiles) {
this.profiles = new LinkedHashSet<String>(Arrays.asList(profiles));
}
/**
* Sets the {@link ProfileDetector}s that will be used to attempt to detect
* {@link Environment#acceptsProfiles(String...) active profiles}.
* {@link DevelopmentProfileDetector} is used.
* @param profileDetectors the profile detectors
* @see #setAdditionalProfiles(String...)
*/
public void setProfileDetectors(ProfileDetector... profileDetectors) {
Assert.notNull(profileDetectors, "Profile detectors must not be null");
this.profileDetectors = new ArrayList<ProfileDetector>(
Arrays.asList(profileDetectors));
}
/**

View File

@ -28,6 +28,7 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.boot.ProfileDetector;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.initializer.ParentContextApplicationContextInitializer;
import org.springframework.boot.context.initializer.ServletContextApplicationContextInitializer;
@ -389,14 +390,26 @@ public class SpringApplicationBuilder {
*/
public SpringApplicationBuilder profiles(String... profiles) {
this.additionalProfiles.addAll(Arrays.asList(profiles));
this.application.setAdditionalProfiles(this.additionalProfiles);
this.application.setAdditionalProfiles(this.additionalProfiles
.toArray(new String[this.additionalProfiles.size()]));
return this;
}
/**
* Set the {@link ProfileDetector}s that should be used for this app.
* @param profileDetectors the profile detectors to set
* @return the current builder
*/
public SpringApplicationBuilder profileDetectors(ProfileDetector... profileDetectors) {
this.application.setProfileDetectors(profileDetectors);
return this;
}
private SpringApplicationBuilder additionalProfiles(
Collection<String> additionalProfiles) {
this.additionalProfiles = new LinkedHashSet<String>(additionalProfiles);
this.application.setAdditionalProfiles(additionalProfiles);
this.application.setAdditionalProfiles(additionalProfiles
.toArray(new String[additionalProfiles.size()]));
return this;
}

View File

@ -61,8 +61,7 @@ public class SpringApplicationContextLoader extends AbstractContextLoader {
SpringApplication application = new SpringApplication();
application.setSources(getSources(mergedConfig));
if (!ObjectUtils.isEmpty(mergedConfig.getActiveProfiles())) {
application.setAdditionalProfiles(Arrays.asList(mergedConfig
.getActiveProfiles()));
application.setAdditionalProfiles(mergedConfig.getActiveProfiles());
}
application.setDefaultProperties(getArgs(mergedConfig));
List<ApplicationContextInitializer<?>> initializers = getInitializers(

View File

@ -0,0 +1,128 @@
/*
* Copyright 2012-2014 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.boot;
import java.io.File;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.core.env.ConfigurableEnvironment;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link DevelopmentProfileDetector}.
*
* @author Phillip Webb
*/
public class DevelopmentProfileDetectorTests {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
private TestDevelopmentProfileDetector detector;
private ConfigurableEnvironment environment;
@Before
public void setup() {
this.detector = new TestDevelopmentProfileDetector();
this.environment = mock(ConfigurableEnvironment.class);
}
@Test
public void notFound() {
this.detector.addDetectedProfiles(this.environment);
verifyZeroInteractions(this.environment);
}
@Test
public void foundDueToMavenBuild() throws Exception {
this.temporaryFolder.newFile("pom.xml").createNewFile();
this.detector.addDetectedProfiles(this.environment);
verify(this.environment).addActiveProfile("development");
}
@Test
public void foundDueToGradleBuild() throws Exception {
this.temporaryFolder.newFile("build.gradle").createNewFile();
this.detector.addDetectedProfiles(this.environment);
verify(this.environment).addActiveProfile("development");
}
@Test
public void foundDueToAntBuild() throws Exception {
this.temporaryFolder.newFile("build.xml").createNewFile();
this.detector.addDetectedProfiles(this.environment);
verify(this.environment).addActiveProfile("development");
}
@Test
public void differentProfileName() throws Exception {
this.detector = new TestDevelopmentProfileDetector("different");
this.temporaryFolder.newFile("pom.xml").createNewFile();
this.detector.addDetectedProfiles(this.environment);
verify(this.environment).addActiveProfile("different");
verifyNoMoreInteractions(this.environment);
}
@Test
public void notFoundWhenStartedFromJar() throws Exception {
System.setProperty("sun.java.command", "something.jar");
this.temporaryFolder.newFile("pom.xml").createNewFile();
this.detector.setSkipPackageAsJar(false);
this.detector.addDetectedProfiles(this.environment);
verifyZeroInteractions(this.environment);
}
private class TestDevelopmentProfileDetector extends DevelopmentProfileDetector {
private boolean skipPackageAsJar = true;
public TestDevelopmentProfileDetector() {
super();
}
public TestDevelopmentProfileDetector(String profileName) {
super(profileName);
}
public void setSkipPackageAsJar(boolean skipPackageAsJar) {
this.skipPackageAsJar = skipPackageAsJar;
}
@Override
protected boolean isPackageAsJar() {
if (this.skipPackageAsJar) {
// Unfortunately surefire uses a jar so we need to stub this out
return false;
}
return super.isPackageAsJar();
}
@Override
protected File getUserDir() {
return DevelopmentProfileDetectorTests.this.temporaryFolder.getRoot();
}
}
}

View File

@ -409,6 +409,29 @@ public class SpringApplicationTests {
assertThat(System.getProperty("java.awt.headless"), equalTo("false"));
}
@Test
public void setAdditionalProfiles() throws Exception {
TestSpringApplication application = new TestSpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
application.setAdditionalProfiles("mine");
this.context = application.run();
assertThat(this.context.getEnvironment().acceptsProfiles("mine"), equalTo(true));
}
@Test
public void setProfileDetectors() throws Exception {
TestSpringApplication application = new TestSpringApplication(ExampleConfig.class);
application.setWebEnvironment(false);
application.setProfileDetectors(new ProfileDetector() {
@Override
public void addDetectedProfiles(ConfigurableEnvironment environment) {
environment.addActiveProfile("mine");
}
});
this.context = application.run();
assertThat(this.context.getEnvironment().acceptsProfiles("mine"), equalTo(true));
}
private boolean hasPropertySource(ConfigurableEnvironment environment,
Class<?> propertySourceClass, String name) {
for (PropertySource<?> source : environment.getPropertySources()) {