Search

Dark theme | Light theme
Showing posts with label Ratpacked:Renderer. Show all posts
Showing posts with label Ratpacked:Renderer. Show all posts

March 7, 2017

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.

July 6, 2016

Ratpacked: Create a Partial Response

Suppose we want to support partial JSON responses in our Ratpack application. The user must send a request parameter with a list of fields that need to be part of the response. In our code we must use the value of the request parameter and output only the given properties of an object. We implement this logic using a custom renderer in Ratpack. Inside the renderer we can get access to the request parameters of the original request.

In our example Ratpack application we have a Course class, which is a simple class withs some properties:

// File: src/main/groovy/mrhaki/ratpack/Course.groovy
package mrhaki.ratpack

import groovy.transform.Immutable

@Immutable
class Course {
    String name
    String teacher
    Integer maxOccupation
}

Next we create a custom renderer for our Course class. We extend the RendererSupport class and override the render method:

// File: src/main/groovy/mrhaki/ratpack/CourseRenderer.groovy
package mrhaki.ratpack

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

import static ratpack.jackson.Jackson.json

class CourseRenderer extends RendererSupport<Course> {
    
    @Override
    void render(final Context context, final Course course) throws Exception {
        // Get request parameter fields with a comma separated list
        // of field names to include in the output.
        final String paramFields = context.request.queryParams.get('fields')
        
        if (paramFields) {
            // Transform comma separated property names to a Set.
            final Set<String> coursePropertyNames = 
                    paramFields.tokenize(',').toSet()
            
            // Create Map with only Course properties that need to
            // be included.
            final Map partialCourse = 
                    filterProperties(course, coursePropertyNames)
            
            // Render Map.
            context.render(json(partialCourse))
        } else {
            // No fields request parameter so we can return
            // the original Course object.
            context.render(json(course))
        }
    }

    /**
     * Find all properties in the object that are in the collection
     * of property names.
     * 
     * @param object Object with properties to filter
     * @param propertyNames Names of properties to find
     * @return Map with properties
     */
    private Map filterProperties(
            final Object object, 
            final Set<String> propertyNames) {

        object.properties.findAll { property -> 
            property.key in propertyNames 
        }
    }
}

Finally we need to add the CourseRenderer to the Ratpack registry. Ratpack will find the renderer when we want to render a Course object. This happens automatically, we don't have to do anything ourselves. The following Ratpack application configuration adds our CourseRenderer with the bind method. We also add a endpoint to show the contents of a sample Course object.

// File: src/ratpack/ratpack.groovy
import mrhaki.ratpack.Course
import mrhaki.ratpack.CourseRenderer
import ratpack.registry.Registry

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        // Add to registry, so Ratpack can use
        // it to render a Course object.
        bind CourseRenderer
    }
    handlers {
        all {
            final Course course = 
                    new Course(
                            name: 'Ratpack rules 101',
                            teacher: 'mrhaki',
                            maxOccupation: 450)
            next(Registry.single(course))
        }
        get('course') { Course course ->
            render(course)
        }
    }
}

Let's try several requests using the fields request parameter and without the fields request parameter using HTTPie as client:

$ http -b http://localhost:5050/course
{
    "maxOccupation": 450,
    "name": "Ratpack rules 101",
    "teacher": "mrhaki"
}

$ http -b http://localhost:5050/course fields==name,teacher
{
    "name": "Ratpack rules 101",
    "teacher": "mrhaki"
}

Written with Ratpack 1.3.3.

January 20, 2016

Ratpacked: Running With LiveReload Using Gradle

When we develop our Ratpack application using Gradle we can use the continuous build feature of Gradle. If we make a change in a source file then our Ratpack application is automatically restarted. It would be nice to combine this with LiveReload using the Gradle LiveReload plugin. Then when we change for example a stylesheet file it is automatically reloaded in the web browser without invoking a refresh action.

In the following build file we add the Gradle LiveReload plugin and configure it to watch for changes in the output directory of the processResources task. This task is executed when we change a file in the source directory if we use Gradle's continuous build feature.

