MollyPages.org
"You were wrong case. To live here is to live." |
Pages /
Database /
Forms /
Servlet /
Javadocs
![]() ![]() |
First install/configure molly on your servlet capable webserver.
Create the following page and save it as (for example) hello.mp
anywhere within your website.
<html> <body> Hello world </body> </html>When you access this page, you will see
Hello World
in your browser.
Here is another example showing a molly expression in action.
<html> <body> 2 + 2 is [= 2 + 2] </body> </html>When you access this page, you will see
2 + 2 is 4
in your browser. See: All page tags
<form action="[=getPagePath(req)]" method="post"> </form>This will submit the form to the same page where the form is defined. This can be hard coded of course but by using getPagePath, the html does not have to be changed if the name of the page changes on disk.
For more information, see: getPagePath, getRealPath
request
, response
and
out
variables[[ java.util.Enumeration headers = request.getHeaders(); response.setHeader("foo", "bar"); out.print("hello"); ]]Shorter forms of request (
req
) and response (res
) are also implicitly defined and it's your
personal preference which one to use.
[[ clientRedirect(req, res, "somepage.html"); /*relative to current page*/ ]] --or-- [[ clientRedirect(req, res, "/somepage.mp"); /*relative to context root*/ ]]For more detail, see: Page.clientRedirect
Also see: WebUtil for various utility methods.
[forward somefile.mp]
[[startTimer();]] <html> <!-- ...stuff.. --> The page took: [=getTime()] milliseconds; </html>See: PageImpl
[[dbg(true);]] <!-- set to false to turn off debugging --> <html> <!-- ...stuff.. --> [[ bug(out, "some debug message"); debug(out, "some other message"); ]] </html>Note,
debug
(and it's shorter form bug
) are aliases
and both methods do the same thing. Also note, the out
parameter to
these methods (if you forget to pass out
, then you will get a
compile exception when you access the page).
Debugging at the page level is meant to interactively debug a particular page, when the page is being written/developed.
See: PageImpl
Logging makes use of the Log class. A
default logger (which writes to System.out
) can be obtained via
the Log.getDefault() method
or equivalently via WebApp.getAppLog()
method. Note: Servlet containers typically redirect System.out
to
either a console or a log file.
This logger has a default
logging level. This can be changed directly via Log.setDefaultLevel or via the configuration file for WebApp (if used). This
logger is available from a page via the log
variable. For example:
[[ log.info("user ABC authenticated"); ]]This will log
user ABC authenticated
in your log file. Another example:
[[ log.debug("got a database connection", "username:", "foo", ";sid", sid); ]]This will send output at the debug level to your log file. Debugging statements can easily be turned off in production by settting your loglevel to INFO or higher.
There is almost zero cost associated with simple method calls in Java (you can millions of method calls per second, even on a low end machine). All of the logging methods take an arbitrary number of parameters, so one can pass multiple strings as separate parameters to these methods and forego string concatenation.
[[ Cookie c = new Cookie("somename", "somevalue"); c.setMaxAge(-1); res.addCookie(c); ]]We can easily get a session id value, from the
newSessionID
utility method in class SessionUtil
[import import fc.web.servlet.SessionUtil; ] [[ String sid = SessionUtil.newSessionID(); Cookie c = new Cookie("somename", sid); c.setMaxAge(-1); res.addCookie(c); ]]
[[ Cookie[] cookies = req.getCookies(); if (cookies == null) { # got no cookies <br> # } else{ for (int n = 0; n < cookies.length; n++) { Cookie c = cookies[n]; # got cookie, name:[=c.getName()], value:[=c.getValue()] <br># } } ]]Note: we don't have to import the servlet classes (like Cookie). These are automatically imported in the molly page.
First, configure and use the GzipFileServlet
If the GzipFileServlet
is mapped to (say) /gzip
,
then, the following will include foo.js.gz
from the server.
<html> <head> <script src="/gzip?js=foo.js.gz"></script> /* foo.js will be available at this point */ </head> <body> /*...stuff...*/ </body> </html>
Molly pages provides a singleton application level class called WebApp. This class provides various convenience functions, including get/set methods to store application level data. It's optional to use WebApp of course (you can always use ServletContext), but in practice, WebApp is a great utility class. It is recommended to read the javadocs and configure it appropriately.
For example, in page: a.mp
[import import java.util.*; import fc.web.servlet.WebApp; ] Setting application level data [[ List list = new ArrayList(); list.add("hello"); list.add("world"); WebApp.getInstance("somename").put("foo", list); ]]
And in page: b.mp
[import import java.util.*; import fc.web.servlet.WebApp; ] Got application level data: [=WebApp.getInstance("somename").get("foo")]In fact, even WebApp.get/put is only really useful for variables and properties, not for instances of classes.
In molly pages, you do not have to use the un-necessary JSP "usebean"
tags all over your code. For accessing your own classes/objects/beans, you
simply define your own classes and put them in an appropriate package inside
your WEB-INF/classes
folder. You can then simply import and
instantiate your classes directly from any page or have the classes return a
singleton instance of themselves on any page.
Note: application level storage does not imply that this data is thread-safe. Typically, the user (and not the system) is responsible for thread safety by either globally wrapping access to thread unsafe objects (inside properly locked code) or by storing/using objects that are internally thread safe.
To keep things thread safe, we can instantiate non-thread safe objects (for example a Random object) and store them as instance variables. We would however have to synchronize access amongst all threads. For example:
[import import java.util.Random; ] [! Random rand = new Random(); /* not thread-safe */ !] [[ synchronized (rand) { /* one request at a time only */ # Random number: [=rand.nextInt()] # } ]]To make things faster, WebApp provides access to ThreadLocal versions of these objects.
[import import java.util.Random; import fc.util.*; import fc.web.servlet.WebApp; ] [[ ThreadLocalRandom rand = WebApp.getInstance("somename").getThreadLocalRandom(); if (rand.isNull()) { rand.set(new Random()); } ]] Random number: [=rand.get().nextInt()]See: WebApp, ThreadLocalCalendar, ThreadLocalDateFormat, ThreadLocalNumberFormat and ThreadLocalRandom
When a runtime exception happens, the stack trace shows the line number in
the java source file. You have to open the java source file to look at the
lines surrounding the error (and seeing this in context will help identify
where the error happened on the page). For example, in
page test.mp
:
<html><head></head> <body> [[ int n = 0; ]]; Dividing by zero gives me: [=42/n] </body> </html>This gives the following stack trace:
Error/Exception stack trace: javax.servlet.ServletException: java.lang.ArithmeticException: / by zero at molly.pages.test_mp.render(test_mp.java:23) at fc.web.page.PageServlet.service(PageServlet.java:216) ...We now open
test_mp.java
at line 23. molly always uses
the WEB-INF/molly_tmp
directory to store the generated
java files. We find the following:
22: out.print ("Dividing by zero gives me: "); 23: out.print (42/n); 24: out.print ("\n");We can now see where this error occurred and fix it on the molly page. Note, it's important to fix errors on the molly page and not touch the java source file. This is because java source file is automatically regenerated from the molly page.
First, put all protected (requiring login) content in a sec
directory. (sec stands for secure, you can use any name you like). So your
website directory tree now looks like:
document root | |-- index.html (the welcome page) |--Create alogin.mp
(the login page) |--sec
(secure directory) | |-- secure files
login.mp
and put it outside the secure area (as shown
above). This file looks like:
If you don't have an account yet: request a new account.
<h3>Please log in:</h3>
<form name=login action=/login
method=post>
Username: <input type=text name="username"><br>
Password: <input type=password name="password"><br>
<input type=submit>
</form>
Requesting a new account is not shown would link to a account
request page in a real application. Also, in a real login page,
there would be form layout, logos, images, etc., which are
all omitted here.
Create a access control servlet filter for the sec
dirctory. (this goes in your servlet web.xml
file).
<filter> <filter-name>JDBCAuthFilter</filter-name> <filter-class>fc.web.servlet.JDBCAuthFilter</filter-class> <init-param> <param-name>This filter uses the utility class JDBCAuthFilter (you can always write your own, of course). This filter redirects to the specified login page if the user is not logged in.login_page
</param-name> <param-value>/login.mp
</param-value> </init-param> </filter> <filter-mapping> <filter-name>JDBCAuthFilter</filter-name> <!-- maps a filter to a url pattern --> <url-pattern>/sec/*
</url-pattern> </filter-mapping>
Similarly, also in your web.xml file, map the /login
path
(used by form submit action parameter in login.mp above), as follows
<servlet> <servlet-name>Finally,LoginServlet
</servlet-name> <servlet-class>test.MyLoginServlet
</servlet-class> <load-on-startup>1</load-on-startup> <!-- just an example, tune for your system --> <init-param> <param-name>login_query</param-name> <param-value>select * from users where username=? and passwd=?</param-value> </init-param> <init-param> <param-name>password_hash</param-name> <param-value>MD5</param-value> </init-param> <init-param> <param-name>login_page</param-name> <param-value>/login.mp</param-value> </init-param> <init-param> <param-name>welcome_page</param-name> <param-value>/sec/index.mp</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>LoginServlet
</servlet-name> <url-pattern>/login
</url-pattern> </servlet-mapping>
MyLoginServlet
(which would be placed in WEB-INF/classes/test/
(since
it's in package test
), looks like:
packagetest
; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.sql.*; import fc.web.servlet.*; /** MyLoginServlet saves user and possibly other information to the session upon successful login. It relies onfc.web.servlet.LoginServlet
for most of it's work.This entire class is optional
, we could have just usedfc.web.servlet.LoginServlet
directly, if we wanted. This example/class presumes aUsers
table, ais_admin
column, etc. */ public final classMyLoginServlet
extendsfc.web.servlet.LoginServlet
{ public void onLogin(Connection con, String sid, String username, HttpServletRequest req, HttpServletResponse res) throws SQLException, IOException { boolean is_admin = false; users u = usersMgr.getByKey(con, username); if (u != null) { is_admin = u.get_is_admin(); } JDBCSession.getInstance().put( con, sid, "is_admin", Boolean.toString(is_admin)); } }
[import import java.sql.*; import fc.jdbc.*; import fc.web.servlet.WebApp; ] [[ Connection con = null; try { con = WebApp.getInstance("somename").getConnection(); ]] <html> <head> </head> <body> Got connection: [=con] </body> </html> [[ } //end try block finally { con.close(); } ]]Note, this is a simple and reliable pattern.
The connection is:
try
block at the very beginning of
the pagefinally
block at the very end of the page (after the closing </html> tag)try..catch..finally
blocks in the
page. But this outermost try..finally
block is uncluttered
with any other concern, it simple always gets and closes a connection.
See also: WebApp. Above, we obtained a connection via WebApp but we don't have to, we can always get a connection directly, via JDBC. For example, here is a bare-bones way of getting a connection:
[import import java.sql.*; ] [! String url = "jdbc:postgresql://127.0.0.1:5432/test"; String user = "postgres"; String password = "xxx"; !] [[ Connection con = null; try { con = DriverManager.getConnection(url,user,password); ]] <html> <head> </head> <body> Got connection: [=con] </body> </html> [[ } //end try block finally { con.close(); } ]]This bare-bones way is for illustration only and a connection pool (or the molly WebApp way shown above, which uses a connection pool internally) is recommended for real applications.
[import import java.sql.*; import fc.jdbc.*; import fc.web.servlet.WebApp; ] [[ dbg(true); startTimer(); ]] [[ Connection con = null; try { con = WebApp.getInstance("somename").getConnection(); ]] <html> <head> </head> <body> Got connection: [=con] [[ Statement stmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet rs = stmt.executeQuery("select 1 + 1"); if (rs != null) { /* fc.jdbc.QueryUtil is a simple utility class */ out.print("Got: " + QueryUtil.getRowCount(rs) + " rows"); QueryUtil.printResultSetHTMLTable(rs, out, QueryUtil.ResultSetPrintDirection.HORIZONTAL); } ]] </body> </html> [[ } //end outermost try finally { con.close(); } ]] [[ bug(out, "Page time: ", getTime(), " milliseconds"); ]]Also see: QueryUtil
Users
in our database, defined as:
Column | Type | ----------+------------------------+ name | character varying(255) | email | character varying(255) | password | character varying(255) |When we run our DBO generator, we end up with 2 classes. We tell our generator to put these classes in a package called
test
(for example).
Users.java UsersMgr.javaWe then compile these classes and put them, where our servlet container can find them. A good place to put them is:
/WEB-INF/classes/test
(since they were in package test
)
To use these classes in a mollypage, we now say:
[import import java.sql.*; import java.util.*; import fc.jdbc.*; import fc.web.servlet.WebApp; import test.* ] [[ Connection con = null; try { con = WebApp.getInstance("somename").getConnection(); ]] <html> <head> </head> <body> All the users in the system are: <table> <tr> <td>Name</td> <td>Email</td> </tr> [[ List list = UsersMgr.getAll(con); for (int n = 0; n < list.size(); n++) { Users u = (Users) list.get(n); # <tr> <td>[=u.get_name()]</td> <td>[=u.get_email()]</td> </tr> # } ]] </table> </body> </html> [[ } //end outermost try finally { con.close(); } ]]Note: the DBO method names
get_name
& get_email
in the example above, can differ based on your
preference in the DBO generator configuration file. For instance,
getName
& getEmail
could also have been
generated.
committed
immediately.
However, it is a better idea to use the same connection to do a unit of work and then commit or rollback the result depending on success/failture for the unit as a whole.
For this reason, methods in various DBO-generated manager classes take a
connection as the first parameter. For example, we may save 2 objects, say
Users
and UsersDetail
together. If both saves
succeed, we call con.commit()
and if either fails, we call
con.rollback()
. This ensures that there is no halfway write.
Either all data (or no data) is saved to the database.
[import import java.sql.*; import java.util.*; import fc.jdbc.*; import fc.web.servlet.WebApp; import test.* ] [[ Connection con = null; try { con = WebApp.getInstance("somename").getConnection(); con.setAutoCommit(false); ]] <html> <head> </head> <body> [[ Users user = new Users(); user.set_name("hiro protoganist"); UsersDetail userdetail = new UsersDetail(); userdetail.set_drug("snow crash"); UsersMgr.save(con, user); UsersMgr.save(con, userdetail); con.commit(); ]] </table> </body> </html> [[ } //end outermost try catch (SQLException e) { con.rollback(); } finally { con.close(); } ]]Note, we said above that new connections are in auto-commit mode. This is true only for unpooled connections. If we use WebApp to get a connection (as shown above), we will get a connection from a internal connection pool. Once we set the
con.setAutoCommit(true|false)
of that connection, the connection
will remember that setting (even when it's closed and reopened) until
another call to con.setAutoCommit(true|false)(if needed to change
the mode again) in the future.
Since we don't always know the state/history of the connection, it is
always a good idea to say: con.setAutoCommit(false)
at
the beginning of our page.
Enter JDBCSession, which allows you to save session information to a database. (this is quite fast, on the order of milliseconds, typically). This also allows one to trivially scale horizontally to as many front-end servlet/web servers as one needs.
The following example shows this in action:
[import import java.util.*; import java.sql.*; import fc.web.servlet.*; ] [[ Connection con = null; try { con = WebApp.getInstance("somename").getConnection(); String sid = SessionUtil.newSessionID(); JDBCSession session = JDBCSession.getInstance(); session.create(con, sid); ]] Session exists: [=session.exists(con, sid)]<br> Session info: [=session.sessionInfo(con, sid)] [[ session.add(con, sid, "apple", "orange"); Map map = new HashMap(); map.put("num", 123); map.put("phrase", "hello"); session.addAll(con, sid, map); ]] All values in session:<br>[=session.getAll(con, sid)] [[ } //end outermost try finally { con.close(); } ]]
As per the above item, if you store session data in the database, then you can horizontally scale your website easily.
Your default mode should be to look up everything, all the time, when developing a website. Premature optimization like lookuptable caching is a waste of keystrokes. When you want to move to production, then it may be useful.
[import /*...other imports here..*/ import fc.web.servlet.WebApp; import fc.util.cache.*; /*..*/ ] [[ Cache cache = WebApp.getInstance("somename").getDBCache(); List states = (List) cache.get("lookup_states"); if (states == null) { states = statesMgr.getAll(con); cache.put("lookup_states", states); } ]] <form> <select name="states"> [[ for (int n = 0; n < states.size(); n++) { state s = (state) states.get(n); # <option value="[=s.get_code()">[=s.get_name()]</option> # } ]] </select>See: Cache
It is a huge pain (and a good user-shedding device) to lose the already filled out information and show a blank form to the user. Molly allows one to easily maintain form state and avoid this problem. Here is a simple example:
[import import fc.web.simpleforms.*; ] <form method=post> fname: <input type=text name=fname value='[= State.text(req, "fname")]'><br> sex: <input type=radio name=sex value=male [= State.radiogroup(req, "sex", "male")]>male <input type=radio name=sex value=female [= State.radiogroup(req, "sex", "female")]>female<br> comment: <textarea name=comment rows=3 >[=State.textarea(req, "comment")]</textarea><br> mood: <select name=mood comment><br> <option value=dunno [=State.select(req, "mood", "dunno")]>select-a-mood</option> <option value=happy [=State.select(req, "mood", "happy")]>happy</option> <option value=sad [=State.select(req, "mood", "sad")]>sad</option> </select><br> agree: <input type=checkbox name=agree value=yes [= State.checkbox(req, "agree")]><br> <input type=submit> </form>Putting state preserving code in form tags (as shown above) is a common pattern. Molly pages gives you the State class (in the simpleforms package), which simplifies the backend logic for you.
Also: see these general (and essential) utility methods:
quoteToEntity, entityToQuote
For example:
[import import fc.web.forms.*; import java.util.*; import java.sql.*; import fc.web.servlet.*; ] [! Form makeForm() { //retrieve if we already made it before Form form = WebApp.getInstance("somename").getForm("test"); if (form != null) { return form; } form = new Form("test"); Text fname = new Text("fname"); fname.setSize(20).setMaxSize(64); form.add(fname); RadioGroup sex = new RadioGroup("sex"); sex.add(new ChoiceGroup.Choice("male", false)); sex.add(new ChoiceGroup.Choice("female", false)); form.add(sex); TextArea comment = new TextArea("comment"); form.add(comment); ArrayList moods = new ArrayList(); moods.add(new Select.Option("dunno", true)); moods.add(new Select.Option("happy")); moods.add(new Select.Option("sad")); Select mood = new Select("mood", moods); form.add(mood); Checkbox agree = new Checkbox("agree"); form.add(agree); WebApp.getInstance("somename").putForm(form); return form; } !] [[ Form form = makeForm(); FormData fd = form.handleSubmit(req); //maintain state ]] <form method=post> fname: [[ form.get("fname").render(fd, out); ]] <br> sex: [[ form.get("sex").render(fd, out); ]] <br> comment: [[ form.get("comment").render(fd, out); ]] <br> mood: [[ form.get("mood").render(fd, out); ]] <br> agree: [[ form.get("agree").render(fd, out); ]] <br> <input type=submit> </form>This form will automatically maintain it's state.
When a form is submitted, it is processed for errors. There can be field level errors and form level errors. This example shows the automatic rendering of both.
[[ Form form = makeForm(); FormData fd = form.handleSubmit(req); //maintain state form.renderError(fd, out); ]] <form method=post> fname: [[ form.get("fname").renderError(fd, out, "<br>").render(fd, out); ]] <br> sex: [[ form.get("sex").renderError(fd, out, "<br>").render(fd, out); ]] <br> comment: [[ form.get("comment").renderError(fd, out, "<br>").render(fd, out); ]] <br> mood: [[ form.get("mood").renderError(fd, out, "<br>").render(fd, out); ]] <br> agree: [[ form.get("agree").renderError(fd, out, "<br>").render(fd, out); ]] <br> <input type=submit> </form>
Typing all of the above together, here is a full example of the validation process.
[import import fc.web.forms.*; import java.util.*; import java.sql.*; import fc.web.servlet.*; ] [! Form makeForm() { //retrieve if we already made it before Form form = WebApp.getInstance("somename").getForm("test"); if (form != null) return form; form = new Form("test"); Text fname = new Text("fname"); fname.setSize(20).setMaxSize(64); form.add(fname); RadioGroup sex = new RadioGroup("sex"); sex.add(new ChoiceGroup.Choice("male", false)); sex.add(new ChoiceGroup.Choice("female", false)); form.add(sex); TextArea comment = new TextArea("comment", 3, 20); //3 rows, 20 cols form.add(comment); ArrayList moods = new ArrayList(); moods.add(new Select.Option("dunno", true)); moods.add(new Select.Option("happy")); moods.add(new Select.Option("sad")); Select mood = new Select("mood", moods); form.add(mood); Checkbox agree = new Checkbox("agree"); form.add(agree); //This will automatically add validation based on database schema //(very cool but advanced feature, study the javadocs) //UsersMgr.addValidators(f); WebApp.getInstance("somename").putForm(form); return form; } FormData processForm(HttpServletRequest req, Form form) throws ServletException, IOException { FormData fd = form.handleSubmit(req); Text fname = (Text) form.get("fname"); String fname_val = fname.getValue(fd); if (fname_val.length() < 3) { fname.addError(fd, "Please choose a longer username"); } /* Assuming a UsersMgr DBO object to talk to the database. Can use a manual JDBC statement here as well. */ if (UsersMgr.exists(con, fname_val)) { fname.addError(fd, "This user name already exists. Please choose another name"); } /* validation for other fields here as needed */ return fd; } !] [[ Form form = makeForm(); FormData fd = null; if (req.getParameter("submit") != null) { fd = processForm(req, form); } //form level errors (if any) form.renderError(fd, out); ]] <form method=post> fname: [[ form.get("fname").renderError(fd, out, "<br>").render(fd, out); ]] <br> sex: [[ form.get("sex").renderError(fd, out, "<br>").render(fd, out); ]] <br> comment: [[ form.get("comment").renderError(fd, out, "<br>").render(fd, out); ]] <br> mood: [[ form.get("mood").renderError(fd, out, "<br>").render(fd, out); ]] <br> agree: [[ form.get("agree").renderError(fd, out, "<br>").render(fd, out); ]] <br> <input type=submit name=submit value=submit> </form>You can use either the abstracted form or the simple form approach based on your personal preference/aversion to complexity.