Fork me on GitHub

Environment Tutorial

This tutorial covers configuring WoOF within different environments. It will look at configuring individual properties and then discuss profiles for easier grouping of properties.

The example used in this tutorial is the following simple server to provide an environment specific response.

EnvironmentHttpServer configuration.

Tutorial Source

Inject Property

The following is the procedure implementation:

public class EnvironmentLogic {

	public void service(@Property("name") String value, ServerHttpConnection connection) throws IOException {
		connection.getResponse().getEntityWriter().write(value);
	}
}

The @Property annotation flags for the named property to be injected. Names of properties are qualified by the name of procedure. This enables simple names for easy configuration.

As per the code, the procedure will respond with the value of the property.

Configuring Properties

The default properties are configured in the application.properties file (at root of class path). The content of this tutorial's file is the following:

service.procedure.name=DEFAULT

As mentioned, the property name is prefixed with the procedure name.

The following unit test shows returning the default property:

	@RegisterExtension
	public final MockWoofServerExtension server = new MockWoofServerExtension();

	@Test
	public void applicationProperties() {
		this.server.send(MockWoofServer.mockRequest("/")).assertResponse(200, "DEFAULT");
	}

JUnit 4 example:

	@Rule
	public final MockWoofServerRule server = new MockWoofServerRule(this);

	@Test
	public void applicationProperties() {
		this.server.send(MockWoofServer.mockRequest("/")).assertResponse(200, "DEFAULT");
	}

Testing

Tests may override the property:

	@RegisterExtension
	public final MockWoofServerExtension server = new MockWoofServerExtension().property("service.procedure.name",
			"PROPERTY");

	@Test
	public void applicationProperties() {
		this.server.send(MockWoofServer.mockRequest("/")).assertResponse(200, "PROPERTY");
	}

JUnit 4 example:

	@Rule
	public final MockWoofServerRule server = new MockWoofServerRule(this).property("service.procedure.name",
			"PROPERTY");

	@Test
	public void applicationProperties() {
		this.server.send(MockWoofServer.mockRequest("/")).assertResponse(200, "PROPERTY");
	}

Note that externally configured properties, detailed in the rest of this tutorial, are not loaded by the mock server. This avoids false positive tests that may fail due to environment differences.

System

When running the application, system properties can be used to override the properties:

	@Order(1)
	@RegisterExtension
	public final SystemPropertiesExtension systemProperties = new SystemPropertiesExtension()
			.property("application.service.procedure.name", "SYSTEM");

	@Order(2)
	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@Order(3)
	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void systemProperty() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "SYSTEM", EntityUtils.toString(response.getEntity()));
	}

JUnit 4 example:

	private final SystemPropertiesRule systemProperties = new SystemPropertiesRule()
			.property("application.service.procedure.name", "SYSTEM");

	private final OfficeFloorRule officeFloor = new OfficeFloorRule(this);

	private final HttpClientRule client = new HttpClientRule();

	@Rule
	public final RuleChain order = RuleChain.outerRule(this.systemProperties).around(this.officeFloor)
			.around(this.client);

	@Test
	public void systemProperty() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "SYSTEM", EntityUtils.toString(response.getEntity()));
	}

Environment

For environments (such as cloud), properties may be overridden by environment variables. To distinguish the environment variables, notice the prefix on the name.

	@Order(1)
	@RegisterExtension
	public final EnvironmentExtension environment = new EnvironmentExtension()
			.property("OFFICEFLOOR.application.service.procedure.name", "ENVIRONMENT");

	@Order(2)
	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@Order(3)
	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void environment() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "ENVIRONMENT", EntityUtils.toString(response.getEntity()));
	}

JUnit 4 example:

	private final EnvironmentRule environment = new EnvironmentRule()
			.property("OFFICEFLOOR.application.service.procedure.name", "ENVIRONMENT");

	private final OfficeFloorRule officeFloor = new OfficeFloorRule(this);

	private final HttpClientRule client = new HttpClientRule();

	@Rule
	public final RuleChain order = RuleChain.outerRule(this.environment).around(this.officeFloor).around(this.client);

	@Test
	public void environment() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "ENVIRONMENT", EntityUtils.toString(response.getEntity()));
	}

User Home

There are times when information is sensitive and can't be made available in the environment or in cloud configuration. In these circumstances, the configuration files can reside in the process's user home directory. This ensures the sensitive information is only on the servers requiring the information and are accessed only by the user requiring it. The following demonstrates overriding with the user properties:

	@Order(1)
	@RegisterExtension
	public final SystemPropertiesExtension systemProperties = new SystemPropertiesExtension().property("user.home",
			new File("./target/test-classes").getAbsolutePath());

	@Order(2)
	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@Order(3)
	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void userHome() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "USER", EntityUtils.toString(response.getEntity()));
	}

JUnit 4 example:

	private final SystemPropertiesRule systemProperties = new SystemPropertiesRule().property("user.home",
			new File("./target/test-classes").getAbsolutePath());

	private final OfficeFloorRule officeFloor = new OfficeFloorRule(this);

	private final HttpClientRule client = new HttpClientRule();

	@Rule
	public final RuleChain order = RuleChain.outerRule(this.systemProperties).around(this.officeFloor)
			.around(this.client);

	@Test
	public void userHome() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "USER", EntityUtils.toString(response.getEntity()));
	}

