Getting Started‎ > ‎

Creating a form

Let's create a form to tweet as follows:

index.jsp

<%@page pageEncoding="UTF-8" isELIgnored="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@taglib prefix="f" uri="http://www.slim3.org/functions"%>

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>twitter Index</title>
<link rel="stylesheet" type="text/css" href="/css/global.css" />
</head>
<body>
<p>What are you doing?</p>
<form method="post" action="tweet">
<textarea name="content"></textarea><br />
<input type="submit" value="tweet"/>
</form>
</body>
</html>

Visit http://localhost:8888/twitter/ again. "index.jsp" will be shown as follows:



Input something, and post it. You will encounter 404 NOT FOUND HTTP ERROR, because you haven't create a controler for /twitter/tweet yet. So let's create it.

Double-click build.xml under the project.
In the Outline view, right-click gen-controller-without-view task and select Run as > Ant Build(first item).
In the ant input dialog, input "/twitter/tweet", and click OK button.

If your project is not refreshed automatically, in the Window menu, select Preferences > General > Workspace. Click "Refresh automatically" checkbox. Or refresh your project manually.

"tutorial.controller.twitter.TweetController", "tutorial.controller.twitter.TweetControllerTest" are created.
A TweetController transfers to "/twitter/ after it is performed, so you don't need to define JSP. If you want to create a controller without JSP, use gen-controller-without-view task.

TweetController.java

package tutorial.controller.twitter;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

public class TweetController extends Controller {

    @Override
    public Navigation run() throws Exception {
        return null;
    }
}

TweetControllerTest.java

package tutorial.controller.twitter;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;

public class TweetControllerTest extends ControllerTestCase {

    @Test
    public void run() throws Exception {
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(false));
        assertThat(tester.getDestinationPath(), is(nullValue()));
    }
}

Run the "TweetControllerTest.java". The result will be green(OK).
The TweetController is redirected to "/twitter/ after it is performed, so you should change the test case as follows:
package tutorial.controller.twitter;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.slim3.tester.ControllerTestCase;

public class TweetControllerTest extends ControllerTestCase {

    @Test
    public void run() throws Exception {
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(true));
        assertThat(tester.getDestinationPath(), is("/twitter/"));
    }
}

Run the "TweetControllerTest.java". The result will be red(NG), because the run method of TweetController returns null. So you should the controller as follows:
package tutorial.controller.twitter;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;

public class TweetController extends Controller {

    @Override
    public Navigation run() {
        return redirect(basePath);
    }
}

In this case, "basePath" is equivalent to "/twitter/". Run the "TweetControllerTest.java". The result will be green(OK).
Then, let's create a data model to store the tweeted content.

Double-click build.xml under the project.
In the Outline view, right-click gen-model task and select Run as > Ant Build(first item).
In the ant input dialog, input "Tweet", and click OK button.

If your project is not refreshed automatically, in the Window menu, select Preferences > General > Workspace. Click "Refresh automatically" checkbox. Or refresh your project manually.

"tutorial.model.Tweet", "tutorial.model.TweetTest" are created.

Tweet.java

package tutorial.model;

import java.io.Serializable;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model(schemaVersion = 1)
public class Tweet implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;

    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param key
     *            the key
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Returns the version.
     *
     * @return the version
     */
    public Long getVersion() {
        return version;
    }

    /**
     * Sets the version.
     *
     * @param version
     *            the version
     */
    public void setVersion(Long version) {
        this.version = version;
    }
}

TweetTest.java

package tutorial.model;

import org.junit.Test;
import org.slim3.tester.AppEngineTestCase;
import static org.junit.Assert.*;

import static org.hamcrest.CoreMatchers.*;


public class TweetTest extends AppEngineTestCase {

    private Tweet model = new Tweet();

    @Test
    public void test() throws Exception {
        assertThat(model, is(notNullValue()));
    }
}

Run the "TweetTest.java". The result will be green(OK).
Then, let's add content, createdDate property to the Tweet model as follows:

Tweet.java

package tutorial.model;

import java.io.Serializable;
import java.util.Date;

import com.google.appengine.api.datastore.Key;

import org.slim3.datastore.Attribute;
import org.slim3.datastore.Model;

@Model(schemaVersion = 1)
public class Tweet implements Serializable {

    private static final long serialVersionUID = 1L;

    @Attribute(primaryKey = true)
    private Key key;

    @Attribute(version = true)
    private Long version;
   
    private String content;
   
    private Date createdDate = new Date();

    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Sets the key.
     *
     * @param key
     *            the key
     */
    public void setKey(Key key) {
        this.key = key;
    }

    /**
     * Returns the version.
     *
     * @return the version
     */
    public Long getVersion() {
        return version;
    }

    /**
     * Sets the version.
     *
     * @param version
     *            the version
     */
    public void setVersion(Long version) {
        this.version = version;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }
}

Run the "TweetTest.java". The result will be green(OK).
The Tweet model is ready.