// File: build.gradle
plugins {
    id "io.ratpack.ratpack-groovy" version "1.1.1"
    // Add LiveReload Gradle plugin.
    id "org.kordamp.gradle.livereload" version "0.2.1"
    id "com.github.johnrengelman.shadow" version "1.2.2"
    id "idea"
}

// Configure liveReload task.
liveReload {
    // Set docRoot as the base directory for
    // the livereload server to watch for changes.
    // We choose the output directory of the 
    // processResources task, because it will contain
    // the changed files if we run our application
    // with Gradle's continuous support ($ gradle -t run).
    // In another terminal we need to start this
    // task ($ gradle liveReload).
    docRoot processResources.destinationDir.canonicalPath
}

repositories {
    jcenter()
}

dependencies {
    // Default SLF4J binding.  Note that this is a blocking implementation.
    runtime 'org.slf4j:slf4j-simple:1.7.12'
    testCompile "org.spockframework:spock-core:1.0-groovy-2.4"
}

Now we have the LiveReload server implementation, but we need LiveReload support in the browser as well. We can use browser extensions that are available or we add a bit of Javascript code to our pages. In the following example application we write an implementation of the RenderableDecorator interface to pass in the view model an attribute that denotes if we are running in development mode or not. When we run in development mode we add the custom Javascript code for LiveReload, otherwise we skip it.

// File: src/ratpack/Ratpack.groovy
import ratpack.groovy.template.MarkupTemplate
import ratpack.groovy.template.MarkupTemplateModule
import ratpack.render.RenderableDecorator
import ratpack.server.ServerConfig

import static ratpack.groovy.Groovy.groovyMarkupTemplate
import static ratpack.groovy.Groovy.ratpack

ratpack {
    
    bindings {
        module MarkupTemplateModule

        // Add extra attribute development to the view model.
        // If we run in development mode the value is true otherwise
        // it is false.
        bindInstance RenderableDecorator.of(MarkupTemplate) { context, template ->

            // Get value for development property of server configuration.
            final boolean development = context.get(ServerConfig).development

            new MarkupTemplate(
                    template.name,
                    template.contentType,
                    [development: development] + template.model)
        }
    }

    handlers {
        get {
            render groovyMarkupTemplate("index.gtpl", title: "Ratpack")
        }

        files { dir "public" }
    }
}

In the application we use the Groovy markup template engine. The engine supports layouts and we use the layout to conditionally add the LiveReload Javascript snippet.

// File: src/ratpack/templates/index.gtpl
layout 'layout.gtpl', true,
    bodyContents: contents {
        header {
            h1 'Welcome to ratpack'
        }
        
        section {
            p "Running in development mode: ${model.development}" 
        }
    }
// File: src/ratpack/templates/layout.gtpl
yieldUnescaped ''
html {
  head {
    meta(charset:'utf-8')
    title(title)

    link(href: '/images/favicon.ico', rel: 'shortcut icon')
    link(href: '/styles/app.css', rel: 'stylesheet')
  }
  body {

    bodyContents()
    
    // Include livereload Javascript snippet,
    // if we run in development mode.
    if (model.development) {
      yieldUnescaped """
      <script>
        document.write('<script src="http://' + 
           (location.host || 'localhost').split(':')[0] + 
           ':35729/livereload.js?snipver=1"></' + 
           'script>');
      </script>
      """
    }
  }
}

We have all the code ready it is time to first start our application with Gradle:

$ gradle -t run
Continuous build is an incubating feature.
:compileJava UP-TO-DATE
:compileGroovy
:processResources
:classes
:configureRun
:run
[main] INFO ratpack.server.RatpackServer - Starting server...
[main] INFO ratpack.server.RatpackServer - Building registry...
[main] INFO ratpack.server.RatpackServer - Ratpack started (development) for http://localhost:5050

BUILD SUCCESSFUL

Total time: 3.57 secs