The property comes from the [user's home directory]/.config/officefloor/application.properties, which for this tutorial contains:

service.procedure.name=USER

Profiles

Configuring property overrides can be a lot of external configuration to the application. This can create a lot of overheads in keeping the externally configured properties in line with the ever evolving application.

Profiles provide means to activate an internal set of properties for the application.

Testing

The following test activates an additional profile:

	@RegisterExtension
	public final MockWoofServerExtension server = new MockWoofServerExtension().profile("mock");

	@Test
	public void applicationProperties() {
		this.server.send(MockWoofServer.mockRequest("/")).assertResponse(200, "MOCK");
	}

JUnit 4 example:

	@Rule
	public final MockWoofServerRule server = new MockWoofServerRule(this).profile("mock");

	@Test
	public void applicationProperties() {
		this.server.send(MockWoofServer.mockRequest("/")).assertResponse(200, "MOCK");
	}

This test will load the default properties from application.properties. It will then override the properties with the set of properties from application-mock.properties. Notice the hyphen and profile name. This naming convention identifies the file containing the properties for a profile. For this tutorial the file contains:

service.procedure.name=MOCK

As the configured profile takes precedence, the profile's property is used.

Note the mock server will always activate the test profile for test specific properties.

System

Running applications can also benefit from profiles. The profile may be activated by a system property:

	@Order(1)
	@RegisterExtension
	public final SystemPropertiesExtension systemProperties = new SystemPropertiesExtension()
			.property(WoOF.DEFAULT_OFFICE_PROFILES, "system");

	@Order(2)
	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@Order(3)
	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void systemProfile() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "SYSTEM", EntityUtils.toString(response.getEntity()));
	}

JUnit 4 example:

	private final SystemPropertiesRule systemProperties = new SystemPropertiesRule()
			.property(WoOF.DEFAULT_OFFICE_PROFILES, "system");

	private final OfficeFloorRule officeFloor = new OfficeFloorRule(this);

	private final HttpClientRule client = new HttpClientRule();

	@Rule
	public final RuleChain order = RuleChain.outerRule(this.systemProperties).around(this.officeFloor)
			.around(this.client);

	@Test
	public void systemProfile() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "SYSTEM", EntityUtils.toString(response.getEntity()));
	}

The contents of the respective profile properties file is:

service.procedure.name=SYSTEM

Environment

Environment variables may also activate a profile:

	@Order(1)
	@RegisterExtension
	public final EnvironmentExtension environment = new EnvironmentExtension()
			.property("OFFICEFLOOR.application.profiles", "environment");

	@Order(2)
	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@Order(3)
	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void environment() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "ENVIRONMENT", EntityUtils.toString(response.getEntity()));
	}

JUnit 4 example:

	private final EnvironmentRule environment = new EnvironmentRule().property("OFFICEFLOOR.application.profiles",
			"environment");

	private final OfficeFloorRule officeFloor = new OfficeFloorRule(this);

	private final HttpClientRule client = new HttpClientRule();

	@Rule
	public final RuleChain order = RuleChain.outerRule(this.environment).around(this.officeFloor).around(this.client);

	@Test
	public void environment() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "ENVIRONMENT", EntityUtils.toString(response.getEntity()));
	}

The contents of the respective profile properties file is:

service.procedure.name=ENVIRONMENT

User Home

For environments with pre-built images (e.g. docker), it is possible to configure profile property files in the user's home directory. These profiles can then be activated by the environment. For example:

	@Order(1)
	@RegisterExtension
	public final SystemPropertiesExtension systemProperties = new SystemPropertiesExtension()
			.property("user.home", new File("./target/test-classes").getAbsolutePath())
			.property(WoOF.DEFAULT_OFFICE_PROFILES, "user");

	@Order(2)
	@RegisterExtension
	public final OfficeFloorExtension officeFloor = new OfficeFloorExtension();

	@Order(3)
	@RegisterExtension
	public final HttpClientExtension client = new HttpClientExtension();

	@Test
	public void userHome() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "USER_PROFILE", EntityUtils.toString(response.getEntity()));
	}

JUnit 4 example:

	private final SystemPropertiesRule systemProperties = new SystemPropertiesRule()
			.property("user.home", new File("./target/test-classes").getAbsolutePath())
			.property(WoOF.DEFAULT_OFFICE_PROFILES, "user");

	private final OfficeFloorRule officeFloor = new OfficeFloorRule(this);

	private final HttpClientRule client = new HttpClientRule();

	@Rule
	public final RuleChain order = RuleChain.outerRule(this.systemProperties).around(this.officeFloor)
			.around(this.client);

	@Test
	public void userHome() throws IOException {
		HttpResponse response = this.client.execute(new HttpGet("http://localhost:7878"));
		assertEquals("Should be successful", 200, response.getStatusLine().getStatusCode());
		assertEquals("Incorrect property override", "USER_PROFILE", EntityUtils.toString(response.getEntity()));
	}

Again, the properties files are located in [user's home directory]/.config/officefloor. They following the profile naming convention. For the above example this would be application-user.properties and contains:

service.procedure.name=USER_PROFILE

Next

The next tutorial looks at deploying OfficeFloor applications.