Models have two roles:Data Model and Service. A main role of Data Model is to store the data. A main role of Service is to perform a use case. Of course Data Model can have some logics for the data, too. "gen-model" creates a Data Model.

So, let's create a service to store a Tweet model to the datastore.

Double-click build.xml under the project.
In the Outline view, right-click gen-service task and select Run as > Ant Build(first item).
In the ant input dialog, input "TwitterService", and click OK button.

If your project is not refreshed automatically, in the Window menu, select Preferences > General > Workspace. Click "Refresh automatically" checkbox. Or refresh your project manually.

"tutorial.service.TwitterService", "tutorial.service.TwitterServiceTest" are created.

TwitterService.java

package tutorial.service;


public class TwitterService {

}

TwitterServiceTest.java

package tutorial.service;

import org.slim3.tester.AppEngineTestCase;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class TwitterServiceTest extends AppEngineTestCase {

    private TwitterService service = new TwitterService();

    @Test
    public void test() throws Exception {
        assertThat(service, is(notNullValue()));
    }
}

Run the "TwitterServiceTest.java". The result will be green(OK).

I want to implement a method to store a tweeted content to the datastore, so I write the test first as follows:

TwitterServiceTest.java

package tutorial.service;

import java.util.HashMap;
import java.util.Map;

import org.slim3.datastore.Datastore;
import org.slim3.tester.AppEngineTestCase;
import org.junit.Test;

import tutorial.model.Tweet;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

public class TwitterServiceTest extends AppEngineTestCase {

    private TwitterService service = new TwitterService();

    @Test
    public void test() throws Exception {
        assertThat(service, is(notNullValue()));
    }
   
    @Test
    public void tweet() throws Exception {
        Map<String, Object> input = new HashMap<String, Object>();
        input.put("content", "Hello");
        Tweet tweeted = service.tweet(input);
        assertThat(tweeted, is(notNullValue()));
        Tweet stored = Datastore.get(Tweet.class, tweeted.getKey());
        assertThat(stored.getContent(), is("Hello"));
    }
}

To get a model by a Key, you use Datastore.get(Class modelClass, Key key).
See Getting a model by Key.
A compile error occurs. Compiler says "The method tweet is undefined for the type TwitterService", so let's define it.

TwitterService.java

package tutorial.service;

import java.util.Map;

import org.slim3.datastore.Datastore;
import org.slim3.util.BeanUtil;

import com.google.appengine.api.datastore.Transaction;

import tutorial.model.Tweet;


public class TwitterService {

    public Tweet tweet(Map<String, Object> input) {
        Tweet tweet = new Tweet();
        BeanUtil.copy(input, tweet);
        Transaction tx = Datastore.beginTransaction();
        Datastore.put(tweet);
        tx.commit();
        return tweet;
    }
}

BeanUtil copies property values from the input data to the model for all cases where the property names are the same. Even if the property type of the input is different from the one of the model, the value is converted appropriately.
To put a model to the datastore, you use Datastore.put(Object model).
See Creating a model.
If an exception occurred during the transaction, it is rolled back by Slim3 automatically.

The compile error is gone, so run the "TwitterServiceTest.java". The result will be green(OK).

I want to modify "TweetController" to call TwitterService#tweet(), so I modify "TweetControllerTest" first as follows:

TweetControllerTest.java

package tutorial.controller.twitter;

import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import org.slim3.datastore.Datastore;
import org.slim3.tester.ControllerTestCase;

import tutorial.model.Tweet;

public class TweetControllerTest extends ControllerTestCase {

    @Test
    public void run() throws Exception {
        tester.param("content", "Hello");
        tester.start("/twitter/tweet");
        TweetController controller = tester.getController();
        assertThat(controller, is(notNullValue()));
        assertThat(tester.isRedirect(), is(true));
        assertThat(tester.getDestinationPath(), is("/twitter/"));
        Tweet stored = Datastore.query(Tweet.class).asSingle();
        assertThat(stored, is(notNullValue()));
        assertThat(stored.getContent(), is("Hello"));
    }
}

ControllerTester(tester)#param() emulates a form parameter.
To query and get a single model, you use Datastore.query(Class modelClass).asSingle().
See Introducing Queries.

Run the "TweetControllerTest.java". The result will be red(NG), because I do not modify "TweetController.java" to call TwitterService#tweet(). Then let's modify "TweetController.java".

TweetController.java

package tutorial.controller.twitter;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.util.RequestMap;

import tutorial.service.TwitterService;

public class TweetController extends Controller {

    private TwitterService service = new TwitterService();
   
    @Override
    public Navigation run() throws Exception {
        service.tweet(new RequestMap(request));
        return redirect(basePath);
    }
}

Slim3 copies request parameters to request attributes, and RequestMap wraps request attributes as Map.
Run the "TweetControllerTest.java". The result will be green(OK). "TweetController.java" is ready, so let's back to index.jsp, and input something, and post it.

After tweeting, visit http://localhost:8888/_ah/admin. In Entity Kind list select Tweet, and click "List Entities" button. You can confirm the tweeted content.


Next...

Continue to Listing tweets.

Comments