Waiting for changes to input files of tasks... (ctrl-d to exit)

In a second terminal or console we start the liveReload task:

$ gradle liveReload
:liveReload
Enabling LiveReload at port 35729 for /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/livereload/build/resources/main
> Building 0% > :liveReload

We open the URL http://localhost:5050 in our browser. Next we change for example the index.gtpl file, save it and see that the page is automatically refreshed in the web browser.

Written with Ratpack 1.1.1.

January 19, 2016

Ratpacked: Customising Renderers With Decorators

When we use the Context.render method Ratpack's rendering mechanism kicks in. The type of the argument we pass to the render method is used to look up the correct renderer. The renderer implements the Renderer interface and provides the real output. We can add functionality that can work with the object of the Renderer implementation before the actual output is created. We do this by adding a class or object to the registry that implements the RenderableDecorator interface. The interface has a method decorate that accepts the Context and object that needs to be rendered. The code is invoked after the Context.render method, but before the Renderer.render method. This is especially useful when we use template renderers with a view model and with a RenderableDecorator implementation we can augment the view model with some general attributes.

Suppose we have a Ratpack application that uses the Groovy text template engine provided by the TextTemplateModule. The module adds a Renderer for TextTemplate objects. Let's write a RenderableDecorator implementation for the TextTemplate, where we add an extra attribute createdOn to the view model:

// File: src/main/groovy/com/mrhaki/ratpack/CreatedOnRendererDecorator.groovy
package com.mrhaki.ratpack

import ratpack.exec.Promise
import ratpack.groovy.template.TextTemplate
import ratpack.handling.Context
import ratpack.render.RenderableDecorator

import java.time.Clock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

/**
 * Add extra attribute to view model for all TextTemplate renderers.
 */
class CreatedOnRendererDecorator implements RenderableDecorator<TextTemplate> {

    /**
     * Apply this decorator for TextTemplate renderers.
     * 
     * @return TextTemplate class.
     */
    @Override
    Class<TextTemplate> getType() {
        return TextTemplate
    }

    /**
     * Add an extra attribute createdOn to the view model with the current
     * date and time.
     * 
     * @param context Context to get Clock instance for this Ratpack application from.
     * @param template Template with view model to extend.
     * @return Promise with new TextTemplate instance with the extended view model.
     */
    @Override
    Promise<TextTemplate> decorate(final Context context, final TextTemplate template) {
        final footerModel = [createdOn: createdOn(context)]

        return Promise.value(
                new TextTemplate(
                        template.model + footerModel, 
                        template.id, 
                        template.type))
    }

    /**
     * Create formatted date/time String based on
     * the Clock available on the Ratpack registry.
     * 
     * @param context Context to get Clock instance from.
     * @return Formatted date/time String.
     */
    private String createdOn(final Context context) {
        final Clock clock = context.get(Clock)
        final LocalDateTime now = LocalDateTime.now(clock)
        final DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return formatter.format(now)
    }
    
}

To use this decorator we must add it to the registry in our Ratpack application. We also use the RenderableDecorator.of method to create a decorator for our application directory in our Ratpack Groovy DSL:

// File: src/ratpack/Ratpack.groovy
import com.mrhaki.ratpack.CreatedOnRendererDecorator
import ratpack.groovy.template.TextTemplate
import ratpack.groovy.template.TextTemplateModule
import ratpack.render.RenderableDecorator
import ratpack.util.RatpackVersion

import static ratpack.groovy.Groovy.groovyTemplate
import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        // Use Groovy's simple text template engine.
        module TextTemplateModule
        
        // Use class that implements the
        // RenderableDecorator interface.
        bind CreatedOnRendererDecorator
        
        // Create RenderableDecorator instance using the 
        // RenderableDecorator.of method.
        // Here we add the model attribute createdWith.
        bindInstance RenderableDecorator.of(TextTemplate) { context, template ->
            new TextTemplate(
                    template.model + [createdWith: "Ratpack ${RatpackVersion.version}"],
                    template.id,
                    template.type)
        }
    }

    handlers {
        get {
            // Set model attributes used on the template.
            final model = 
                    [title: 'Ratpack Application',
                     welcomeMessage: 'Welcome to Ratpack']
            
            render groovyTemplate(model, 'index.html')
        }

        files { dir "public" }
    }
}

