Loading...

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.

March 17, 2017

Spring Sweets: Access Application Arguments With ApplicationArguments Bean

When we start a Spring Boot application and pass arguments to the application, Spring Boot will capture those arguments and creates a Spring bean of type ApplicationArguments and puts it in the application context. We can use this Spring bean to access the arguments passed to the application. We could for example auto wire the bean in another bean and use the provided argument values. The ApplicationArguments interface has methods to get arguments values that are options and plain argument values. An option argument is prefixed with --, for example --format=xml is a valid option argument. If the argument value is not prefixed with -- it is a plain argument.

In the following example application we have a Spring Boot application with a Spring bean that implements CommandLineRunner. When we define the CommandLineRunner implementation we use the ApplicationArguments bean that is filled by Spring Boot:

package mrhaki.springboot;

import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Result;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

import static mrhaki.springboot.sql.Tables.USERS;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application, let Spring Boot
     * decide the exit code and use the
     * exit code value for {@code System.exit()} method.
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Find users based on user input given via application arguments.
     * Example usage: {@code java -jar sample.jar --format=csv mrhaki}
     * 
     * @param dslContext JOOQ context to access data.
     * @param applicationArguments Bean filled by Spring Boot 
     *                             with arguments used to start the application
     * @return Bean to run at startup
     */
    @Bean
    CommandLineRunner showUsers(
            final DSLContext dslContext, 
            final ApplicationArguments applicationArguments) {
        
        return (String... arguments) -> {
            // Get arguments passed to the application  that are
            // not option arguments from ApplicationArguments bean.
            // In the following example: java -jar sample.jar mrhaki
            // mrhaki is the non option argument.
            final String name = applicationArguments.getNonOptionArgs().get(0);
            final Result<Record1<String>> result =
                    dslContext.select(USERS.NAME)
                              .from(USERS)
                              .where(USERS.NAME.likeIgnoreCase(name))
                              .fetch();

            // Check if option argument --format is used.
            // In the following example: java -jar sample.jar --format=xml --format=csv
            // format is the option argument with two values: xml, csv.
            if (applicationArguments.containsOption("format")) {
                // Get values for format option argument.
                final List<String> format = applicationArguments.getOptionValues("format");
                if (format.contains("xml")) {
                    result.formatXML(System.out);
                }
                if (format.contains("json")) {
                    result.formatJSON(System.out);
                }
                if (format.contains("html")) {
                    result.formatHTML(System.out);
                }
                if (format.contains("html")) {
                    result.formatCSV(System.out);
                }
            } else {
                result.format(System.out);
            }
        };
    }

}

We have an alternative if we want to use the ApplicationArguments bean in a CommandlineRunner implementation. Spring Boot offers the ApplicationRunner interface. The interface has the method run(ApplicationArguments) we need to implement and gives us directly access to the ApplicationArguments bean. In the next example we refactor the application and use ApplicationRunner:

package mrhaki.springboot;

import org.jooq.DSLContext;
import org.jooq.Record1;
import org.jooq.Result;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.List;

import static mrhaki.springboot.sql.Tables.USEaRS;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application, let Spring Boot
     * decide the exit code and use the
     * exit code value for {@code System.exit()} method.
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Find users based on user input given via application arguments.
     * Example usage: {@code java -jar sample.jar --format=csv mrhaki}
     *
     * @param dslContext JOOQ context to access data.
     * @return Bean to run at startup
     */
    @Bean
    ApplicationRunner showUsers(final DSLContext dslContext) {
        return (ApplicationArguments arguments) -> {
            // Get arguments passed to the application that are
            // not option arguments. 
            // In the following example: java -jar sample.jar mrhaki
            // mrhaki is the non option argument.
            final String name = arguments.getNonOptionArgs().get(0);
            final Result<Record1<String>> result =
                    dslContext.select(USERS.NAME)
                              .from(USERS)
                              .where(USERS.NAME.likeIgnoreCase(name))
                              .fetch();

            // Check if option argument --format is used.
            // In the following example: java -jar sample.jar --format=xml --format=csv
            // format is the option argument with two values: xml, csv.
            if (arguments.containsOption("format")) {
                // Get values for format option argument.
                final List<String> format = arguments.getOptionValues("format");
                if (format.contains("xml")) {
                    result.formatXML(System.out);
                } 
                if (format.contains("json")) {
                    result.formatJSON(System.out);
                }
                if (format.contains("html")) {
                    result.formatHTML(System.out);
                }
                if (format.contains("html")) {
                    result.formatCSV(System.out);
                }
            } else {
                result.format(System.out);
            }
        };
    }

}

