Tutorial

A Simple Polling App

 

This tutorial was originally proposed to build an automatic admin backend in Python’s popular framework Django. The link is found here. It addresses such an application that you, as a web master, create a few polling questions, and let web visitors to vote. You will learn how to do that in Genelet, plus the following features:

  • To have two role groups of visitors: one for admins and one for public viewers;
  • To authenticate admin users;
  • To build public viewers’ API.

 

 

GO | Java | Perl

 

You are assumed to develop Java EE in NetBeans IDE & GlassFish web server in the MS Windows 10 environment.

 

1.1 Download Genelet

The Genelet Java framework can be downloaded from GitHub:

https://github.com/genelet/java

Normally, the https downloaded file is named java-master.zip. Unzip it. Move Genelet.jar and lib in dist to NetBeans’ workspace, usually located at $Env:HOMEPATH/Documents/NetBeans.

Open PowerShell, then cd to the workspace.  You should see

> ls
Genelet.jar
lib

among other NetBeans project files.

 

1.2 Preparing SQLite Database

Database SQLite will be used in this tutorial. You can download it from here. Create the database geneletdb in the workspace:

> sqlite3 geneletdb

Now you are in SQLite’s prompt. Copy the following schema and run it by ENTER:

BEGIN;

DROP TABLE IF EXISTS polls_choice;
DROP TABLE IF EXISTS polls_question;
CREATE TABLE "polls_question" (
 "id" INTEGER PRIMARY KEY NOT NULL,
 "question_text" varchar(200) NOT NULL,
 "pub_date" DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE "polls_choice" (
 "id" INTEGER PRIMARY KEY NOT NULL,
 "question_id" integer NOT NULL,
 "choice_text" varchar(200) NOT NULL,
 "votes" integer NOT NULL,
 FOREIGN KEY(question_id) REFERENCES polls_question(question_id)
);

COMMIT;

This will create two tables polls_question and polls_choice. Now, quit SQLite by Ctr-C. You should see the new database file geneletdb.

 

1.3 Run Help

Under PowerShell, run Genelet.jar without any argument

> java -jar Genelet.jar

This results in the usage:

Usage: Help [options] table1 table2 ...
 --root program root, default 'C:\genejava'
 --dbtype database type 'sqlite' or 'mysql', default 'mysql'
 --dbname database name, mandatory
 --dbuser database username, default ''
 --dbpass database password, default ''
 --proj project name, default to 'myproject'
 --scri script name, default to 'myscript'
 --force if to override existing files, default to false
 --angular if to include Angular 1.3 files, default to false

Then run it, passing the two Poll tables as the arguments:

> java -jar Genelet.jar -force -root "$Env:HOMEPATH/Documents/NetBeans/Tutojava" -dbtype sqlite -dbname "$Env:HOMEPATH/Documents/NetBeans/geneletdb" polls_question polls_choice

The following source files will be added to Tutoperl:

./src
./src/myproject
./src/myproject/Filter.java
./src/myproject/Model.java
./src/myproject/Servlet.java
./src/myproject/SerevletListener.java
./src/myproject/pollsquestion
./src/myproject/pollsquestion/Filter.java
./src/myproject/pollsquestion/Model.java
./src/myproject/pollsquestion/component.json
./src/myproject/pollschoice
./src/myproject/pollschoice/Filter.java
./src/myproject/pollschoice/Model.java
./src/myproject/pollschoice/component.json
./web
./Web/index.html
./web/WEB-INF/
./web/WEB-INF/config.json
./web/WEB-INF/web.xml
./web/WEB-INF/views
./web/WEB-INF/views/admin
./web/WEB-INF/views/admin/error.html
./web/WEB-INF/views/admin/login.html
./web/WEB-INF/views/admin/pollsquestion
./web/WEB-INF/views/admin/pollsquestion/update.html
./web/WEB-INF/views/admin/pollsquestion/topics.html
./web/WEB-INF/views/admin/pollsquestion/edit.html
./web/WEB-INF/views/admin/pollsquestion/startnew.html
./web/WEB-INF/views/admin/pollsquestion/insert.html
./web/WEB-INF/views/admin/pollsquestion/delete.html
./web/WEB-INF/views/admin/pollschoice
./web/WEB-INF/views/admin/pollschoice/update.html
./web/WEB-INF/views/admin/pollschoice/topics.html
./web/WEB-INF/views/admin/pollschoice/edit.html
./web/WEB-INF/views/admin/pollschoice/startnew.html
./web/WEB-INF/views/admin/pollschoice/insert.html
./web/WEB-INF/views/admin/pollschoice/delete.html
./web/WEB-INF/views/public
./web/WEB-INF/views/public/error.html
./web/WEB-INF/views/public/pollsquestion
./web/WEB-INF/views/public/pollsquestion/startnew.html

As a summary, you get Java classes in src, descriptor web.xml and configuration config.json in web/WEB-INF, JSP templates for admin and public in web/WEB-INF/views.

 

1.4 Create Project Tutojava

In NetBeans IDE, create a new project of type Java Web. Select Web Application with Existing Sources. Click on Next. In Location, browse to the newly created folder Tutojava. Take the pre-selected Project Name and Project Folder. Next. Use server GlassFish and version Java EE 7 Web, but put Context Path be empty. Next and Finish. Project Tutojava is thus created.

Move mouse over it, click on the left button, select Properties. Add Genelet.jar,  as well as gson and sqlite jars in lib, to Libraries.

Now the project is ready to go. Run it and Call the URL:

http://WEBSITE/myscript/public/html/pollsquestion?action=startnew

where WEBSITE is usually localhost:8080. If you see a page like

everything should have been set up correctly.

 

1.5 Login to Admin

We’d like to explain a little bit authentication here before moving on. Initially, Help generates two roles admin and public. The first one, admin,  manages the whole website who can access all actions and components. admin is protected by login. The second one, public, is a public role for whom no login is needed. By default, public is limited to access the startnew action of the first table in Help, which in our case is polls_question. Accessing to other URLs by public is denied with status 401.

The way to control admin’s login is defined in the Issuers block in web/WEB-INF/config.json:

"Issuers" : {
  "plain" : {
    "Default" : true,
    "Credential" : ["login", "passwd"],
    "Provider_pars": {"Def_login":"hello", "Def_password":"world"}
  }
}

By default, Help always generates the plain issuer for admin, with the built-in login and password being hello and world.

Now click on Enter Admin, which is the page for listing all polling questions

http://WEBSITE/myscript/admin/html/pollsquestion?action=topics

Because admin is protected, you have to login. On the next screen, input hello and world. You should login, and be redirected to the intended URL page:

Up to now, everything works correctly. Meanwhile, a self-certified ticket is issued and saved in your browser as a cookie. It will let you pass security checks for subsequent visits, until it is expired.

 

1.6 (optional) Use db Issuer

This is only optional. If you want to use a database table to authenticate admin, you can use db issuer. Under the Powershell, run

$ sqlite3 geneletdb

First, create the account table, polls_admin, for admin:

BEGIN;

DROP TABLE IF EXISTS polls_admin;
CREATE TABLE "polls_admin" (
 "id" INTEGER PRIMARY KEY NOT NULL,
 "login" varchar(16) NOT NULL,
 "passwd" varchar(200) NOT NULL,
 KEY login
);

COMMIT;

Second, add the manager hello:

INSERT INTO polls_admin (login, passwd) VALUES ("hello", "world");

You need to replace the above Issuers by the following db issuer in config.json:

"Issuers" : {
  "db" : {
    "Default": true,
    "Credential" : ["login", "passwd"],
    "Sql": "select login, id FROM polls_admin WHERE login=? and passwd=?" 
  }
}

Now all the accounts in polls_admin can login to admin.

 

1.7 Add Questions

Let’s create a poll question. Click on the Create New link. On the next action=startnew page, fill in the first question: Do you like iPhone 7 or 7plus? then Submit. If successful, a confirmation page will be shown as inserted.

Now go back 2 steps using browser’s Back button, to land on the list-all page again. Refresh it, you will see the newly-added question.

 

1.8 Add Choices

Let’s do the same to add poll choices for this question. Change the name pollsquestion to pollschoice in URL, we get the list-all page for pollschoice:

http://WEBSITE/myscript/admin/html/pollschoice?action=topics

Click on the Create New link.  The action=startnew page should look like:

Input the choice: iPhone 7plus, the question id: 1 (the id of Do you like iPhone 7 or 7plus?), the vote count: 0. Submit and you will get them inserted.

Go back 1-stop using browser’s Back. Fill in the other choice to the poll question: iPhone 7,  keep the questions id 1 and count 0, and Submit.

 

1.9 Open to public

You have one question and two choices in your system. Now let public visitors to vote which choice they like.

Since by default, all URLs except for startnew on pollsquestion are forbidden for public, you need to change ACL (Access Control List).

In Genelete, ACL and RESTful properties are managed by component.json, located in the same directory as Model.java and Filter.java. See Config and Manual for the full document.

Go to myproject.pollsquestion

In componen.json, replace

"topics" : {}

by

"topics" : {"groups": ["public"]}

Repeat the same change for component.json in myproject.pollschoice.

You don’t need to do any change in Model.java. Visitor public will see the same list as admin. By default, the same RESTful action, which is implemented as a class method in Model.java, will be shared by all roles.

For public to view the pages properly, you need new Views:

web/WEB-INF/views/public/pollsquestion/topics.html
web/WEB-INF/views/public/pollschoice/topics.html

which you can copy from the corresponding files from admin. Of course, you need to edit their contents so they are appropriate for public.

Here is final poolsquestion/topics.html:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Public Pages</title>
</head>
<body>
<h2>MYPROJECT</h2>

<h4>Welcome! 
You are role <em>${ARGS.g_role}</em>.
<a href='${ARGS.g_json_url}'>JSON View</a>
</h4><h3>List of Records</h3>
<table>
<thead>
<tr><th>Question_text</th><th>Id</th></tr>
</thead>
<tbody><c:forEach var='item' items='${LISTS}'>
<tr>
<td>${item.question_text}</td>
<td><a href="pollsquestion?action=edit&id=${item.id}">${item.id}</a></td>
</tr>
</c:forEach></tbody>
</table>

</body></html>

And pollschoice/topics.html:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Public Pages</title>
</head>
<body>
<h2>MYPROJECT</h2>

<h4>Welcome 
You are role <em>${ARGS.g_role}</em>.
<a href='${ARGS.g_json_url}'>JSON View</a>
</h4>
<h3>List of Records</h3>
<form method=post action="pollschoice">
<input type="hidden" name="action" value="update" />
<table>
<thead>
<tr><th>Question_id</th><th>Choice_text</th><th>Votes</th><th></th></tr>
</thead>
<tbody><c:forEach var='item' items='${LISTS}'>
<tr>
<td>${item.question_id}</td>
<td>${item.choice_text}</td>
<td>${item.votes}</td>
<td><input type=radio name=id value="${item.id}" /></td>
</tr>
</c:forEach></tbody>
</table>
<input type=submit value=" Submit " />
</form>
</body></html>

Now take a look at the public poll question and choice pages at:

http://WEBSITE/myscript/public/html/pollsquestion?action=topics
http://WEBSITE/myscript/public/html/pollschoice?action=topics

Just to verify, the choice page should look like

 

1.10 Vote!

So how to vote?

A vote is to add one more count to column votes in table polls_choice. You could achieve this by adding an extra voting method into Model.java (and component.json). Or, you can modify existing update for the purpose. Let do the later solution.

The default behavior of update is to update all fields, not limited to column votes in polls_choice. We should leave it untouched if the visitor is admin but make changes if the visitor is public. In Model.java, add

 @Override
 public Error update(List<Map<String,Object>> extras) throws SQLException, Exception { 
 String role = (String) ARGS.get("g_role");
 if ("admin".equals(role)) {
   return super.update(extras);
 }
 return this.do_sql("UPDATE polls_choice SET votes=votes+1 WHERE id=?", new Object[]{ ARGS.get("id") });
 }

So updating by public will add one more count to column votes.

Of course, update should also be open to and have a view for public. Make the ACL change in component.json:

"update" : {"validate":["id"], "groups":["public"]}

And copy the view from admin. 

Now, go to the public choice page:

http://WEBSITE/myscript/public/html/pollschoice?action=topics

and click on vote several times. Refresh. You should see that the voting counts are increasing.

Congratulation! You have almost finished the project in Genelet.

 

1.11 Override topics

What we meant “almost” is that the app does not work well with more than one questions. Go to admin‘s pages, add the second question: Where would you like go in New Year Eve? and attach three choices to it: Quebec City, New Orleans, and Stay at Home. Now, if public hits the choice page, she would see 5 choices: 2 from the first question and 3 from the second question!

The solution is to override the topics method so that only the choices belong to a specific question_id, which you can pass in the query, would be returned. Here it is:

 @Override
 public Error topics(List<Map<String,Object>> extras) throws SQLException, Exception { 
 String role = (String) ARGS.get("g_role");
 if ("admin".equals(role)) {
  return super.topics(extras);
 }
 LISTS = new ArrayList<>();
 return this.select_sql(LISTS, "SELECT * FROM polls_choice WHERE question_id=?", new Object[]{ ARGS.get("question_id") });
 }

Now, hit the public choice page with question_id=1. It only lists the 2 choices!

 

1.12 Logout

Just to hit:

http://WEBSITE/myscript/admin/html/logout

will logout you from role admin. You will be redirected to the front page, which is defined in myscript.json.

 

1.13 Public API

In Genelet, every time when a HTML is displayed, there is a JSON API as well.

For example, GET all poll questions at:

http://WEBSITE/myscript/public/json/pollsquestion

GET all choices belong to specific question_id:

http://WEBSITE/myscript/public/json/pollschoice?question_id=xxxx

To vote a choice with choice id, send PUT to

http://WEBSITE/myscript/public/json/pollschoice

with the http request body id=yyyy.

These three APIs should be good enough for a 3rd-party to access data and vote on your system.

 

(You can download the Tutojava source code here.)