Loading...

April 21, 2017

Gradle Goodness: Using Incremental Task Action

Gradle has incremental build support to speed up our builds. This means Gradle checks input and output for a task and if something changed the task is executed, otherwise the task is skipped. In previous posts we learned how to add incremental build support to our tasks with annotations and inputs and outputs property of a task. When we have a task that has an output file for an input file, like with transformations, we can have a more efficient task using an incremental task action. With an incremental task action we have extra information on the files that are handled by the task. We can have different actions based on if an input file is out of date or removed. This way we can handle only the input files that have changed or removed with incremental builds, instead of all the input files.

To create an incremental task action we must have a task action method (annotated with @TaskAction) that has a single argument of type IncrementalTaskInputs. The IncrementalTaskInputs class has the method outOfDate and removed. These methods take an action, that can be implemented with a closure, with an instance of InputFileDetails as argument. We can get to the input file via this instance and use that for our task logic. When an input file is out of date, because the file contents has changed or the output file has been removed, the action we defined for the outOfDate method is invoked. If the input file is removed the action for the method removed is invoked.

// Create task to convert text to HTML.
task convert(type: HtmlConverter) {
    sourceDir = file('src/docs/text')
    outputDir = file("${buildDir}/html")
}

import groovy.xml.MarkupBuilder
import groovy.transform.CompileStatic
import groovy.transform.CompileDynamic

/**
 * Simple Gradle task that takes an text
 * input file and converts it to a HTML file.
 */
@CompileStatic
class HtmlConverter extends DefaultTask {

    @InputDirectory
    @PathSensitive(PathSensitivity.RELATIVE)
    File sourceDir

    @OutputDirectory
    File outputDir

    /**
     * Task action that will check if a source file is out of date
     * or removed. If out of date the source file is converted
     * to HTML. If source file is removed the generated 
     * HTML file is removed.
     *
     * @param inputs Used for incremental task action.
     */
    @TaskAction
    void convert(IncrementalTaskInputs inputs) {
        // If the user for example used --rerun-tasks
        // this task is not incremental. Only
        // inputs.outOfDate is executed, so we must first 
        // remove all output files.
        if (!inputs.incremental) {
            project.delete(outputDir.listFiles())
        }

        // Input file has changed, so we convert it.
        inputs.outOfDate { InputFileDetails outOfDate ->
            convertFile(outOfDate.file)
        }

        // Input file is removed, so we remove the
        // output file that was created for the input file.
        inputs.removed { InputFileDetails removed ->
            removeOutputFile(removed.file)
        }
    }

    /**
     * Convert text file to HTML.
     *
     * @param file Text file to convert to HTML.
     */
    private void convertFile(final File file) {
        logger.lifecycle 'Convert file {}', file.name
        final lines = file.readLines()
        final outputWriter = new FileWriter(new File(outputDir, outputFilename(file)))
        writeHtml(lines, outputWriter)
    }

    /**
     * Use first line as title for HTML, rest is body.
     *
     * @param lines Lines to transform to HTML.
     * @param writer Writer to write HTML to.
     */
    @CompileDynamic
    private void writeHtml(final List lines, final Writer writer) {
        final html = new MarkupBuilder(writer)
        html.html {
            head {
                title lines[0]
            }
            body {
                lines[2..-1].each { line ->
                    p line
                }
            }
        }
    }

    /**
     * Remove the output file thas was created for the
     * given input file.
     *
     * @param file Input file to remove output file for.
     */
    private void removeOutputFile(final File file) {
        logger.lifecycle 'Remove HTML for file {}', file.name
        new File(outputDir, outputFilename(file)).delete()
    }

    /**
     * Determine HTML output filename based on base of input filename.
     *
     * @param file Used to create HTML output file name.
     */
    private String outputFilename(final File file) {
        file.name[0..file.name.lastIndexOf('.')] + 'html'
    }

}

In our project we have 3 source files in the directory src/docs/text: sample1.txt, sample2.txt and hello.txt. We run the convert task for the first time and we see all input files are processed:

$ gradle convert
:convert
Convert file hello.txt
Convert file sample1.txt
Convert file sample2.txt

BUILD SUCCESSFUL

Total time: 0.948 secs

Next we change hello.txt and when re-run the task we see only our changed file is processed. If we rename it after the change we can see the hello.html is removed and the new file is processed:

$ echo "Gradle rocks" >> src/docs/text/hello.txt
$ gradle convert
:convert
Convert file hello.txt

BUILD SUCCESSFUL

Total time: 0.793 secs
$ mv src/docs/text/hello.txt src/docs/text/sample.txt
$ gradle convert
:convert
Convert file sample.txt
Remove HTML for file hello.txt

BUILD SUCCESSFUL

Total time: 0.76 secs
$

Written with Gradle 3.5.

April 19, 2017

Gradle Goodness: Change Local Build Cache Directory

Gradle 3.5 introduced the build cache. With the build cache we can reuse task output from builds that can come from different computers. We can also use the build cache feature for our local builds. By default the directory to store the cache is located in the Gradle user home directory on our computer (USER_HOME/.gradle/caches/build-cache-1). We can change the directory for the local cache via settings.gradle of our Gradle project. For example we could configure a directory in our project file structure to be the build cache directory. Then it is easy to clean the cache, because it is a directory not shared by other Gradle projects. With the default directory location in the Gradle user home directory the caches of all Gradle projects we run on our computer are stored in a single directory. And the cache doesn't shrink and will only grow we might want to have more control of where the cache of a single Gradle project is stored. This way we can easily clean the cache, because all files of a project are stored in the directory for that project.

In the following example settings.gradle file we configure our build cache directory to be the directory build-cache in the root directory of our project where we store our settings.gradle file:

// File: settings.gradle
buildCache {
    local {
        // Set local build cache directory.
        directory = "${settingsDir}/build-cache"
    }
}

Written with Gradle 3.5.

April 14, 2017

Gradle Goodness: Enable Build Cache For All Builds

Gradle 3.5 introduced the build cache. With the build cache we can share task output between builds on different computers. For example the build output from a continuous integration server can be used on a developer's computer. To use the build cache feature we use the command-line option --build-cache. Instead of using the command-line option --build-cache we can set the Gradle property org.gradle.caching with the value true in the file gradle.properties of our project. To set this property for all our projects we set the property in the gradle.properties file in the Gradle home directory, which is usually at USER_HOME/.gradle/gradle.properties.

In the following example we set the property org.gradle.caching in ~/.gradle/gradle.properties:

# File: ~/.gradle/gradle.properties
org.gradle.caching=true

If we want to disable the build cache feature set via the global property we can use the command-line option --no-build-cache to disable the build cache for a particular build.

Written with Gradle 3.5.

April 12, 2017

Spring Sweets: Hiding Sensitive Environment Or Configuration Values From Actuator Endpoints

We can use Spring Boot Actuator to add endpoints to our application that can expose information about our application. For example we can request the /env endpoint to see which Spring environment properties are available. Or use /configprops to see the values of properties defined using @ConfigurationProperties. Sensitive information like passwords and keys are replaced with ******. Spring Boot Actuator has a list of properties that have sensitive information and therefore should be replaced with ******. The default list of keys that have their value hidden is defined as password,secret,key,token,.*credentials.*,vcap_services. A value is either what the property name ends with or a regular expression. We can define our own list of property names from which the values should be hidden or sanitized and replaced with ******. We define the key we want to be hidden using the application properties endpoints.env.keys-to-sanatize and endpoints.configprops.keys-to-sanatize.

In the following example Spring application YAML configuration we define new values for keys we want to be sanitized. Properties in our Spring environment that end with username or password should be sanatized. For properties set via @ConfigurationProperties we want to hide values for keys that end with port and key:

# File: src/main/resources/application.yml
endpoints:
  env:
    # Hide properties that end with password and username:
    keys-to-sanitize: password,username
  configprops:
    # Also hide port and key values from the output:
    keys-to-sanitize: port,key
