Agrest Workflow


1. Create a Simple Agrest App

In this chapter, we set up a minimal environment to build an Agrest-based application.

1.1. Install Java

Install Java 1.8 or later.

1.2. Install IntelliJ IDEA

Download and install IntelliJ IDEA or your own preferred Java IDE.

1.3. The resulting application

The final application is available at from GitHub

1.4. Starting a project

In this chapter, we create a new Java project in IntelliJ IDEA and introduce a simple Bookstore application that will be used as an example.

1.4.1. Define a Bookstore Domain Model

The application contains two types of entities: Category and Book. The relationship between Category and Book entities is one-to-many.

bookstore er diagram

1.4.2. Create a new Project in IntelliJ IDEA

In IntelliJ IDEA, select File > New > Project... Then select Maven and click Next.

In the dialog shown on the screenshot below, fill in the Group Id and Artifact Id fields and click Next.

tutorial idea project

During the next step, you will be able to customize the directory for your project. Click Finish where you are done. Now you should have a new empty project.

1.4.3. Configure Maven pom.xml

Add the following dependencies:

<dependency>
    <groupId>io.agrest</groupId>
    <artifactId>agrest</artifactId>
    <version>4.9</version>
</dependency>

<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet-core</artifactId>
    <version>2.35</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>2.35</version>
</dependency>

<dependency>
    <groupId>org.apache.derby</groupId>
    <artifactId>derby</artifactId>
    <version>10.15.2.0</version>
</dependency>

Configure a jetty Maven plugin to start app using mvn jetty:run command

<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>10.0.7</version>
    <configuration>
        <scanIntervalSeconds>5</scanIntervalSeconds>
        <classesDirectory>${project.basedir}/target/classes</classesDirectory>
        <supportedPackagings><supportedPackaging>jar</supportedPackaging></supportedPackagings>
    </configuration>
</plugin>

1.5. Implementation

In this chapter, we implement a simple application to demonstrate Agrest features. The application uses Cayenne as an ORM framework and for further information regarding a DB mapping please, refer to Apache Cayenne ORM

1.5.1. Configure Cayenne

In the application’s resources folder, create a Cayenne project file:

cayenne-project.xml

<?xml version="1.0" encoding="utf-8"?>
<domain project-version="9">
    <map name="datamap"/>

    <node name="datanode"
          factory="org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory"
          schema-update-strategy="org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy"
    >
        <map-ref name="datamap"/>
        <data-source>
            <driver value="org.apache.derby.iapi.jdbc.AutoloadedDriver"/>
            <url value="jdbc:derby:memory:testdb;create=true"/>
            <connectionPool min="1" max="1"/>
            <login/>
        </data-source>
    </node>
</domain>

In the same folder, add a file that contains a basic Cayenne mapping. The mapping is done based on the ER diagram from the Starting a project charter:

datamap.map.xml

<?xml version="1.0" encoding="utf-8"?>
<data-map xmlns="http://cayenne.apache.org/schema/9/modelMap"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://cayenne.apache.org/schema/9/modelMap https://cayenne.apache.org/schema/9/modelMap.xsd"
          project-version="9">
    <property name="defaultPackage" value="org.example.agrest.persistent"/>
    <db-entity name="BOOK">
        <db-attribute name="AUTHOR" type="VARCHAR" length="128"/>
        <db-attribute name="CATEGORY_ID" type="INTEGER"/>
        <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
        <db-attribute name="TITLE" type="VARCHAR" isMandatory="true" length="128"/>
    </db-entity>
    <db-entity name="CATEGORY">
        <db-attribute name="DESCRIPTION" type="NCLOB"/>
        <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
        <db-attribute name="NAME" type="VARCHAR" isMandatory="true" length="128"/>
    </db-entity>
    <obj-entity name="Book" className="org.example.agrest.persistent.Book" dbEntityName="BOOK">
        <obj-attribute name="author" type="java.lang.String" db-attribute-path="AUTHOR"/>
        <obj-attribute name="title" type="java.lang.String" db-attribute-path="TITLE"/>
    </obj-entity>
    <obj-entity name="Category" className="org.example.agrest.persistent.Category" dbEntityName="CATEGORY">
        <obj-attribute name="description" type="java.lang.String" db-attribute-path="DESCRIPTION"/>
        <obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
    </obj-entity>
    <db-relationship name="category" source="BOOK" target="CATEGORY" toMany="false">
        <db-attribute-pair source="CATEGORY_ID" target="ID"/>
    </db-relationship>
    <db-relationship name="books" source="CATEGORY" target="BOOK" toMany="true">
        <db-attribute-pair source="ID" target="CATEGORY_ID"/>
    </db-relationship>
    <obj-relationship name="category" source="Book" target="Category" deleteRule="Nullify" db-relationship-path="category"/>
    <obj-relationship name="books" source="Category" target="Book" deleteRule="Deny" db-relationship-path="books"/>
