View on GitHub RESTX     the lightweight Java REST framework
Improve This Page

Generated app explained

Once you have tried out the generated app, now it’s time to understand its sources.

Intro

First let’s have a look at the lines of code:

$ cloc .
      10 text files.
      10 unique files.
       2 files ignored.

http://cloc.sourceforge.net v 1.55  T=0.5 s (16.0 files/s, 278.0 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
Java                             5             14              7             73
XML                              2              3              0             35
YAML                             1              0              0              7
-------------------------------------------------------------------------------
SUM:                             8             17              7            115
-------------------------------------------------------------------------------

As you can see the generated code is minimal, and shouldn’t be too difficult to understand as you will see below.

Resource definition

The resource definition is done in the class <your.main.package>.rest.HelloResource:

@Component @RestxResource
public class HelloResource {
    @GET("/message")
    public Message sayHello(String who) {
        return new Message().setMessage(String.format(
                "hello %s, it's %s",
                who, DateTime.now().toString("HH:mm:ss")));
    }
}

Let’s decompose its content:

The @Component annotation declares this class as an injectable component, and the @RestxResource declares it as a resource.

To get more information on RESTX dependency injection mechanism, check RESTX Factory reference documentation.

Then the sole method defines a resource endpoint thanks to its @GET annotation (similar annotations are available for other HTTP verbs). The parameter "/message" tells that this endpoint is mounted on /message relative to RESTX base path (/api in this case, this is defined in the web.xml).

The parameter String who defines a query parameter (a parameter that will be provided after the ? in the URL).

The content of the method is called when a matching request is received, and constructs a Message object and returns it, using joda time to get current date / time.

`Message` is not part of RESTX API, it could be any class that Jackson is able to map to JSON. See below to have details on that class in this example.

You can set a breakpoint in this method and run your app in debug to see when this is called. You can also use the open call hierarchy action of your IDE to see the caller of the method. Here is the generated code:

new StdEntityRoute<Void, hello.domain.Message>("default#HelloResource#sayHello",
                readerRegistry.<Void>build(Void.class, Optional.<String>absent()),
                writerRegistry.<hello.domain.Message>build(hello.domain.Message.class, Optional.<String>absent()),
                new StdRestxRequestMatcher("GET", "/message"),
                HttpStatus.OK, RestxLogLevel.DEFAULT) {
            @Override
            protected Optional<hello.domain.Message> doRoute(RestxRequest request, RestxRequestMatch match, Void body) throws IOException {
                securityManager.check(request, open());
                return Optional.of(resource.sayHello(
                        /* [QUERY] who */ checkPresent(request.getQueryParam("who"), "query param who is required")
                ));
            }
}

As you can see the code generated by annotation processing is readable, with comments on the type of parameters. The reader/writer registries are used to determine which reader should be used to process request body into an object and writer is used to convert returned Object into a stream in the response body.

Writing routes manually is also possible, though most of the time using annotation processing is fine, it's good to know that you can always fall back to a more low level code. All you need to do for that is declare a component implementing the RestxRoute interface.

To get more information on RESTX REST endpoint definitions, check RESTX REST endpoints reference documentation.

Domain class: Message

The Message class is part of the application domain (it’s also called an entity):

public class Message {
    private String message;
    public String getMessage() {
        return message;
    }
    public Message setMessage(String message) {
        this.message = message;
        return this;
    }
    @Override
    public String toString() {
        return "Message{" +
                "message='" + message + '\'' +
                '}';
    }
}

This is a plain Java bean: the toString method is not mandatory, and using fluent setter (which returns this) is not mandatory either.

Binding to JSON is done using the jackson library, check their docs to see how to configure JSON mapping.

You can also use Bean Validation (JSR 303) / Hibernate Validator annotations to add validation to your beans when they are used as body parameters.

Resource Spec

What is called a spec in RESTX is a yaml file describing a resource behaviour, or a set of behaviours chained in a scenario.

In the generated app, you can check the should_say_hello.spec.yaml file in src/test/resources/specs/hello:

title: should say hello
given:
  - time: 2013-03-31T14:33:18.272+02:00
wts:
  - when: GET message?who=xavier
    then: |
      {"message":"hello xavier, it's 14:33:18"}

The notation follows BDD terminology given when then (wts stands for When ThenS).