Written with Spring Boot 1.5.2.RELEASE.

March 15, 2017

Spring Sweets: Custom Exit Code From Exception

When we write a Spring Boot application a lot of things are done for us. For example when an exception in the application occurs when we start our application, Spring Boot will exit the application with exit code 1. If everything goes well and the we stop the application the exit code is 0. When we use the run method of SpringApplication and an exception is not handled by our code, Spring Boot will catch it and will check if the exception implements the ExitCodeGenerator interface. The ExitCodeGenerator interface has one method getExitCode() which must return a exit code value. This value is used as input argument for the method System.exit() that is invoke by Spring Boot to stop the application.

In the following example application we write a Spring Boot command line application that can throw an exception on startup. The exception class implements the ExitCodeGenerator interface:

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

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.Random;

import static org.springframework.boot.SpringApplication.run;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application.
     */
    public static void main(String[] args) {
        run(SampleApp.class, args);
    }

    /**
     * Very simple {@code CommandLineRunner} to get a
     * random {@code Integer} value and throw exception if value
     * is higher than or equal to 5. This code is executed
     * when we run the application.
     *
     * @return Simple application logic.
     */
    @Bean
    CommandLineRunner randomException() {
        return args -> {
            final Integer value = new Random().nextInt(10);
            if (value >= 5) {
                throw new TooHighException(String.format("Value %d is too high", value));
            } else {
                System.out.println(value);
            }
        };
    }

    /**
     * Exception when a Integer value is too high.
     * We implement the {@code ExitCodeGenerator} interface and
     * annotate this class as a Spring component. Spring Boot
     * will look for classes that implement {@code ExitCodeGenerator}
     * and use them to get a exit code.
     */
    static class TooHighException extends Exception implements ExitCodeGenerator {
        TooHighException(final String message) {
            super(message);
        }

        /**
         * @return Always return 42 as exit code when this exception occurs.
         */
        @Override
        public int getExitCode() {
            return 42;
        }
    }

}

But there is also an exit method available in the SpringApplication class. This method determines the exit code value by looking up beans in the application context that implement the ExitCodeExceptionMapper interface. The ExitCodeExceptionMapper interface has the method getExitCode(Throwable). We can write an implementation that has logic to return different exit codes based on characteristics of the given Throwable. This is especially useful when we want to have an exit code for exceptions from third party libraries.

In the following example we use the exit method of SpringApplication and a Spring bean exitCodeExceptionMapper that implements the ExitCodeExceptionMapper interface using a Java 8 lambda expression:

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

import org.jooq.DSLContext;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ExitCodeExceptionMapper;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.dao.DataAccessResourceFailureException;

import static mrhaki.springboot.sql.Tables.USERS;
import static org.springframework.boot.SpringApplication.exit;
import static org.springframework.boot.SpringApplication.run;

@SpringBootApplication
public class SampleApp {

    /**
     * Run the application, let Spring Boot
     * decide the exit code and use the
     * exit code value for {@code System.exit()} method.
     */
    public static void main(String[] args) {
        System.exit(exit(run(SampleApp.class, args)));
    }

    /**
     * Very simple {@code CommandLineRunner} to get a
     * a list of users from a Postgresql database 
     * using JOOQ.
     *
     * @return Simple application logic.
     */
    @Bean
    CommandLineRunner showUsers(final DSLContext dslContext) {
        return args -> dslContext.select(USERS.NAME)
                                 .from(USERS)
                                 .fetch()
                                 .forEach(name -> System.out.println(name.get(USERS.NAME)));
    }

    /**
     * Spring bean that implements the {@code ExitCodeExceptionMapper}
     * interface using a lambda expression.
     */
    @Bean
    ExitCodeExceptionMapper exitCodeExceptionMapper() {
        return exception -> {
            // Using ExitCodeExceptionMapper we can set
            // the exit code ourselves, even if we didn't
            // write the exception ourselves.
            if (exception.getCause() instanceof DataAccessResourceFailureException) {
                return 42;
            }
            return 1;
        };
    }

}