---
# Extra properties will be exposed
# via /env endpoint.
sample:
  username: test
  password: test

When we request the /env we see in the output that values of properties that end with username and password are hidden:

...
    "applicationConfig: [classpath:/application.yml]": {
        ...
        "sample.password": "******",
        "sample.username": "******"
    },
...

When we request the /configprops we see in the output that for example key and port properties are sanitized:

...
    "spring.metrics.export-org.springframework.boot.actuate.metrics.export.MetricExportProperties": {
        "prefix": "spring.metrics.export",
        "properties": {
            ...
            "redis": {
                "key": "******",
                "prefix": "spring.metrics.application.f2325e314fc8223e6bb8ee6ddebbbd79"
            },
            "statsd": {
                "host": null,
                "port": "******",
                "prefix": null
            }
        }
    },
...

Written with Spring Boot 1.5.2.RELEASE.

April 11, 2017

Spocklight: Set Timeout On Specification Methods

When we write a feature method in our Spock specification to test our class we might run into long running methods that are invoked. We can specify a maximum time we want to wait for a method. If the time spent by the method is more than the maximum time our feature method must fail. Spock has the @Timeout annotation to define this. We can apply the annotation to our specification or to feature methods in the specification. We specify the timeout value as argument for the @Timeout annotation. Seconds are the default time unit that is used. If we want to specify a different time unit we can use the annotation argument unit and use constants from java.util.concurrent.TimeUnit to set a value.

In the following example specification we set a general timeout of 1 second for the whole specification. For two methods we override this default timeout with their own value and unit:

package mrhaki.spock

@Grab('org.spockframework:spock-core:1.0-groovy-2.4')
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Timeout

import static java.util.concurrent.TimeUnit.MILLISECONDS

// Set a timeout for all feature methods.
// If a feature method doesn't return in 1 second
// the method fails.
@Timeout(1)
class SampleSpec extends Specification {

    @Subject
    private final Sample sample = new Sample()

    // Check that method will return within 1 second.
    void 'timeout will not happen'() {
        expect:
        sample.run(500) == 'Awake after 500 ms.'
    }

    // Method will fail, because it doesn't return in 1 second.
    void 'method under test should return in 1 second'() {
        expect:
        sample.run(1500) == 'Awake after 1500 ms.'
    }

    // We can change the timeout value and 
    // the unit. The unit type is 
    // java.util.concurrent.TimeUnit.
    @Timeout(value = 200, unit = MILLISECONDS)
    void 'method under test should return in 200 ms'() {
        expect:
        sample.run(100) == 'Awake after 100 ms.'
    }

    // Method will fail.
    @Timeout(value = 100, unit = MILLISECONDS)
    void 'method under test should return in 100 ms'() {
        expect:
        sample.run(200) == 'Awake after 200 ms.'
    }

}

// Simple class for testing.
class Sample {
    /**
     * Run method and sleep for specified timeout value.
     *
     * @param timeout Sleep number of milliseconds specified
     *                by the timeout argument.
     * @return String value with simple message.
     */
    String run(final Long timeout) {
        sleep(timeout)
        "Awake after $timeout ms."
    }
}

Written with Spock 1.0-groovy-2.4.

April 10, 2017

Spocklight: Ignoring Other Feature Methods Using @IgnoreRest

To ignore feature methods in our Spock specification we can use the annotation @Ignore. Any feature method or specification with this annotation is not invoked when we run a specification. With the annotation @IgnoreRest we indicate that feature methods that do not have this annotation must be ignored. So any method with the annotation is invoked, but the ones without aren't. This annotation can only be applied to methods and not to a specification class.

In the next example we have a specification with two feature methods that will be executed and one that is ignored:

@Grab('org.spockframework:spock-core:1.0-groovy-2.4')
import spock.lang.Specification
import spock.lang.IgnoreRest
import spock.lang.Subject

class SampleSpec extends Specification {

