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.
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.
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"); }
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.
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())); }
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())); }
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
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.
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.
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 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
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
The next tutorial looks at deploying OfficeFloor applications.