Written with Spring Boot 1.5.2.RELEASE.

March 8, 2017

Ratpacked: Override Registry Objects With Mocks In Integration Specifications

Testing a Ratpack application is not difficult. Ratpack has excellent support for writing unit and integration tests. When we create the fixture MainClassApplicationUnderTest we can override the method addImpositions to add mock objects to the application. We can add them using the ImpositionsSpec object. When the application starts with the test fixture the provided mock objects are used instead of the original objects. For a Groovy based Ratpack application we can do the same thing when we create the fixture GroovyRatpackMainApplicationUnderTest.

We start with a simple Java Ratpack application. The application adds an implementation of a NumberService interface to the registry. A handler uses this implementation for rendering some output.

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

import ratpack.registry.Registry;
import ratpack.server.RatpackServer;

public class RatpackApplication {
    public static void main(String[] args) throws Exception {
        RatpackServer.start(server -> server
                // Add a implementation of NumberService interface
                // to the registry, so it can be used by the handler.
                .registry(Registry.single(NumberService.class, new NumberGenerator()))
                
                // Register a simple handler to get the implementation
                // of the NumberService interface, invoke the give() method
                // and render the value.
                .handler(registry -> ctx -> ctx
                        .get(NumberService.class).give()
                        .then(number -> ctx.render(String.format("The answer is: %d", number)))));
    }
}

The NumberService interface is not difficult:

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

import ratpack.exec.Promise;

public interface NumberService {
    Promise<Integer> give();
}

The implementation of the NumberService interface returns a random number:

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

import ratpack.exec.Promise;

import java.util.Random;

public class NumberGenerator implements NumberService {
    @Override
    public Promise<Integer> give() {
        return Promise.sync(() -> new Random().nextInt());
    }
}

To test the application we want to use a mock for the NumberService interface. In the following specification we override the addImpositions method of the MainClassApplicationUnderTest class:

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

import ratpack.exec.Promise
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import ratpack.test.http.TestHttpClient
import spock.lang.AutoCleanup
import spock.lang.Specification

/**
 * Integration test for the application.
 */
class RatpackApplicationSpec extends Specification {
    
    /**
     * Mock implementation for the {@link NumberService}.
     */
    private final NumberService mockNumberService = Mock()

    /**
     * Setup {@link RatpackApplication} for testing and provide
     * the {@link #mockNumberService} instance to the Ratpack registry.
     */
    @AutoCleanup
    private aut = new MainClassApplicationUnderTest(RatpackApplication) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            // Set implementation of NumberService interface to
            // our mock implementation for the test.
            impositions.add(
                    UserRegistryImposition.of(
                            Registry.single(NumberService, mockNumberService)))
        }
    }

    /**
     * Use HTTP to test our application.
     */
    private TestHttpClient httpClient = aut.httpClient
    
    void 'render output with number'() {
        when:
        final response = httpClient.get()
        
        then:
        // Our mock should get invoked once and we return 
        // the fixed value 42 wrapped in a Promise.
        1 * mockNumberService.give() >> Promise.sync { 42 }

        and:
        response.statusCode == 200
        response.body.text == 'The answer is: 42'
    }
}

Written with Ratpack 1.4.5.

Ratpacked: Combine Groovy DSL With RatpackServer Java Configuration

We have several options to define a Ratpack application. We can use a Java syntax to set up the bindings and handlers. Or we can use the very nice Groovy DSL. It turns out we can use both together as well. For example we can define the handlers with the Groovy DSL and the rest of the application definition is written in Java. To combine both we start with the Java configuration and use the bindings and handlers method of the Groovy.Script class to inject the files with the Groovy DSL.

We start with a sample application where we use Java configuration to set up our Ratpack application:

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