    @Subject
    private final underTest = new Sample()

    @IgnoreRest
    void 'run this spec'() {
        expect:
        underTest.message('Asciidoctor') == 'Asciidoctor is awesome.'
    }
    
    @IgnoreRest
    void 'run this spec also'() {
        expect:
        underTest.message('Groovy') == 'Groovy is awesome.'
    }

    void 'ignore this spec'() {
        expect:
        underTest.message('Word') == 'Word is awesome'
    }
    
}

class Sample {
    String message(String tool) {
        println "Getting message for $tool"
        "$tool is awesome."
    }
}

We can run this specification directly from the command line:

$ groovy SampleSpec.groovy
Getting message for Asciidoctor
Getting message for Groovy
JUnit 4 Runner, Tests: 2, Failures: 0, Time: 54
$

Written with Spock 1.0 and Groovy 2.4.10.

April 6, 2017

Ratpacked: Conditionally Map Or Flatmap A Promise

When we want to transform a Promise value we can use the map and flatMap methods. There are also variants to this methods that will only transform a value when a given predicate is true: mapIf and flatMapIf. We provide a predicate and function to the methods. If the predicate is true the function is invoked, otherwise the function is not invoked and the promised value is returned as is.

In the following example we have two methods that use the mapIf and flatMapIf methods of the Promise class:

// File: src/main/java/mrhaki/ratpack/NumberService.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

public class NumberService {

    public Promise<Integer> multiplyEven(final Integer value) {
        return Promise.value(value)
                      .mapIf(number -> number % 2 == 0, number -> number * number);
    }
    
    public Promise<Integer> multiplyTens(final Integer value) {
        return Promise.value(value)
                      .flatMapIf(number -> number % 10 == 0, number -> multiplyEven(number));
    }
    
}

Now we take a look at the following specification to see the result of the methods with different input arguments:

// File: src/test/groovy/mrhaki/ratpack/NumberServiceSpec.groovy
package mrhaki.ratpack

import ratpack.test.exec.ExecHarness
import spock.lang.Specification
import spock.lang.Subject

class NumberServiceSpec extends Specification {

    @Subject
    private final numberService = new NumberService()

    void 'even numbers must be transformed with mapIf'() {
        when:
        final result = ExecHarness.yieldSingle {
            numberService.multiplyEven(startValue)
        }

        then:
        result.value == expected

        where:
        startValue || expected
        1          || 1
        2          || 4
        3          || 3
        4          || 16
    }

    void 'ten-th numbers must be transformed with flatMapIf'() {
        when:
        final result = ExecHarness.yieldSingle {
            numberService.multiplyTens(startValue)
        }

        then:
        result.value == expected

        where:
        startValue || expected
        1          || 1
        10         || 100
        2          || 2
        20         || 400
    }
}

Written with Ratpack 1.4.5.

April 3, 2017

Ratpacked: Get Time Taken To Fulfil Promise

The Promise class has a lot of methods. One of the methods is the time method. We can invoke this method an a Promise instance. The method creates a Duration object that we can use inside the method. The duration is the time taken from when the promise is subscribed to to when the result is available. The promise value is not changed, so we can add the time method at any position of a method chain for the Promise object.

In the following specification we check the duration for a Promise that is returned by the method generate of the class Numbers. For our example we wait for a number of seconds dependent on the argument of the generate method. In the specification we use the time method and check the time spent to fulfil the promise.

package mrhaki.ratpack

import ratpack.exec.Promise

import ratpack.test.exec.ExecHarness
import spock.lang.Specification
import spock.lang.Unroll

import java.time.Duration

class NumbersSpec extends Specification {

    @Unroll('with argument #num response time should be at least #responseTime')
    void 'time used by Numbers.generate method increased and dependent on argument'() {
        given:
        final numbers = new Numbers()

        and:
        long timer

        when:
        final result = ExecHarness.yieldSingle {
            numbers.generate(num)
                   .map { value -> value - 1 }
                   .time { duration -> timer = duration.toMillis() }
        }

        then:
        timer >= responseTime
        result.value == generateResult

        where:
        num | responseTime | generateResult
        1   | 1_000        | 0
        2   | 2_000        | 3
        10  | 10_000       | 99
    }

}

