Spring Configuration and Profiles

Spring supports profile specific application properties via a file naming convention:

  • Profile specific application properties outside your jar (application-{profile}.properties or application-{profile}.yml)
  • Profile specific application properties packaged in your jar (application-{profile}.properties or application-{profile}.yml)

If you’re using YAML, then you can also use profile documents inside the YAML file. Let’s take a closer look using the CommandLineRunner from last week’s post Externalized Configuration in Spring

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Value("${myApp.name}")
    private String name;
    @Value("${myApp.primary}")
    private String primary = "foo";
    @Value("${myApp.secondary}")
    private String secondary = "foo";

    private static final Logger log = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) throws Exception {
        final SpringApplication app = new SpringApplicationBuilder(Application.class)
            .properties("spring.config.name=myApp")
            .build();
        app.run();
    }

    public void run(String ... strings)
        throws Exception
    {
        log.warn("name\t {}", name);
        log.warn("primary\t {}", primary);
        log.warn("secondary\t {}", secondary);
    }
}

A YAML file is actually a sequence of documents separated by --- lines, and Spring parses each document separately to a flattened map. If a YAML document contains a spring.profiles key, then that value controls whether that document is included in the final merge.

Let’s take a look at an example. Here is the file packaged in my jar:

myApp:
  name:         Package Default
  primary:      Package Default
  secondary:    Package Default

---
spring.profiles: one

myApp:
  name:         Package 1

---
spring.profiles: two

myApp:
  name:         Package 22
  primary:      Package 22

---
spring.profiles: three

myApp:
  primary:      Package 333
  secondary:    Package 333

With no active profile, we get the values from the first document.

$ java -jar target/application-config-0.1.0.jar
WARN  [main]  name       Package Default
WARN  [main]  primary    Package Default
WARN  [main]  secondary  Package Default

Set one as the active profile, to merge the first document with the document containing spring.profiles: one:

$ (export SPRING_PROFILES_ACTIVE=one ; java -jar target/application-config-0.1.0.jar)
WARN  [main]  name       Package 1
WARN  [main]  primary    Package Default
WARN  [main]  secondary  Package Default

Set two,three as the active profiles, to merge the first document with the documents containing spring.profiles: two, spring.profiles: three:

$ (export SPRING_PROFILES_ACTIVE=two,three ; java -jar target/application-config-0.1.0.jar)
WARN  [main]  name       Package 22
WARN  [main]  primary    Package 333
WARN  [main]  secondary  Package 333

And finally, add a local myApp.yml that sets the active profile. The merge order is:

  1. packaged default document
  2. local default document
  3. active profile document
$ cat myApp.yml
spring.profiles.active: one

myApp:
    name:       Local
    secondary:  Local
$ java -jar target/application-config-0.1.0.jar
WARN  [main]  name       Package 1
WARN  [main]  primary    Package Default
WARN  [main]  secondary  Local

Externalized Configuration in Spring

Spring comes with excellent support for externalized configuration. This allows you to have sensible defaults checked into your source code repository, while providing a plethora of options for setting properties that shouldn’t be checked in (account credentials) or can’t be checked in (specification of active profiles):

  • OS environment variables
  • Profile specific application properties outside your jar (application-{profile}.properties or application-{profile}.yml)
  • Profile specific application properties packaged in your jar (application-{profile}.properties or application-{profile}.yml)
  • Application properties outside your jar (application.properties or application.yml)
  • Application properties packaged in your jar (application.properties or application.yml)

But if you’re deploying to an application server, then there is a little tweak that you’ll need to keep in your back pocket - changing the spring.config.name from application to something else in code. This will let you add a centralized configuration directory to your application server classpath for all your deployed web applications, where setting spring.config.name by environment variable, command line options or JNDI don’t work).

Personally, I like using the org.springframework.boot.builder.SpringApplicationBuilder to change the spring.config.name to myApp:

app = new SpringApplicationBuilder(Application.class)
    .properties("spring.config.name=myApp")
    .build()
    .run();

But, it can can also be done with the standard org.springframework.boot.SpringConfiguration.

app = new SpringApplication(Application.class);
Properties props = new Properties();
props.setProperty("spring.config.name","myApp");
app.setDefaultProperties(props);
app.run();

It is also possible to externalize the configuration with org.springframework.context.annotation.PropertySource, but this forgoes some application properties magic:

  • PropertySource does not support YAML
  • PropertySource does not have profile support
  • You override properties with application properties while you define all properties with PropertySource (application properties uses the value from the file with the highest priority, while PropertySource uses the file with the highest priority).
  • Maven plugin adds the project.build.directory to the application properties search list (spring.config.location), where developers can easily specify values while keeping them out of source control.

Goodbye Harold

Appointment TV may be dead, but Person of Interest was the next best thing - a show worthy of an HD recording. Harold Finch, the “Man in the Suit” and the Machine finished their five year run in style. Harold has started a new life with Grace, John gave his life to save the world and the Machine was re-born. In a world without Samaritan, who knows what the Machine will do.

