Build a blog engine in 15 minutes in Java
- Prerequisites
- Project creation
- Preparing a database
- Adding a post module
- Validation
- Adding a comment module
- Associating Models
- Configuring routes
- Adding comment list view
- Adding comment entry view
- Creating a comment controller
- Adding validation in comment
- Performance tuning
- Total code lines
- Testing
- Creating war file
- Other deployment options
Let's build a blog engine by using scooter framework.
Prerequisites
You need to follow the installation guide and download the Scooter framework. We put the file scooter.zip in c:\demo directory. If you use a UNIX-like OS, you may put the file under /home/your_account/demo/ directory.
You may use your own favoriate IDE to edit files. However, a simple text editor like Notepad or VI would be fine too. In development mode, the framework compiles and deploys the application automatically. All we need to do is just type (the source code) and click (the refresh button of a browser).
The framework comes with Eclipse setup files.
Project creation
Let's follow the instructions in startup guide to create the blog application.
Now we can browse the site with this url: http://localhost:8080/blog.
During the rest of this development, we do not need to restart the web server.
Preparing a database
Scooter framework targets database-backed application development. It takes a bottom-up apprach, in that a database table must be created first. But you don't have to create a complete database schema before you can use Scooter, as Scooter can detect your database schema changes during development.
The database is specified in a configuration file, config/database.properties. In that file, you can specify a default database. When you create an application (see Step 1 in Startup), Scooter automatically configures database connections based on the type of database you choose.
Three types of databases are specified: development, test and production. For this demo, we use blog_development as our default database.
In this demo, we use MySQL database. We create a blog database by executing the following script.
> mysql -u root < blog_development.sql
The content of blog_development.sql for now is as follows:
CREATE DATABASE blog_development DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; USE blog_development; CREATE TABLE posts ( id int(11) NOT NULL auto_increment, name varchar(255), title varchar(255), content text, created_at timestamp, updated_at timestamp, PRIMARY KEY (id) ) ENGINE=InnoDB;
Here we created a blog database and a table for posts.
Adding a post module
Now let's add a post module to the blog application. Scooter's scaffolding code generator creates a complete set of code for CRUD operations of a model in a single command.
> java -jar tools/generate.jar blog scaffold post
The scaffold code generator creates a controller, a model and some view files for post. It also adds posts as a resource in resources.properties. This allows us to access post in a restful way.
Scooter automatically detects and loads configuration changes. The newly added resource posts are already in the system. Clicking on the routes link on top of the screen, we can see all routes allowed by the applications including the posts related routes.
We can view posts now by accessing url: http://localhost:8080/blog/posts.
There is no post yet. Let's add one by clicking on the Add post link.
We save the post by clicking on the Create button.
We can edit a post by clicking on edit link on the post list screen.
The update was saved successfully.
Scooter supports i18n in all layers. We can even post in a different language such as Chinese:
You may delete a post by clicking on delete link. Or you can display posts in a paginated way by clicking on the Paged list link. The default pagination style is Yahoo style. You also get Window style pagination links on the same screen as an example.
Validation
We can easily add validations in our post model. Open up src/blog/models/Post.java and paste the following code:
public class Post extends ActiveRecord {
public void validatesRecord() {
validators().validatesPresenceOf("name, title, content");
validators().validatesLengthMaximum("name", 10);
validators().validatesLengthMaximum("content", 140);
//Our posts are twitter friendly.
}
}
Or you can use the built-in file browser to modify the Post.java file:
After you save the update, you will see this:
The above code indicates that
- A post must have a name, a title and some content.
- The name cannot be too long.
- And the length of the content must be twitter compatible.
Maybe in the future we want to forward posts to twitter automatically. Now let's see if it works. If I click the create button now, the application should remind me of entering name, title and content of my post.
Apparently validation works.
Adding a comment module
A blog engine should allow others to comment a post. Let's add a comment module.
First, we create a table for comments. This can be done by opening MySQL client and executing the following script:
CREATE TABLE comments ( id int(11) NOT NULL auto_increment, commenter varchar(255), body text, post_id int(11), created_at timestamp, PRIMARY KEY (id) ) ENGINE=InnoDB;
The comments table has a column for commenter's name, comment body and a timestamp of the comment. The post_id field is a foreign key that maps to the related parent post record.
Let's add a comment model to the blog application. Run the following command in your terminal:
> java -jar tools/generate.jar blog model comment
This command generates two files for us:
- src/blog/models/Comment.java: comment model
- test/blog/models/CommentTest.java: comment model test
Associating Models
We now have a comment model. Let's associate it with the post model so that a post can find all its comments.
Open Post.java and paste the following code:
public class Post extends ActiveRecord {
public void validatesRecord() {
validators().validatesPresenceOf("name, title, content");
validators().validatesLengthMaximum("name", 10);
validators().validatesLengthMaximum("content", 140);
//Our posts are twitter friendly.
}
public void registerRelations() {
hasMany("comments", "cascade:delete");
}
}
This is a simple way of saying a post has many comments.
We can declare the link in comment model too. Open Comment.java, and paste the following:
public class Comment extends ActiveRecord {
public void registerRelations() {
belongsTo("post");
}
}
The above code tells us that a comment belongs to a post. In this way, a comment object can find its associated post.
Scooter automatically compiles the code changes.
Configuring routes
Scooter uses routes to match HTTP requests to controller actions. Open config/routes.properties file and add the following:
resources.name.comments=\
controller:comments; parents: strict posts
Again we can use the built-in file browser to modify the route.properties file:
Here we created a nested resource of comments. The keyword strict means we can only access comments through a post.
Scooter automatically detects and loads configureation changes. It should have loaded our update in the routes.properties file. We can verify this from the routes view by clicking on the routes link on top of browser screen. You should notice that seven new nested routes are added.
Adding comment list view
We plan to display all comments of a post directly under the post's show screen. Now let's add some code in the post show page to display comments of a post.
Open posts/show.jsp file and paste the following code:
<h2>Comments</h2>
<div id="comments">
<%for (Iterator it = post.allAssociated("comments").getRecords().iterator(); it.hasNext();)
{
Object comment = it.next();%>
<p>
<b>Commenter: </b><%=O.property(comment, "commenter")%>
<b>posted on </b><%=O.property(comment, "created_at")%>
</p>
<p>
<b>Comment:</b>
<%=O.property(comment, "body")%>
</p>
<%}%>
</div>
Don't forget to import java.util.Iterator class on top of the jsp page.
Because we have associated comment model with post model, we can easily load a list of comments for a particular post with the allAssociated method. This is a typical example of lazy loading. We only load objects when we need them.
But directly using post object here is not null-pointer safe. Scooter provides helper methods to deal with this. Let's refactor this code.
We replace the following code line:
for (Iterator it = post.allAssociated("comments").getRecords().iterator(); it.hasNext();) {
with the following line:
for (Iterator it = O.iteratorOf(O.allAssociatedRecordsOf("post.comments")); it.hasNext();) {
This is better. Class O provideds many helper methods for working with an object. Again, you need to import the class in the import statement of this jsp file.
Adding comment entry view
We also need a form for submitting a comment. We plan to add comment entry form right below the comment list view. Let me add some code here into post's show.jsp.
<h2>Add comment</h2>
<%=W.errorMessage("comment")%>
<%=F.formForOpen("posts", post, "comments", "comment")%>
<p>
<%=F.label("commenter")%><br />
<input type="text" id="comment_commenter" name="commenter"
value="<%=O.hv("comment.commenter")%>" size="80" />
</p>
<p>
<%=F.label("body")%><br />
<textarea id="comment_body" name="body" cols="60" rows="10">
<%=O.hv("comment.body")%></textarea>
</p>
<input id="comment_submit" name="commit" type="submit"
value="Create" /> <input type="reset"/>
<%=F.formForClose("comments")%>
The class F is a form utility class with helpers methods on html form. Its formForOpen method is used to create an HTML form for a post instance. Again, we need to import this class com.scooterframework.web.util.F.
The above code shows that a comment will be submitted for a specific post. The resource name of the comment is comments, while the resource name of post object is posts.
There are two input fields for comment: commenter name and comment body. Method O.hv gives us html-escaped value of an object's property.
Creating a comment controller
We then need a comments controller to handle this comment entry form. We need a create action in the generated comments controller.
We can generate a comments controller with the following command:
> java -jar tools/generate.jar blog controller comments create
The above command creates two files, a controller and a view.
- src/blog/controllers/CommentsController.java
- webapps/blog/WEB-INF/views/comments/create.jsp
We can discard the view file as we don't need it. The generated create action in CommentsController is empty. Let's paste the following code in it:
package blog.controllers;
import static com.scooterframework.web.controller.ActionControl.*;
import com.scooterframework.admin.FilterManagerFactory;
import blog.models.Comment;
import blog.models.Post;
import com.scooterframework.orm.activerecord.ActiveRecord;
import com.scooterframework.web.util.R;
/**
* CommentsController class handles comments related access.
*/
public class CommentsController extends ApplicationController {
/**
* create method creates a new comment record.
*/
public String create() {
ActiveRecord post = Post.findById(p("post_id"));
setViewData("post", post);
ActiveRecord newComment = null;
try {
newComment = Comment.newRecord();
newComment.setData("commenter", p("commenter"));
newComment.setData("body", p("body"));
newComment.setData("post_id", p("post_id"));
newComment.save();
flash("notice", "Comment was successfully created.");
return redirectTo(R.resourceRecordPath("posts", post));
}
catch(Exception ex) {
log.error("Error in create() caused by " + ex.getMessage());
flash("error", "There was a problem creating the comment record.");
}
setViewData("comment", newComment);
return forwardTo(viewPath("posts", "show"));
}
}
In this create() method, we first find the post object, then put the post object to the HTTP request scope so that it can be found by view. This is needed as we are displaying all comments of a specific post.
The findById, newRecord, p, and setViewData methods are convenient methods. The p method returns the value corresponding to HTTP parameter post_id.
Then we create a new comment based on the submitted parameters. After we save the comment successfully to database, we notify the client by using the flash method and then redirect it to the show page of the post object.
The resourceRecordPath method can give us a restful url to the post such as /posts/10 for post with id 10.
If it fails to save, we log the error and also notify the caller about the failure by using the flash method. After that, we forward the request back to the show page of the post. That is where we initially submit the comment.
To make the code compile, we need to import Post model class.
Now let's give it try. Just go back to show page and type a comment to a post. It should work and you should see something like the following.
Adding validation in comment
Empty comment is meaningless. We can add validation in comment model to check if user has typed anything for the commenter name and comment body. We add the validatesRecord method in the comment model.
package blog.models;
import com.scooterframework.orm.activerecord.ActiveRecord;
/**
* Comment class represents a comment record in database.
*/
public class Comment extends ActiveRecord {
public void validatesRecord() {
validators().validatesPresenceOf("commenter, body");
}
public void registerRelations() {
belongsTo("post", "counter_cache:true");
}
}
Here we require that a comment must have a commenter name and comment body. Now if I click the create button without providing comment content, the application should remind me of doing that.
The validation apparently works.
Performance tuning
You should watch the screencast of creating a web blog in 15 minutes. Besides building a blog engine, the video also shows three ways to improve the site's performance:
- Picking which columns to retrieve through option ex_columns
- Counting number of comments per post by using counter_cache
- Deleting a post would automatically delete all its comments by using cascade:delete
Total code lines
The total number of code lines and files can be found with the following command:
> java -jar tools/codestats.jar webapps/blog/WEB-INF/src webapps/blog/WEB-INF/views/posts
-------------------------------------
code total files
-------------------------------------
java 145 233 5
jsp 248 292 5
-------------------------------------
summary 393 528 10
There are only have 145 lines of Java code, and 248 lines of jsp code. Most of which are actually generated. That's not a lot.
Testing
Scooter generates unit test and functional test classes located under blog/WEB-INF/test/ directory. Once you've written a test, you can open a terminal and run the following ant commands:
Unit testing:
> ant app_test_unit -DappPath=webapps/blog
Functional testing:
> ant app_test_fun -DappPath=webapps/blog
The above ant command will perform the tests and generate a report under blog/static/docs/tests/.
Creating war file
We strongly recommend that you use the expanded directory structure for deployment. But if you want to deploy a war file for a production site, you can do so by using the provided ant script.
> ant war -DappPath=webapps/blog
The above ant command will create a blog.war file under build/war/ directory.
Other deployment options
Besides using embedded Jetty web server that comes with Scooter, you may also use Apache's Tomcat. You may deploy a war to other web servers too. For more details, please refer to Scooter's deployment document.