class Numbers  {
    Promise<Long> generate(final Long multiplier) {
        Promise
            .sync { -> 
                 // Wait for n-seconds...
                 sleep(multiplier * 1000)
                 multiplier
            }
            .map { value ->
                value * value 
            }
    }
}

Written with Ratpack 1.4.5.

March 30, 2017

Groovy Goodness: Redirecting Print Methods In Scripts

To run external Groovy scripts in our Java or Groovy application is easy to do. For example we can use GroovyShell to evaluate Groovy code in our applications. If our script contains print methods like println we can redirect the output of these methods. The Script class, which is a base class to run script code, has an implementation for the print, printf and println methods. The implementation of the method is to look for a property out, either as part of a Script subclass or in the binding added to a Script class. If the property out is available than all calls to print, printf and println methods are delegated to the object assigned to the out property. When we use a PrintWriter instance we have such an object, but we could also write our own class with an implementation for the print methods. Without an assignment to the out property the fallback is to print on System.out.

In the following example we have a external script defined with the variable scriptText, but it could also be a file or other source with the contents of the script we want to run. We assign our own PrintWriter that encapsulates a StringWriter to capture all invocations to the print methods:

// Groovy script to execute.
def scriptText = '''
def s = "Groovy rocks!"

// Print value of s.
println s

// Use printf for formatted printing.
printf 'The answer is %X', 42
'''

// Assign new PrintWriter to "out"
// variable of binding object.
def stringWriter = new StringWriter()
def shellBinding = new Binding(out: new PrintWriter(stringWriter))

// Create GroovyShell to evaluate script.
def shell = new GroovyShell(shellBinding)

// Run the script.
shell.evaluate(scriptText)

// Check the output of print, println and printf methods.
assert stringWriter.toString() == 'Groovy rocks!\nThe answer is 2A'

Another option is to directory set the out property of a Script object:

def scriptText = '''
def s = "Groovy rocks!"

// Print value of s.
println s

// Use printf for formatted printing.
printf 'The answer is %X', 42
'''

def shell = new GroovyShell()

// Parse script text and return Script object.
def script = shell.parse(scriptText)

// Assign new PrintWriter to "out"
// variable of Script class.
def stringWriter = new StringWriter()
script.out = new PrintWriter(stringWriter)

// Run the script.
script.run()

// Check the output of print, println and printf methods.
assert stringWriter.toString() == 'Groovy rocks!\nThe answer is 2A'

Written with Groovy 2.4.10.

March 23, 2017

Ratpacked: Add Ratpack To Spring Boot Application

In a previous post we saw how we can use Spring Boot in a Ratpack application. But the integration can also be the other way around: using Ratpack in a Spring Boot application. This way we can use Ratpack's power to handle requests sent to our Spring Boot application and still use all Spring Boot features in our application. The easiest way to add Ratpack to a Spring Boot application is adding a Ratpack dependency and use the @EnableRatpack annotation. With this annotation a RatpackServer instance is created and started along with configuration options.

Let's see an example Spring Boot application with Ratpack enabled. First we add Ratpack as dependency to our Spring Boot application. In our example we also add Ratpack's Dropwizard module as dependency. We use Gradle in our example:

// File: build.gradle
plugins {
    id 'groovy'
    id 'idea'
    id 'org.springframework.boot' version '1.5.2.RELEASE'
}

repositories {
    jcenter()
}

ext {
    ratpackVersion = '1.4.5'
}
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    compile 'org.springframework.boot:spring-boot-devtools'
    
    // Add Ratpack for Spring Boot dependency.
    compile "io.ratpack:ratpack-spring-boot-starter:$ratpackVersion"
    // Add Dropwizard for Ratpack dependency.
    compile "io.ratpack:ratpack-dropwizard-metrics:$ratpackVersion"
    
    runtime 'ch.qos.logback:logback-classic:1.2.2'
    
    testCompile "org.spockframework:spock-core:1.0-groovy-2.4" 
}