import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }

    void startServer() throws Exception {
        RatpackServer.start(server -> server
                .registryOf(registry())
                .handlers(chain()));
    }

    private Action<RegistrySpec> registry() {
        return registry -> registry
                .add(new RecipeRenderer())
                .add(RecipeRepository.class, new RecipesList());
    }

    private Action<Chain> chain() {
        return chain -> chain.post("recipe", recipeHandler());
    }

    private Handler recipeHandler() {
        return ctx -> ctx
                .parse(RecipeRequest.class)
                .flatMap(recipeRequest -> ctx
                        .get(RecipeRepository.class)
                        .findRecipeByName(recipeRequest.getName()))
                .then((Optional<Recipe> optionalRecipe) -> ctx.render(optionalRecipe));
    }

}

We can use the Groovy DSL for the bindings and handlers definitions and use them in our Java class with the Groovy.Script class. First we create the files bindings.groovy and handlers.groovy in the directory src/main/resources so they will be in the class path of the Java application. We can use the Groovy DSL syntax in the files:

// File: src/main/resources/bindings.groovy
import mrhaki.ratpack.RecipeRenderer
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipesList

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        add new RecipeRenderer()
        add RecipeRepository, new RecipesList()
    }   
}
// File: src/main/resources/handlers.groovy
import mrhaki.ratpack.Recipe
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipeRequest

import static ratpack.groovy.Groovy.ratpack

ratpack {
    handlers {
        post('recipe') { RecipeRepository recipeRepository ->
            parse(RecipeRequest)
                    .flatMap { RecipeRequest recipeRequest -> 
                        recipeRepository.findRecipeByName(recipeRequest.name) 
                    }
                    .then { Optional<Recipe> optionalRecipe -> 
                        render(optionalRecipe) 
                    }
        }
    }
}

We have our Groovy DSL files with the definitions. To use them in our Java code to define the Ratpack application we must make sure Ratpack can find them. Therefore we create an empty marker file .ratpack in src/main/resources. With this file in place we can use Ratpack's findBaseDir method to set the base directory for finding external files. It is time to refactor our application:

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

import ratpack.func.Action;
import ratpack.groovy.Groovy;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }

    void startServer() throws Exception {
        RatpackServer.start(server -> server
                // Set base dir with directory that
                // contains marker file .ratpack.
                .serverConfig(config -> config.findBaseDir())
                // Use bindings.groovy with static compilation.
                .registry(Groovy.Script.bindings(true))
                // Use handlers.groovy with static compilation.
                .handler(Groovy.Script.handlers(true)));
    }

}

Written with Ratpack 1.4.5.

March 7, 2017

Ratpacked: Type Check And Static Compilation For Groovy DSL

One of the very nice features of Ratpack is the Groovy DSL to define our application. We get a nice DSL to set up the registry, to define handlers and more. Because of clever use of the @DelegateTo annotation we get good code completion in our IDE. We can also add static compilation of our Groovy DSL when we start our Ratpack application. With static compilation the script is type checked at compile time so we get earlier feedback on possible errors in the script. To configure static compilation we must invoke the app method of the Groovy.Script class with the argument true.

We start with a Groovy DSL for an application that serves recipes. Notice the Closure arguments are all typed, so with type checking there are no errors.

// File: src/ratpack/ratpack.groovy
import mrhaki.ratpack.Recipe
import mrhaki.ratpack.RecipeRenderer
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipeRequest
import mrhaki.ratpack.RecipesList

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        add new RecipeRenderer()
        add RecipeRepository, new RecipesList()
    }

    handlers {
        post('recipe') { RecipeRepository recipeRepository ->
            parse(RecipeRequest)
                    .flatMap { RecipeRequest recipeRequest -> 
                        recipeRepository.findRecipeByName(recipeRequest.name) 
                    }
                    .then { Optional<Recipe> optionalRecipe -> 
                        render(optionalRecipe) 
                    }
        }
    }
}

Next we create a class with a main method that will be the starting point of our application. We need to run this class to start our Ratpack application. The example class is a Java class, but could also be written with Groovy:

// File: src/main/java/mrhaki/sample/GroovyCompileStaticRatpackMain.java
package mrhaki.ratpack;

import ratpack.groovy.Groovy;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class GroovyCompileStaticRatpackMain {
    
    public static void main(String[] args) throws Exception {
        RatpackServer.start(Groovy.Script.app(true /* compileStatic */));
    }
}

When we use the Gradle Ratpack plugin we use this class as the main class:

// File: build.gradle
...
mainClassName = 'mrhaki.ratpack.GroovyCompileStaticRatpackMain'
...

Now we can still use the run task to start our Ratpack application.

Written with Ratpack 1.4.5.

Gradle Goodness: Create Shortcut Key To Refresh Gradle Projects In IntellIJ IDEA

We can open a Gradle project in IntelliJ IDEA and get support for Gradle inside IntelliJ. Sometimes we need to refresh the project in IntelliJ IDEA, for example when we add a new dependency or plugin in our Gradle build file. We need to refresh the Gradle project so IntelliJ IDEA can work with the changes. The Gradle tool window has an icon to Refresh all Gradle projects. But this means a mouse action and we want to have a shortcut key so we can leave our hands on the keyboard.

The action Refresh all Gradle projects is actually the action Refresh all external projects. We can add keyboard shortcut key via Preferences | Keymap. We use the search box to search for Refresh all external projects.

We can right click on the found action and select Add Keyboard Shortcut to define a new shortcut key:

Now we simply use the shortcut to refresh our Gradle project when we have made a change in the Gradle build file that IntelliJ IDEA should know about.

Besides have the action in the Gradle tool window we can also add it to the main toolbar. We right click on the main toolbar and select the option Customize Menus and Toolbars.... We can add the action Refresh all external projects here to the toolbar:

Written with Gradle 3.4.1 and IntelliJ IDEA 2016.3.4.

Ratpacked: Implement A Custom Request Parser

Ratpack has parsers to parse a request with a JSON body or a HTML form. We simply use the parse method of Context and Ratpack will check if there is a compliant parser in the registry. If there is a parser for that type available then Ratpack will parse the request and return a Promise with the value. To write a new parser we need to implement the Parser interface. The easiest way to implement this interface is by writing a class that extends ParserSupport. Using the ParserSupport class we can also work with an options object that a user can pass on to the parse method of Context. If we don't need options we can also extend the NoOptParserSupport class.

Let's write a custom parser that can parse a request with a hex or base64 encoded value. The parser returns a String object with the decoded value. In our example we also want the user to provide an optional options object of type StringParserOpts which denotes the type of decoding:

// File: src/main/groovy/mrhaki/sample/StringParser.groovy
package mrhaki.ratpack

import ratpack.handling.Context
import ratpack.http.TypedData
import ratpack.parse.Parse
import ratpack.parse.ParserSupport
import ratpack.util.Types

/**
 * Parser to decode hex or base64 values send 
 * in the body of a request. 
 */
class StringParser extends ParserSupport<StringParserOpts> {
    
    @Override
    def <T> T parse(
            final Context context,
            final TypedData body,
            final Parse<T, StringParserOpts> parse) throws Exception {

        // Check if type to be parsed can be handled by
        // this parser. We can also create a check based 
        // on content type of the body for example.
        if (supportsType(parse.type)) {
            // Get request body that is either hex or 
            // base64 encoded.
            final String bodyText = body.text

            // Get optional options. If the options are not set
            // a default instance is given. 
            final StringParserOpts opts = parse.opts.orElse(StringParserOpts.hex())
            
            // Check the options to see if hex or base64 decoding is needed.
            if (opts.hex) {
                return Types.cast(new String(bodyText.decodeHex()))
            } else if (opts.base64) {
                return Types.cast(new String(bodyText.decodeBase64()))    
            }
        }

        // Cannot handle the type to be parsed. 
        // Ratpack will try to find another match.
        return null
    }

    /**
     * Support String parsing.
     * 
     * @param typeToken Type defined to be parsed.
     * @return True if type is String, false if not.
     */
    private boolean supportsType(final typeToken) {
        typeToken.rawType == String
    }

}

/**
 * Class with options used to decode a value. 
 * A user can provide an instance of this class using the 
 * {@link Context#parse(java.lang.Class, java.lang.Object)} method.
 */
class StringParserOpts {
    
    private static enum Decoders { HEX, BASE64 }
    
    private Decoders decoder
    
    private StringParserOpts(final Decoders decoder) {
        this.decoder = decoder
    }

    static StringParserOpts hex() {
        new StringParserOpts(Decoders.HEX)
    }
    
    boolean isHex() {
        decoder == Decoders.HEX
    }