To complete the example we create the following index.html file in the directory src/ratpack/templates:

<!doctype html>
<html class="no-js" lang="">
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>${model.title}</title>
</head>
<body>

<header>
    <h1>${model.welcomeMessage}</h1>
</header>

<section>
    <p>Some sample text for the template.</p>
</section>

<footer>
    <p>Page is created with ${model.createdWith} at ${model.createdOn}.</p>
</footer>

</body>
</html>

Written with Ratpack 1.1.1.

December 23, 2015

Ratpacked: Register Renderer For A List Of Objects

When we use the render method in our Ratpack application then Ratpack will use the type of the object we want to render to find an appropriate renderer. Some renderers are built-in, like a Promise or CharSequence renderer. We can write our own renderers by implementing the ratpack.render.Renderer interface. Next we must register our renderer in the Ratpack registry.

In our example application we have a very simple User class:

// File: src/main/groovy/com/mrhaki/ratpack/User.groovy
package com.mrhaki.ratpack

class User {
    String username
}

We define a renderer for a list of User objects:

// File: src/main/groovy/com/mrhaki/ratpack/UserListRenderer.grooovy
package com.mrhaki.ratpack

import com.google.inject.util.Types
import ratpack.handling.ByContentSpec
import ratpack.handling.Context
import ratpack.jackson.Jackson
import ratpack.render.Renderer

import static ratpack.groovy.Groovy.markupBuilder

class UserListRenderer implements Renderer<List<User>> {

    /**
     * Define that this renderer is used for List instance
     * with User instances.
     *
     * @return Type of List<User>
     */
    @Override
    Class<List<User>> getType() {
        Types.listOf(User).rawType
    }

    @Override
    void render(Context context, List<User> userList) throws Exception {
        // We return different responses based on the 
        // requested content type.
        context.byContent { ByContentSpec spec ->
            spec
                // Render JSON response.
                .json {
                    context.render(Jackson.json(userList))
                }
                // Render XML response
                .xml {
                    // Use markupBuilder method to create XML content.
                    context.render(markupBuilder('application/xml', 'UTF-8') {
                        users {
                            userList.each { singleUser ->
                                user {
                                    username(singleUser.username)
                                }
                            }
                        }
                    })
                }
        }
    }
}

Now we use the UserListRenderer in the configuration of our Ratpack application:

// File: src/ratpack/ratpack.groovy
import com.mrhaki.ratpack.UserListRenderer

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        // Add renderer for a list of users
        // to the registry.
        bind(UserListRenderer)
        
        // Create two sample users.
        bindInstance(['mrhaki', 'hubert'].collect { String name -> 
            new User(username: name)
        })
    }

    handlers {
        get('users') { List<User> users ->
            // Render the list of users
            // fetched from the registry.
            // Ratpack will use the 
            // UserListRenderer we have added
            // to the registry.
            render(users)
        }
    }
}

When we invoke the URL http://localhost:5050/users for both JSON and XML content we get the following results:

$ http localhost:5050/users Accept:application/json
HTTP/1.1 200 OK
connection: keep-alive
content-encoding: gzip
content-type: application/json
transfer-encoding: chunked

[
    {
        "username": "mrhaki"
    }, 
    {
        "username": "hubert"
    }
]

$ http localhost:5050/users Accept:application/xml
HTTP/1.1 200 OK
connection: keep-alive
content-encoding: gzip
content-type: application/xml
transfer-encoding: chunked

<users>
    <user>
        <username>mrhaki</username>
    </user>
    <user>
        <username>hubert</username>
    </user>
</users>

$

Written with Ratpack 1.1.1.