springBoot {
    mainClass = 'mrhaki.sample.SampleApp'    
}

Now we look at our example application. We use the annotation @EnableRatpack to have Ratpack in our Spring Boot application. We add a Spring bean that implements Action<Chain>. Beans of this type are recognised by Spring Boot and are added to the Ratpack configuration. We also add a Spring bean that is a Ratpack module. This bean is also automatically added to the Ratpack configuration.

// File: src/main/java/mrhaki/sample/SampleApp.java
package mrhaki.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import ratpack.dropwizard.metrics.DropwizardMetricsConfig;
import ratpack.dropwizard.metrics.DropwizardMetricsModule;
import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.RequestLogger;
import ratpack.spring.config.EnableRatpack;
import ratpack.spring.config.RatpackServerCustomizer;

import java.time.Duration;

// Add Ratpack configuration for Spring Boot
@EnableRatpack
@EnableConfigurationProperties
@SpringBootApplication
public class SampleApp {

    /**
     * Start application.
     * 
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Implementation for {@link MessageService} with pirate accent.
     * 
     * @return {@link MessageService} Pirate speak.
     */
    @Bean
    MessageService pirateMessage() {
        return name -> String.format("Arr, matey %s", name);
    }

    /**
     * Create Ratpack chain to handle requests to {@code /message} endpoint.
     * 
     * @return Ratpack chain.
     */
    @Bean
    Action<Chain> messageHandler() {
        return chain -> chain
                // Add logging for requests.
                .all(RequestLogger.ncsa())
                .get("message/:name?", ctx -> {
                    final String name = ctx.getPathTokens().getOrDefault("name", "mrhaki");
                    // Use MessageService implementation added to Spring context.
                    final String message = ctx.get(MessageService.class).message(name);
                    ctx.render(message);
                });
    }

    /**
     * Configuration properties to configure {@link DropwizardMetricsModule}.
     * Properties can be set via default Spring Boot mechanism like
     * environment variables, system properties, configuration files, etc.
     * 
     * @return Configuration for {@link DropwizardMetricsModule}
     */
    @Bean
    MetricsProperties metricsProperties() {
        return new MetricsProperties();
    }

    /**
     * Spring beans that are {@link com.google.inject.Module} objects are
     * automatically added to Ratpack's registry.
     * 
     * @param metricsProperties Configuration for module.
     * @return Module to add Dropwizard to Ratpack.
     */
    @Bean
    DropwizardMetricsModule metricsModule(final MetricsProperties metricsProperties) {
        // Create Dropwizard configuration.
        final DropwizardMetricsConfig config = new DropwizardMetricsConfig();
        if (metricsProperties.isJmx()) {
            config.jmx();
        }
        if (metricsProperties.getSlf4j().isEnabled()) {
            config.slf4j(slf4jConfig -> slf4jConfig
                    .enable(true)
                    .reporterInterval(Duration.ofSeconds(metricsProperties.getSlf4j().getInterval())));
        }

        // Create Dropwizard module.
        final DropwizardMetricsModule metricsModule = new DropwizardMetricsModule();
        metricsModule.setConfig(config);

        return metricsModule;
    }
}

We create a bean pirateMessage that implements the following interface:

package mrhaki.sample;

public interface MessageService {
    String message(final String name);
}

We also need the supporting class MetricsProperties to allow for configuration of the Dropwizard module.

// File: src/main/java/mrhaki/sample/MetricsProperties.java
package mrhaki.sample;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Configuration for {@link ratpack.dropwizard.metrics.DropwizardMetricsModule}.
 */
@ConfigurationProperties(prefix = "dropwizard")
public class MetricsProperties {
    private boolean jmx;
    private Slf4Config slf4j = new Slf4Config();

    public boolean isJmx() {
        return jmx;
    }