    static StringParserOpts base64() {
        new StringParserOpts(Decoders.BASE64)
    }
    
    boolean isBase64() {
        decoder == Decoders.BASE64
    }
    
}

We have the implementation of our parser, so now we write a specification to test it. We test the parser with a simple handler implementation that uses the parse method and then simply renders the resulting String value. In our specification we use RequestFixture to invoke the handler and inspect the result:

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

import ratpack.handling.Handler
import ratpack.test.handling.HandlingResult
import ratpack.test.handling.RequestFixture
import spock.lang.Specification

class StringParserSpec extends Specification {

    void 'parse value in request body with StringParser using default decoder'() {
        given:
        final String content = 'Ratpack is gr8!'.bytes.encodeHex().toString()
        
        and:
        final Handler handler = { context ->
            context.parse(String)
                   .then(context.&render)
        }

        when:
        final HandlingResult result = RequestFixture.handle(handler) { fixture ->
            fixture.body(content, 'text/plain')
                    // Add StringParser to registry, so it can be used by Ratpack.
                   .registry { registry -> registry.add(new StringParser()) }
        }

        then:
        result.rendered(String) == 'Ratpack is gr8!'
    }

    void 'parse hex value in request body with StringParser'() {
        given:
        final String content = 'Ratpack is gr8!'.bytes.encodeHex().toString()

        and:
        final Handler handler = { context ->
            // Parse and set options for hex decoding.
            context.parse(String, StringParserOpts.hex())
                   .then(context.&render)
        }

        when:
        final HandlingResult result = RequestFixture.handle(handler) { fixture ->
            fixture.body(content, 'text/plain')
                    // Add StringParser to registry, so it can be used by Ratpack.
                   .registry { registry -> registry.add(new StringParser()) }
        }

        then:
        result.rendered(String) == 'Ratpack is gr8!'
    }
    
    void 'parse base64 value in request body with StringParser'() {
        given:
        final String content = 'Ratpack is gr8!'.bytes.encodeBase64().toString()

        and:
        final Handler handler = { context ->
            // Parse and set options for base64 decoding.
            context.parse(String, StringParserOpts.base64())
                   .then(context.&render)
        }

        when:
        final HandlingResult result = RequestFixture.handle(handler) { fixture ->
            fixture.body(content, 'text/plain')
                    // Add StringParser to registry, so it can be used by Ratpack.
                   .registry { registry -> registry.add(new StringParser()) }
        }

        then:
        result.rendered(String) == 'Ratpack is gr8!'
    }
    
}

Written with Ratpack 1.4.5.

Ratpacked: Implement Custom Rendering With Renderable Interface

Ratpack uses renderers to render output. We can create our own renderer by implementing the Renderer interface. The renderer class needs to implement a render method that has the object we want to render as argument. Alternatively we can add the logic to render a object to the class definition of that object. So instead of having a separate renderer class for a class, we add the render logic to the class itself. To achieve this we must implement the Renderable interface for our class. Ratpack provides a RenderableRenderer in the registry that knows how to render classes that implement the Renderable interface.

In the following example we have a Recipe class that implements the Renderable interface:

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

import ratpack.handling.Context;
import ratpack.render.Renderable;

import static ratpack.jackson.Jackson.json;

public class Recipe implements Renderable {
    
    private final String name;

    public Recipe(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * Render object as JSON.
     * 
     * @param context Ratpack context.
     */
    @Override
    public void render(final Context context) throws Exception {
        context.byContent(content -> content
                .plainText(() -> context.render(this.toString()))
                .json(() -> context.render(json(this))));
    }
    
    public String toString() {
        return String.format("Recipe::name=%s", this.name);
    }
}

Let's write a specification to test how the Recipe is rendered:

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

import groovy.json.JsonSlurper
import ratpack.test.embed.EmbeddedApp
import spock.lang.Specification

class RecipeRenderableSpec extends Specification {
    
    def app = EmbeddedApp.fromHandler { ctx ->
        ctx.render(new Recipe('macaroni'))
    }
    
    def httpClient = app.httpClient
    
    void 'render Recipe as plain text'() {
        when:
        def response = httpClient.requestSpec { request -> 
            request.headers.set 'Accept', 'text/plain'
        }.get()
        
        then:
        response.statusCode == 200
        
        and:
        response.body.text == 'Recipe::name=macaroni'
    }