CBS has given me back 22 hours a year. I’d rather spend those 22 hours with the Machine.

This One's for Pat

Two years ago I was cautiously optimistic. The Seahawks had a great defense, the Broncos had an great offense and I was looking forward to a great Super Bowl. That lead to a beat down as painful to watch as the Super Bowl XXIV thrashing.

This year I was guardedly pessimistic. All season long I had been saying that the Broncos didn’t need Peyton Manning at the height of his powers - they just needed Peyton to play at a replacement level and the defense could do the rest. Unfortunately, Peyton wasn’t able to play at a replacement level. The result was a game in which Denver never trailed, but kept me on the edge of my seat most of the game.

After obsessively reviewing the post game analysis, I subscribe to the theory that the Carolina coaching staff let their team down while the Denver staff understood how to the Broncos could/would win. With the Broncos unlikely to put together a long TD drive, it was imperative that the Panthers manage field position and not give the Broncos a short field. Instead the Panthers gave up a defensive touchdown on their second position and the Broncos were on their way.

I really think Carolina’s coaches didn’t appreciate the kind of game they were in, and it led to handing the Broncos their best chance to win the game. If Rivera and Co. had approached it with the sort of thinking that goes along with trying to win a game 16-13 or 13-9, all sorts of things might have been different. In particular, if Carolina approaches the game with the 1st principal being giving the Broncos zero chance to get a turnover on a very short field, the game might turn out different.

Theme and Site Update

Website viewers may notice a couple of site changes.

  1. My tag cloud is now shown at the bottom of the front page.
  2. The main menu links to “Home”, “Technology Category”, “Sports Category”, and “Java Tag”.

I thought that my front page ended a bit abruptly after removing the ability to page through the entire weblog. The tag cloud provides some guidance in exploring my back catalog and is enabled by the latest update to my hexo-theme-landscape work branch.

It’s feels odd, not having a link to my Java Tutorials. Getting my tutorial page on DMOZ was a big step 12 years ago. It was a strong effort back then, but I let it wither on the vine. Now it is time to move on.

CloudFront Checkpoint/Hexo Theme Update

CloudFront is looking good. My added bandwidth cost is about $.20 a month - a very cost effective way to add SSL to my S3 hosted weblog. There is one financial gotcha - the first 1000 URL invalidations are free, but it is $.005 per invalidation after that. I blew through my free invalidations when I added SSL and regenerated every page last month. I think 1000 free invalidations a month would be ample for most people, but not for me.

I have almost 900 total posts. That means I invalidate about 90 URLs with every post (10 posts per page). Or about 10 posts a month, unless I’m willing to pay. I don’t think anyone really wants to page through my entire weblog. And I already updated my archive (2015), category (technology), and tag (java) pages to present a single page of titles last year. So I’ve updated the hexo landscape theme to only generate the first index page.

My fork of hexo-theme-landscape is on github. Take the First Step uses the work branch. I cherry-pick commits from work to master so that I can generate clean pull requests from master.

Cucumber-java and TestNG

With the addition of my jersey client tests, I thought I was ready to add cucumber support to my project. I was wrong. The documentation on using Cucumber and TestNG is a bit sparse and I was getting a bit flustered on where to put the feature definition file.

It turns out that I was making a mountain out of a mole hill. If you put the feature file in the wrong place, then cucumber will tell you where it was looking.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ mvn clean test -q -Dskip=client,browser

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running TestSuite
Configuring TestNG with: TestNG652Configurator
No features found at [classpath:com/ideoplex/tutorial]

0 Scenarios
0 Steps
0m0.000s

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.806 sec - in TestSuite

Results :

Tests run: 4, Failures: 0, Errors: 0, Skipped: 0

Read More

Jersey Client with Gson

It doesn’t make sense to have full test coverage of the jersey-gson project in selenium. Let’s fill in the gaps with some jersey-client unit tests.

First, we need a UserMap deserializer to mirror the existing serializer. The existing serializer sends the UserMap as an object whose value is an Array of rows (this simplifies interaction with the DataTables front end). We deserialize the UserMap by iterating through the array, turning each row into a User object, and adding each User object to the Map.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
public class UserMapUnmarshall implements JsonDeserializer<UserMap> {
private static Gson gson = new Gson();

@Override
public UserMap deserialize(JsonElement mapFormat, Type typeOfSrc, JsonDeserializationContext context) {
Iterator<JsonElement> iterate = mapFormat.getAsJsonObject()
.get("data")
.getAsJsonArray()
.iterator();

UserMap users = new UserMap();
while( iterate.hasNext() ) {
JsonObject user = ((JsonElement) iterate.next()).getAsJsonObject();
String email = user.get("email").getAsString();
User add = new User();
add.setEmail( email );
add.setSurname( user.get("surname").getAsString() );
add.setGivenName( user.get("givenName").getAsString() );
users.put( email, add );
}

return users;
}
}

Read More