</data-map>

1.5.2. Define domain models

Create two classes to present data objects in package org.example.agrest.persistent:

public class Category extends CayenneDataObject {

    public static final String ID_PK_COLUMN = "ID";

    public static final Property<String> DESCRIPTION = Property.create("description", String.class);
    public static final Property<String> NAME = Property.create("name", String.class);
    public static final Property<List<Book>> BOOKS = Property.create("books", List.class);
}
public class Book extends CayenneDataObject {

    public static final String ID_PK_COLUMN = "ID";

    public static final Property<String> AUTHOR = Property.create("author", String.class);
    public static final Property<String> TITLE = Property.create("title", String.class);
    public static final Property<Category> CATEGORY = Property.create("category", Category.class);
}

1.5.3. Implement Agrest application classes

Create an application and a resource class in package org.example.agrest:

@ApplicationPath("/api/*")
public class Application extends ResourceConfig {

    public Application() {

        ServerRuntime cayenneRuntime = ServerRuntime.builder()
            .addConfig("cayenne-project.xml")
            .build();

        AgRuntime agRuntime = AgBuilder.build(cayenneRuntime);
        super.register(agRuntime);

        packages("org.example.agrest");
    }
}
@Path("category")
@Produces(MediaType.APPLICATION_JSON)
public class CategoryResource {

    @Context
    private Configuration config;

    @POST
    public SimpleResponse create(String data) {
        return Ag.create(Category.class, config).sync(data);
    }

    @GET
    public DataResponse<Category> getAll(@Context UriInfo uriInfo) {
        return Ag.select(Category.class, config).uri(uriInfo).get();
    }
}

1.5.4. Configure web.xml

Provide a servlet configuration and a mapping based on the application class that you already created.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="false"
         version="3.1">

    <servlet>
        <servlet-name>api</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>org.example.agrest.Application</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>api</servlet-name>
        <url-pattern>/api/*</url-pattern>
    </servlet-mapping>

</web-app>

1.6. Running the Application

In this chapter, we run and test our application. After you have completed the above steps, the structure of your project will look like this:

bookstore ide prjct

1.6.1. Building and running

To build the application, use the mvn clean install command.

To run a jetty server with our application, use the mvn jetty:run command.

1.6.2. Configure IntelliJ IDEA Maven plugin

To run the application using IDE, add a new Maven configuration on a menu path Run → Edit Configurations…​. Then set a Name: (e.g. Bookstore) and a Command line: in jetty:run

1.6.3. Testing

After running the application, you can call this endpoint to get a list of categories:

curl -i -X GET 'http://localhost:8080/api/category'

And get the following response:

HTTP/1.1 200 OK
Date: Wed, 03 Oct 2018 10:14:51 GMT
Content-Type: application/json
Content-Length: 21
Server: Jetty(9.3.14.v20161028)

{"data":[],"total":0}

As you may see, the list is empty. So, use the 'POST' command to add some categories:

curl -i -X POST 'http://localhost:8080/api/category'  -d '{"id":"1","name":"Science Fiction"}'

Repeat the command, if necessary:

curl -i -X POST 'http://localhost:8080/api/category' -d '{"id":"2","name":"Horror"}'

The response will be:

HTTP/1.1 201 Created
Date: Wed, 03 Oct 2018 10:42:17 GMT
Content-Type: application/json
Content-Length: 16
Server: Jetty(9.3.14.v20161028)

{"success":true}

Now make the 'GET' request again and you will receive the following:

HTTP/1.1 200 OK
Date: Wed, 03 Oct 2018 10:44:44 GMT
Content-Type: application/json
Content-Length: 117
Server: Jetty(9.3.14.v20161028)

{"data":[{"id":1,"description":null,"name":"Science Fiction"},{"id":2,"description":null,"name":"Horror"}],"total":2}