    void 'render Recipe as JSON'() {
        when:
        def response = httpClient.requestSpec { request ->
            request.headers.set 'Accept', 'application/json'
        }.get()

        then:
        response.statusCode == 200

        and:
        def recipe = new JsonSlurper().parseText(response.body.text)
        recipe.name == 'macaroni'
    }
}

Written with Ratpack 1.4.5.

March 6, 2017

Ratpacked: Render Optional Type Instance

Ratpack uses renderers to render objects with the render method of the Context class. Ratpack has several renderers that are available automatically. One of those renderers is the OptionalRenderer. When we want to render an Optional object this renderer is selected by Ratpack. If the Optional instance has a value the value is passed to the render method. If the value is not present a 404 client error is returned.

In the following example application we have a RecipeRepository class with a findRecipeByName method. This method returns Promise<Optional<Recipe>>:

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

import ratpack.exec.Promise;

import java.util.Optional;

public interface RecipeRepository {
    Promise<Optional<Recipe>> findRecipeByName(final String name);
}

We have a Handler that will use the findRecipeByName method and then render the Optional<Recipe> object. The following example application shows the handler implementation:

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

import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }
    
    void startServer() throws Exception {
        RatpackServer.start(server -> server
                .registryOf(registry())
                .handlers(chain()));
    }
    
    private Action<RegistrySpec> registry() {
        return registry -> registry
                .add(new RecipeRenderer())
                .add(RecipeRepository.class, new RecipesList());
    }

    private Action<Chain> chain() {
        return chain -> chain.post("recipe", recipeHandler());
    }

    private Handler recipeHandler() {
        return ctx -> ctx
                .parse(RecipeRequest.class)
                .flatMap(recipeRequest -> ctx
                        .get(RecipeRepository.class)
                        .findRecipeByName(recipeRequest.getName()))
                .then((Optional<Recipe> optionalRecipe) -> ctx.render(optionalRecipe));
    }

}

The application also uses a custom RecipeRenderer. This renderer is used when the Optional<Recipe> has a value:

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

import ratpack.handling.Context;
import ratpack.render.RendererSupport;

import static ratpack.jackson.Jackson.json;

public class RecipeRenderer extends RendererSupport<Recipe> {
    @Override
    public void render(final Context ctx, final Recipe recipe) throws Exception {
        ctx.render(json(recipe));
    }
}

Let's write a specification where we can test that a client error with status code 404 is returned when the Optional is empty. Otherwise the actual value is rendered:

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

import groovy.json.JsonSlurper
import ratpack.exec.Promise
import ratpack.http.MediaType
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.Specification
import spock.lang.Subject

import static groovy.json.JsonOutput.toJson

class ApplicationSpec extends Specification {
    
    private RecipeRepository recipeMock = Mock()
    
    @Subject
    private aut = new MainClassApplicationUnderTest(Application) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            // Add mock for RecipeRepository.
            impositions.add(UserRegistryImposition.of(Registry.of { registry ->
                registry.add(RecipeRepository, recipeMock)
            }))
        }
    }
    
    private httpClient = aut.httpClient
    
    void 'response status 404 when Optional<Recipe> is empty'() {
        when:
        def response = httpClient.requestSpec { requestSpec ->
            requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
            requestSpec.body { body ->
                body.text(toJson(name: 'sushi'))
            }
        }.post('recipe')
        
        then:
        1 * recipeMock.findRecipeByName('sushi') >> Promise.value(Optional.empty())
        
        and:
        response.statusCode == 404
    }

    void 'render Recipe when Optional<Recipe> is not empty'() {
        when:
        def response = httpClient.requestSpec { requestSpec ->
            requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
            requestSpec.body { body ->
                body.text(toJson(name: 'macaroni'))
            }
        }.post('recipe')

        then:
        1 * recipeMock.findRecipeByName('macaroni') >> Promise.value(Optional.of(new Recipe('macaroni')))

        and:
        response.statusCode == 200
        
        and:
        def recipe = new JsonSlurper().parseText(response.body.text)
        recipe.name == 'macaroni'
    }

}

Written with Ratpack 1.4.5.