    public void setJmx(final boolean jmx) {
        this.jmx = jmx;
    }

    public Slf4Config getSlf4j() {
        return slf4j;
    }

    public static class Slf4Config {
        private boolean enabled;
        private long interval = 30;

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(final boolean enabled) {
            this.enabled = enabled;
        }

        public long getInterval() {
            return interval;
        }

        public void setInterval(final long interval) {
            this.interval = interval;
        }
    }
}

To complete our application we also add a configuration file where we can change several aspects of our application:

# File: src/main/resources/application.yml
ratpack:
  port: 9000
---
dropwizard:
  jmx: true
  slf4j:
    enabled: true
    interval: 10

Let's start the application and we can see already in the logging output Ratpack is started:

$ ./gradlew bootRun
:compileJava
:compileGroovy NO-SOURCE
:processResources UP-TO-DATE
:classes
:findMainClass
:bootRun
06:38:45.137 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
06:38:45.139 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
06:38:45.140 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/classes/main/, file:/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/resources/main/]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-03-23 06:38:45.531  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : Starting SampleApp on mrhaki-laptop-2015.fritz.box with PID 25302 (/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/classes/main started by mrhaki in /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot)
2017-03-23 06:38:45.532  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : No active profile set, falling back to default profiles: default
2017-03-23 06:38:45.609  INFO 25302 --- [  restartedMain] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@701a7feb: startup date [Thu Mar 23 06:38:45 CET 2017]; root of context hierarchy
2017-03-23 06:38:46.023  INFO 25302 --- [  restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2017-03-23 06:38:46.687  INFO 25302 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-03-23 06:38:46.714  INFO 25302 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-03-23 06:38:46.726  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Starting server...
2017-03-23 06:38:46.963  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Building registry...
2017-03-23 06:38:47.618  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Initializing 1 services...
2017-03-23 06:38:47.711  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Ratpack started for http://localhost:9000
2017-03-23 06:38:47.716  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : Started SampleApp in 2.556 seconds (JVM running for 2.992)

To add extra server configuration properties we must add a Spring bean that implements the ratpack.spring.config.RatpackServerCustomizer interface. The Spring Boot Ratpack configuration uses all beans found in the context that implement this interface. The interface has three methods we need to implement: getHandlers, getBindings and getServerConfig. The easiest way to implement the interface is by extending the class RatpackServerCustomizerAdapter. This class already provides empty implementations for the three methods. We only need to override the method we need in our application.

We rewrite our previous example application. We create a new class RatpackServerConfig that extends RatpackServerCustomizerAdapter. We override the method getServerConfig to set the development mode property of our Ratpack server configuration:

// File: src/main/java/mrhaki/sample/RatpackServerConfig.java
package mrhaki.sample;

import org.springframework.beans.factory.annotation.Autowired;
import ratpack.func.Action;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackProperties;
import ratpack.spring.config.RatpackServerCustomizerAdapter;

/**
 * Spring beans that implement {@link ratpack.spring.config.RatpackServerCustomizer}
 * interface our used for configuring Ratpack. The class
 * {@linly onk RatpackServerCustomizerAdapter} is a convenience class we can 
 * extend and only override the methods we need to.
 */
public class RatpackServerConfig extends RatpackServerCustomizerAdapter {

    /**
     * {@link RatpackProperties} configuration properties 
     * for Ratpack configuration.
     */
    private final RatpackProperties ratpack;

    public RatpackServerConfig(final RatpackProperties ratpack) {
        this.ratpack = ratpack;
    }

    /**
     * Extra configuration for the default Ratpack server configuration.
     * 
     * @return Extra server configuration.
     */
    @Override
    public Action<ServerConfigBuilder> getServerConfig() {
        return serverConfigBuilder -> serverConfigBuilder
                .development(ratpack.isDevelopment());
    }
    
}

We change SampleApp and add RatpackServerConfig as Spring bean:

// File: src/main/java/mrhaki/sample/SampleApp.java
...
    /**
     * Extra Ratpack server configuration.
     * 
     * @param ratpackProperties Properties for Ratpack server configuration.
     * @return Bean with extra Ratpack server configuration.
     */
    @Bean
    RatpackServerCustomizer ratpackServerSpec(final RatpackProperties ratpackProperties) {
        return new RatpackServerConfig(ratpackProperties);
    }
...

Another rewrite of our application could be to move all Ratpack configuration like handlers and bindings in the RatpackServerConfig class. We simply need to override the other two methods: getHandlers and getBindings. This way we have all the configuration together.

// File: src/main/java/mrhaki/sample/RatpackServerConfig.java
package mrhaki.sample;

import ratpack.dropwizard.metrics.DropwizardMetricsConfig;
import ratpack.dropwizard.metrics.DropwizardMetricsModule;
import ratpack.func.Action;
import ratpack.guice.BindingsSpec;
import ratpack.handling.Chain;
import ratpack.handling.RequestLogger;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackProperties;
import ratpack.spring.config.RatpackServerCustomizerAdapter;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;

/**
 * Spring beans that implement {@link ratpack.spring.config.RatpackServerCustomizer}
 * interface our used for configuring Ratpack. The class
 * {@linly onk RatpackServerCustomizerAdapter} is a convenience class we can 
 * extend and only override the methods we need to.
 */
public class RatpackServerConfig extends RatpackServerCustomizerAdapter {

    /**
     * {@link RatpackProperties} configuration properties 
     * for Ratpack configuration. 
     */
    private final RatpackProperties ratpack;

    /**
     * Configuration properties for {@link DropwizardMetricsModule}.
     */
    private final MetricsProperties metrics;

    public RatpackServerConfig(
            final RatpackProperties ratpack, 
            final MetricsProperties metrics) {
        this.ratpack = ratpack;
        this.metrics = metrics;
    }

    /**
     * Extra configuration for the default Ratpack server configuration.
     * 
     * @return Extra server configuration.
     */
    @Override
    public Action<ServerConfigBuilder> getServerConfig() {
        return serverConfigBuilder -> serverConfigBuilder
                .development(ratpack.isDevelopment());
    }

    /**
     * Configure Ratpack handlers.
     * 
     * @return List of Ratpack chain configurations.
     */
    @Override
    public List<Action<Chain>> getHandlers() {
        return Arrays.asList(messageHandler()); 
    }

    /**
     * Create Ratpack chain to handle requests to {@code /message} endpoint.
     *
     * @return Ratpack chain.
     */
    private Action<Chain> messageHandler() {
        return chain -> chain
                // Add logging for requests.
                .all(RequestLogger.ncsa())
                .get("message/:name?", ctx -> {
                    final String name = ctx.getPathTokens().getOrDefault("name", "mrhaki");
                    // Use MessageService implementation added to Spring context.
                    final String message = ctx.get(MessageService.class).message(name);
                    ctx.render(message);
                });
    }

    /**
     * Add {@link DropwizardMetricsModule} to the Ratpack bindings.
     * 
     * @return Ratpack bindings.
     */
    @Override
    public Action<BindingsSpec> getBindings() {
        return bindings -> {
            bindings.module(DropwizardMetricsModule.class, dropwizardMetricsConfig());
        };
    }

    /**
     * Configuration for {@link DropwizardMetricsModule}.
     * 
     * @return Configuration action for configuring {@link DropwizardMetricsModule}.
     */
    private Action<DropwizardMetricsConfig> dropwizardMetricsConfig() {
        return config -> {
            if (metrics.isJmx()) {
                config.jmx();
            }
            if (metrics.getSlf4j().isEnabled()) {
                config.slf4j(slf4jConfig -> slf4jConfig
                        .enable(true)
                        .reporterInterval(Duration.ofSeconds(metrics.getSlf4j().getInterval())));
            }
        };
    }
}

Written with Ratpack 1.4.5 and Spring Boot 1.5.2.RELEASE.