In the given section the state of the system before the HTTP requests is described. In this case we only specify the time in ISO format. Then a list of when then pairs follows, the when specify HTTP request, the then HTTP response.

This spec is used for 2 things:

  • example in the API docs
  • integration test

Because RESTX app follows REST principles, the server has no conversation state. Therefore any HTTP request can be tested in isolation.

The principle of scenario is there mainly to avoid repeating the `given` part too frequently, or also to be able to verify that the system state change after an HTTP request, for example issue a `GET` after a `PUT` to verify that the new resource representation has been stored.

To get more information on RESTX spec concept and related features, check RESTX Specs reference documentation.

Resource Spec Test

To actually be able to run this spec as a test, it is necessary to write a JUnit test to run it:

@RunWith(RestxSpecTestsRunner.class)
@FindSpecsIn("specs/hello")
public class HelloResourceSpecTest {
    /**
     * Useless, thanks to both @RunWith(RestxSpecTestsRunner.class) & @FindSpecsIn()
     *
     * @Rule
     * public RestxSpecRule rule = new RestxSpecRule();
     *
     * @Test
     * public void test_spec() throws Exception {
     *     rule.runTest(specTestPath);
     * }
     */
}

As you can see this code is very basic thanks to provided runner and @FindSpecsIn annotation. The comments also show how to use a JUnit rule to do pretty much the same thing but more programmatically. In either cases the test will start a server on a free port, and verify the spec (i.e. issue HTTP requests and verify the results) against it.

To get more information on RESTX spec concept and related features, check RESTX Specs reference documentation.

AppServer

The AppServer class is the class used to run the app as a standard Java app.

public class AppServer {
    public static final String WEB_INF_LOCATION = "src/main/webapp/WEB-INF/web.xml";
    public static final String WEB_APP_LOCATION = "src/main/webapp";
    public static void main(String[] args) throws Exception {
        int port = Integer.valueOf(Optional.fromNullable(System.getenv("PORT")).or("8080"));
        WebServer server = new JettyWebServer(WEB_INF_LOCATION, WEB_APP_LOCATION, port, "0.0.0.0");

        /*
         * load mode from system property if defined, or default to dev
         * be careful with that setting, if you use this class to launch your server in production, make sure to launch
         * it with -Drestx.mode=prod or change the default here
         */
        System.setProperty("restx.mode", System.getProperty("restx.mode", "dev"));
        System.setProperty("restx.app.package", "hello");

        server.startAndAwait();
    }
}

All it does is launch an embedded server (Jetty in this particular case, but Tomcat and SimpleFramework server are also supported).

If you prefer to run your JavaEE web container of choice separately and use standard deploy mechanism, no problem, the generated app is already configured to be packaged as a standard war.

AppModule

The AppModule class is defined like this:

@Module
public class AppModule {
    @Provides
    public SignatureKey signatureKey() {
         return new SignatureKey("4f768f23-703e-4268-9e9e-51d2e052b6a1 4082747839477764571 MyApp myapp".getBytes(Charsets.UTF_8));
    }
    @Provides
    @Named("restx.admin.password")
    public String restxAdminPassword() {
        return "qwerty";
    }
    @Provides
    @Named("app.name")
    public String appName() {
        return "MyApp";
    }
}

This class is mandatory to provide at least a SignatureKey used to sign content sent to the clients. The string is used as salt, it can be any content, but make sure to keep it private.

The @Module annotation indicates that this class is used as a RESTX module, able to define a set of components.

The @Provides annotation on the signatureKey method is a way to define a component instanciation programmatically. This kind of method can take arbitrary parameters, injected by the RESTX factory.

To get more information on RESTX dependency injection mechanism, check RESTX Factory reference documentation.

In the AppModule, you will be able to define lots of Application scoped objects, such as : - An admin password, which will be used to authenticate on the ` RESTX Administration Console - An application name, which will be used in different ways, particularly by suffixing RESTX Cookies names`

web.xml

This file is the standard JavaEE web descriptor. It’s used to configure the RESTX servlet:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0" metadata-complete="true">
    <servlet>
        <servlet-name>restx</servlet-name>
        <servlet-class>restx.servlet.RestxMainRouterServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>restx</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>
</web-app>

This file is not needed if you use SimpleFramework integration rather than JavaEE web container.

RESTX module descriptor

RESTX uses its own module descriptor format, which is build tool agnostic. The file is called md.restx.json:

{
    "module": "myapp:myapp:0.1-SNAPSHOT",
    "packaging": "war",
    "properties": {
        "java.version": "1.7",
        "restx.version": "0.34.1"
    },
    "dependencies": {
        "compile": [
            "io.restx:restx-core:${restx.version}",
            "io.restx:restx-security-basic:${restx.version}",
            "io.restx:restx-core-annotation-processor:${restx.version}",
            "io.restx:restx-factory:${restx.version}",
            "io.restx:restx-factory-admin:${restx.version}",
            "io.restx:restx-monitor-admin:${restx.version}",
            "io.restx:restx-server-jetty:${restx.version}",
            "io.restx:restx-apidocs:${restx.version}",
            "io.restx:restx-specs-admin:${restx.version}",
            "io.restx:restx-admin:${restx.version}",
            "ch.qos.logback:logback-classic:1.0.13"
        ],
        "test": [
            "io.restx:restx-specs-tests:${restx.version}",
            "junit:junit:4.11"
        ]
    }
}

This file is used at build time only, its the source used by RESTX to:

  • generate Maven POM or Ivy files
  • download dependencies (with restx deps install or for restx app run)
  • manage app dependencies (with restx deps install command)

The module descriptor isn't used at runtime, you can get rid of it if you prefer to manage building, running and deploying your app on your own.

The file format is pretty straightforward to understand if you are familiar with Maven, Ivy or similar build / dependency management tools.

logback.xml

This file is used to configure logging. This is maybe one of the more complex generated files, depending on your experience with logback configuration.

Feel free to adjust it to your own needs, but if you are not confortable with log configuration it provides a reasonnable configuration both for development and production.

<configuration>
    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>
    <property name="LOGS_FOLDER" value="${logs.base:-logs}" />
    <appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOGS_FOLDER}/errors.log</File>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <encoder>
            <pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOGS_FOLDER}/errors.%d.log</fileNamePattern>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <if condition='p("restx.mode").equals("prod")'>
        <then>
            <!-- production mode -->
            <appender name="appLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <File>${LOGS_FOLDER}/app.log</File>
                <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                    <level>INFO</level>
                </filter>
                <encoder>
                    <pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
                </encoder>
                <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                    <fileNamePattern>${LOGS_FOLDER}/app.%d.log</fileNamePattern>
                    <maxHistory>10</maxHistory>
                </rollingPolicy>
            </appender>
            <appender name="debugFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
                <File>${LOGS_FOLDER}/debug.log</File>
                <encoder>
                    <pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
                </encoder>
                <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
                    <fileNamePattern>${LOGS_FOLDER}/debug.%i.log.zip</fileNamePattern>
                    <minIndex>1</minIndex>
                    <maxIndex>3</maxIndex>
                </rollingPolicy>

                <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                    <maxFileSize>50MB</maxFileSize>
                </triggeringPolicy>
            </appender>
            <root level="INFO">
                <appender-ref ref="debugFile" />
                <appender-ref ref="appLog" />
            </root>
        </then>
        <else>
            <!-- not production mode -->
            <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
                <encoder>
                    <pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
                </encoder>
            </appender>
            <appender name="appLog" class="ch.qos.logback.core.FileAppender">
                <File>${LOGS_FOLDER}/app.log</File>
                <encoder>
                    <pattern>%d [%-16thread] [%-10X{principal}] %-5level %logger{36} - %msg%n</pattern>
                </encoder>
            </appender>

            <root level="INFO">
                <appender-ref ref="STDOUT" />
                <appender-ref ref="appLog" />
            </root>
        </else>
    </if>
    <!-- clean up container logs -->
    <logger name="org.eclipse.jetty.server.AbstractConnector" level="WARN" />
    <logger name="org.eclipse.jetty.server.handler.ContextHandler" level="WARN" />
    <logger name="org.eclipse.jetty.webapp.StandardDescriptorProcessor" level="WARN" />
    <logger name="org.hibernate.validator.internal.engine.ConfigurationImpl" level="WARN" />
    <logger name="org.reflections.Reflections" level="WARN" />
    <logger name="restx.factory.Factory" level="WARN" />
    <!-- app logs - set DEBUG level, in prod it will go to a dedicated file -->
    <logger name="test43" level="DEBUG" />
    <root level="INFO">
        <appender-ref ref="errorFile" />
    </root>
</configuration>