It's time to take a step back, now that we've gotten a set of tools in place to allow a user to log in and to allow us to write templates for dynamic web pages.
We need to decide what features we want on the site. What pages will there be, what links will be on the pages, etc. Doing a rough design now will make things move much faster in the next steps.
A web designer might work through this by actually designing web pages. I'm not a web designer, so I'm going to do it by thinking about the different ways people might use the site, and then just listing out the functionality that each page should have.
If you remember, the project I'm doing will be a community events site. I've generalized it a bit since that post, into more of a community portal. That allows me to add lists of recommended restaurants, etc, not just events.
People will be able to use the site either as a guest, or logged in. Some features will only be available to logged in users. I'll mark those features with (*) in the lists below.
Now keep in mind that this is an initial design of the site. I reserve the right to change this around at any point during future development, if it seems like a good idea. Designs are absolutely necessary for organizing your thoughts...but don't let them become straitjackets.
Front Page
First time visitors to the site need a brief explanation of what it is, while returning visitors probably want to see a list of upcoming events (or other recent activity on the site).
The features I'd want on the front page include: list of recent activity (this will be generated dynamically), a Sign In link, a Sign Out link (*), an Edit Profile link (*), a Create Event link (*), a Create Recommendation link (*), a Create Group link (*), an Events link, a Recommendations link, a Groups link, and a Members link.
There will be more eventually, but right now those are the main ones. Groups will represent organization in the community...churches, schools, businesses, government offices, etc.
The Members link will go to a list of members and the ability to view their profiles.
Edit Profile Page
This allows the logged in users to edit their profile information. I don't really know yet what all profile information I'll have, so for now this will just be the user's Name.
Create Event Page
This page will allow a logged in user to create a new event. Some of the info that will be needed: event name, description, time, date, location, and group affiliations. Maybe more later, but that's enough for now.
The location will allow us to generate maps to the event, and the location and date/time will allow us to pick up weather forecasts for the event.
Eventually we might want to categorize events based on age range, cost, etc, to allow users to search on those fields. But we'll save that for later.
Events Page
This page will list the most recent events, and allow browsing/searching events. A logged in user will be able to mark themselves as attending an event.
Recommendations Page
This page will list the most recent recommendations, and allow browsing/searching recommendations.
Groups Page
Allows the user to browse groups and join them.
Members Page
Allows the user to browse member profiles. Might need to be logged in for this, for privacy reasons.
Create Recommendation Page
Allows the logged in user to create a new recommendation. We need a good search algorithm to make sure we don't get duplicate recommendations (e.g. one for "Johnsons" and one for "Johnson's".
Create Group Page
Allows the logged in user to create a new group. Need the group name, description, address, etc.
That's enough to get started. The next several posts will deal with creating FreeMarker templates for those pages and designing the data model needed for each. By the time we're done with this we'll have a much better idea of what entities we'll need in the datastore.
Tuesday, June 16, 2009
Monday, June 15, 2009
Executing A FreeMarker Template
In our front page servlet, we need to execute the FreeMarker template we created.
We start by creating a Configuration instance. This only needs to be done once per application, so we can use a singleton pattern (remember the PMF class?) Only this time we don't have a utility class provided to us, so you'll have to write your own. Follow the PMF pattern and create a singleton for getting Configuration instances.
You'll end up with something like this (I've left off the package and import statements to save space):
Note that I have it looking for templates in WEB-INF/templates. That gets templates out of the way, which is generally a good idea to keep things tidy. Now would be a good time to right click on WEB-INF and create a new folder named templates, and to drag Front.ftl into that new directory.
In our front page servlet, we can now use the singleton class to get a Configuration instance.
Now we need to create the data model for the template. In the case of our simplified front page, the data model just has a boolean named loggedIn. Before we can do that we need to know if they're logged in or not.
If they are logged in, we'll have their user id in the session. So let's get the session and check.
I pass false into getSession this time because if there isn't already a session, I don't want to create one. If no session already existed, getSession(false) returns null.
We could very well have used getAttribute to get the actual user id and looked up the User record in the datastore, but this will do for the moment. Now that we have our loggedIn variable, let's create the data model:
That's it. A data model is nothing more than a map of values. You can nest maps inside the map to create objects with fields for the template to use.
Now we need to get the template:
If you named your template something other than Front, you'll want to modify that. Now that we have the template, let's execute it:
Note that printing the stack trace to the user's web browser isn't the best way to handle errors. We'll want to do something better than that later, but for now it'll serve.
We have one last piece before this will ever show "Sign Out". That's to cause the RPXResults servlet to redirect the user's browser back to the front page after the session has been set.
At the bottom of doPost in RPXResults, this should do it:
Assuming that /front is what you put in for the url mapping for your front page servlet.
You should now be able to run the application, click Sign In, go through the RPX process and end up back at the front page with the Sign Out link showing. This shows a very simple example of using templates to display pages with dynamic content. If our content is going to be updated according to user action, we'll use GWT widgets, but for simple content that stays the same once it's generated on the page, templates are a great option.
We start by creating a Configuration instance. This only needs to be done once per application, so we can use a singleton pattern (remember the PMF class?) Only this time we don't have a utility class provided to us, so you'll have to write your own. Follow the PMF pattern and create a singleton for getting Configuration instances.
You'll end up with something like this (I've left off the package and import statements to save space):
public class ConfigFactory
{
private static Configuration configInstance = null;
private ConfigFactory() {}
public static Configuration get(ServletContext context)
{
if (configInstance == null)
{
configInstance = new Configuration();
configInstance.setServletContextForTemplateLoading(context, "WEB-INF/templates");
configInstance.setObjectWrapper(new DefaultObjectWrapper());
}
return configInstance;
}
}
Note that I have it looking for templates in WEB-INF/templates. That gets templates out of the way, which is generally a good idea to keep things tidy. Now would be a good time to right click on WEB-INF and create a new folder named templates, and to drag Front.ftl into that new directory.
In our front page servlet, we can now use the singleton class to get a Configuration instance.
Configuration config = ConfigFactory.get(getServletContext ());
Now we need to create the data model for the template. In the case of our simplified front page, the data model just has a boolean named loggedIn. Before we can do that we need to know if they're logged in or not.
If they are logged in, we'll have their user id in the session. So let's get the session and check.
HttpSession session = req.getSession(false);
I pass false into getSession this time because if there isn't already a session, I don't want to create one. If no session already existed, getSession(false) returns null.
boolean loggedIn = false;
if (session != null)
loggedIn = true;
We could very well have used getAttribute to get the actual user id and looked up the User record in the datastore, but this will do for the moment. Now that we have our loggedIn variable, let's create the data model:
Map root = new HashMap();
root.put("loggedIn", loggedIn);
That's it. A data model is nothing more than a map of values. You can nest maps inside the map to create objects with fields for the template to use.
Now we need to get the template:
Template temp = config.getTemplate("Front.ftl");
If you named your template something other than Front, you'll want to modify that. Now that we have the template, let's execute it:
PrintWriter out = resp.getWriter();
try
{
temp.process(root, out);
out.flush();
} catch (TemplateException e)
{
e.printStackTrace(out);
}
Note that printing the stack trace to the user's web browser isn't the best way to handle errors. We'll want to do something better than that later, but for now it'll serve.
We have one last piece before this will ever show "Sign Out". That's to cause the RPXResults servlet to redirect the user's browser back to the front page after the session has been set.
At the bottom of doPost in RPXResults, this should do it:
resp.sendRedirect("/front");
Assuming that /front is what you put in for the url mapping for your front page servlet.
You should now be able to run the application, click Sign In, go through the RPX process and end up back at the front page with the Sign Out link showing. This shows a very simple example of using templates to display pages with dynamic content. If our content is going to be updated according to user action, we'll use GWT widgets, but for simple content that stays the same once it's generated on the page, templates are a great option.
Creating FreeMarker Templates
Before we can execute a template, we have to create one.
A template is basically just an HTML file with some extra bits for making minor decisions and displaying collections of data. When we create the template we decide what kind of data we'll need, but we don't know exactly what values will be assigned. That happens later, when the servlet gives the template values for the data it needs.
For the front page, let's say that we just need to know if the user is logged in or not. Eventually we'll want their name, too, but remember that we haven't put that into the datastore yet. So we'll settle for knowing if they're logged in.
In Eclipse, right click on the war directory in your project and choose New->File. Name it something like Front.ftl. The extension must be .ftl for the FreeMarker plugin to operate on it.
Now let's copy over the HTML from the project.html file, and remove the bits we don't need. We'll end up with something like this:
Except that you would have actually copied your RPX Sign In link and Javascript over from the project.html file.
Now picture that if the user is signed in, we want to display a Sign Out link instead of the Sign In link. Let's just go ahead and put a Sign Out link in there, just before the Sign In link.
We don't have a signout servlet yet, but that's okay for now. We can use an if statement inside a FreeMarker template, similar to in Java. Let's pretend that we have a variable called "loggedIn" that's true if the user is logged in.
If we do something like this:
Replacing, of course, the comments with the appropriate HTML, then the template will only display one of the links, depending on the value of loggedIn.
So how does the value of loggedIn get set? That's over in our front page servlet, and the next post.
A template is basically just an HTML file with some extra bits for making minor decisions and displaying collections of data. When we create the template we decide what kind of data we'll need, but we don't know exactly what values will be assigned. That happens later, when the servlet gives the template values for the data it needs.
For the front page, let's say that we just need to know if the user is logged in or not. Eventually we'll want their name, too, but remember that we haven't put that into the datastore yet. So we'll settle for knowing if they're logged in.
In Eclipse, right click on the war directory in your project and choose New->File. Name it something like Front.ftl. The extension must be .ftl for the FreeMarker plugin to operate on it.
Now let's copy over the HTML from the project.html file, and remove the bits we don't need. We'll end up with something like this:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link type="text/css" rel="stylesheet" href="Omega.css">
<title>Web Application Starter Project</title>
</head>
<body>
<h1>Web Application Starter Project</h1>
<!-- Put your RPX Sign In link here -->
<!-- Put your RPX Javascript here -->
</body>
</html>
Except that you would have actually copied your RPX Sign In link and Javascript over from the project.html file.
Now picture that if the user is signed in, we want to display a Sign Out link instead of the Sign In link. Let's just go ahead and put a Sign Out link in there, just before the Sign In link.
<a href="/signout">Sign Out</a>
We don't have a signout servlet yet, but that's okay for now. We can use an if statement inside a FreeMarker template, similar to in Java. Let's pretend that we have a variable called "loggedIn" that's true if the user is logged in.
If we do something like this:
<#if loggedIn>
<!-- Put Sign Out link here -->
<#else>
<!-- Put Sign In link here -->
</#if>
Replacing, of course, the comments with the appropriate HTML, then the template will only display one of the links, depending on the value of loggedIn.
So how does the value of loggedIn get set? That's over in our front page servlet, and the next post.
Installing FreeMarker
There are two pieces to install with FreeMarker. One is the server side code needed to allow the servlets to run template files. The other is the Eclipse plugin that provides support for writing template files.
Let's start with the plugin (you remember installing all those plugins earlier, right?). Go to this page and copy the link for the version of Eclipse you have (use the Stable Updates links). Install just the FreeMarker IDE from that site.
Restart Eclipse at the end, and we'll move on to the FreeMarker server libraries. Download the latest stable version at the FreeMarker download page. Unzip the file onto your desktop, and look for lib/freemarker.jar (the rest of the zip file is documentation that you may want to keep).
Copy freemarker.jar into the war/WEB-INF/lib directory of your project in Eclipse. Right click on the file, choose Build Path->Add to Build Path.
That's it, we can now both create template files and execute them from servlets. We'll see how in the next few posts.
Let's start with the plugin (you remember installing all those plugins earlier, right?). Go to this page and copy the link for the version of Eclipse you have (use the Stable Updates links). Install just the FreeMarker IDE from that site.
Restart Eclipse at the end, and we'll move on to the FreeMarker server libraries. Download the latest stable version at the FreeMarker download page. Unzip the file onto your desktop, and look for lib/freemarker.jar (the rest of the zip file is documentation that you may want to keep).
Copy freemarker.jar into the war/WEB-INF/lib directory of your project in Eclipse. Right click on the file, choose Build Path->Add to Build Path.
That's it, we can now both create template files and execute them from servlets. We'll see how in the next few posts.
A One Page Web Site
We'll now create a one-page web site that properly reflects the user's logged in status. Where our current first page has the "Sign In" link, that will be "Sign In" if the user is not logged in, or "Sign Out" if the user is logged in.
Our RPXResults servlet will do its thing silently, and then redirect back to the first page. We'll have to change the first page to be a servlet rather than a static HTML page, so that we can dynamically change it based on the user's logged in status.
Note that we could do the same thing with a static HTML page that included GWT components. We could in a GWT pane display the "Sign In" or "Sign Out" links, and use GWT calls to the server to see if the user is logged in or not. Eventually we'll get around to that sort of thing, but for now I'd like to keep it as simple as possible.
First step, change the main page of the site to be a servlet. Right click on war/WEB-INF/web.xml and choose Open With->Text Editor.
Enter the info for a new servlet (you remember how, right?) to be used as the main page. You don't need to have a servlet mapping for this servlet, but it might come in handy later to have it map to something like /front. Then copy the welcome-file-list section down to just before /web-app, and change the welcome file to be the name you gave to the servlet.
Now create the servlet (which you also remember, right?). Go ahead and put in the code to create the PrintWriter.
Let me describe the easy way (at least in the short term) to do this, which makes the code very ugly and the HTML almost impossible to maintain. Then we'll look at the hard way to do it, which makes the code nicer and the HTML easier to maintain.
The easy way is to output the HTML using our PrintWriter:
Etc, printing the entire HTML file using the PrintWriter. Notice the need to escape the double quotes inside the output using a backslash. This method is easy, because we don't have to work with anything but the servlet, but it makes it nearly impossible to easily change the HTML later.
The harder way (in the short run at least) is to use a templating engine. A templating engine is server side code that allows us to write the HTML in a file of its own, with a mini-programming language for making decisions. The servlet will execute that template file and provide any information it needs to make decisions. The result is the HTML that should be sent to the client.
There are lots of templating engines out there. I'm going to use FreeMarker, which integrates well with Java servlets and Eclipse.
So before we can use FreeMarker, we have to install it and the Eclipse plugin for it.
Our RPXResults servlet will do its thing silently, and then redirect back to the first page. We'll have to change the first page to be a servlet rather than a static HTML page, so that we can dynamically change it based on the user's logged in status.
Note that we could do the same thing with a static HTML page that included GWT components. We could in a GWT pane display the "Sign In" or "Sign Out" links, and use GWT calls to the server to see if the user is logged in or not. Eventually we'll get around to that sort of thing, but for now I'd like to keep it as simple as possible.
First step, change the main page of the site to be a servlet. Right click on war/WEB-INF/web.xml and choose Open With->Text Editor.
Enter the info for a new servlet (you remember how, right?) to be used as the main page. You don't need to have a servlet mapping for this servlet, but it might come in handy later to have it map to something like /front. Then copy the welcome-file-list section down to just before /web-app, and change the welcome file to be the name you gave to the servlet.
Now create the servlet (which you also remember, right?). Go ahead and put in the code to create the PrintWriter.
Let me describe the easy way (at least in the short term) to do this, which makes the code very ugly and the HTML almost impossible to maintain. Then we'll look at the hard way to do it, which makes the code nicer and the HTML easier to maintain.
The easy way is to output the HTML using our PrintWriter:
writer.println ("<html>");
writer.println ("<head>");
writer.println ("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");
Etc, printing the entire HTML file using the PrintWriter. Notice the need to escape the double quotes inside the output using a backslash. This method is easy, because we don't have to work with anything but the servlet, but it makes it nearly impossible to easily change the HTML later.
The harder way (in the short run at least) is to use a templating engine. A templating engine is server side code that allows us to write the HTML in a file of its own, with a mini-programming language for making decisions. The servlet will execute that template file and provide any information it needs to make decisions. The result is the HTML that should be sent to the client.
There are lots of templating engines out there. I'm going to use FreeMarker, which integrates well with Java servlets and Eclipse.
So before we can use FreeMarker, we have to install it and the Eclipse plugin for it.
Session Handling
Normal web pages are "stateless". This means that every time they're loaded is like the first. They don't keep any information around about the user loading them.
This doesn't work for web applications, where we do want to keep information about the user around from page to page. To carry information over from page load to page load, we need to create something called a "session".
A session is basically an id that gets sent to the browser and stored in a cookie. Future page loads on the same domain will send that session id along with the page request. The server then uses that session id as the primary key in a datastore lookup to load in any information you wanted to carry over from page load to page load.
By default, session support in the Java version of App Engine is not enabled. Right click on war/WEB-INF/appengine-web.xml and choose Open With->Text Editor. Just before the ending /appengine-web-app tag, put this line:
This makes the class HttpSession available to your servlets. The HttpSession class is how you'll set data on one page and get it on another page. Inside RPXResults.java, after we have the User instance, we can get the HttpSession instance like this:
The HttpSession class then has methods for getting to data you've previously set, and for setting data. Think of it like a map between a property name and a property value. Use the setAttribute method to set property values, and the getAttribute method to get property values.
Another useful method on HttpSession is setMaxInactiveInterval, where you specify the number of seconds the session will live without activity (e.g. the timeout). Pass in a negative value to make the session live forever (or at least until the user clears their cookies).
What do we need to store in the session? Not much, really. The datastore already will have our User records (and other entity records we'll add later), so in the session we just need enough to get to the User in the datastore.
So let's store the user's primary key in the session. From there we can look up the rest of their fields.
And that's it. In other code we can use getAttribute to fetch the user's id, and then use the datastore to fetch the full User instance for that id.
So the user is officially logged in at this point, although we don't have much of a web site yet to demonstrate that fact. So that's next, to develop a very simple web site that will reflect the logged in status of the user.
This doesn't work for web applications, where we do want to keep information about the user around from page to page. To carry information over from page load to page load, we need to create something called a "session".
A session is basically an id that gets sent to the browser and stored in a cookie. Future page loads on the same domain will send that session id along with the page request. The server then uses that session id as the primary key in a datastore lookup to load in any information you wanted to carry over from page load to page load.
By default, session support in the Java version of App Engine is not enabled. Right click on war/WEB-INF/appengine-web.xml and choose Open With->Text Editor. Just before the ending /appengine-web-app tag, put this line:
<sessions-enabled>true</sessions-enabled>
This makes the class HttpSession available to your servlets. The HttpSession class is how you'll set data on one page and get it on another page. Inside RPXResults.java, after we have the User instance, we can get the HttpSession instance like this:
HttpSession session = req.getSession ();
The HttpSession class then has methods for getting to data you've previously set, and for setting data. Think of it like a map between a property name and a property value. Use the setAttribute method to set property values, and the getAttribute method to get property values.
Another useful method on HttpSession is setMaxInactiveInterval, where you specify the number of seconds the session will live without activity (e.g. the timeout). Pass in a negative value to make the session live forever (or at least until the user clears their cookies).
What do we need to store in the session? Not much, really. The datastore already will have our User records (and other entity records we'll add later), so in the session we just need enough to get to the User in the datastore.
So let's store the user's primary key in the session. From there we can look up the rest of their fields.
HttpSession session = req.getSession();
session.setAttribute("userid", user.getId());
And that's it. In other code we can use getAttribute to fetch the user's id, and then use the datastore to fetch the full User instance for that id.
So the user is officially logged in at this point, although we don't have much of a web site yet to demonstrate that fact. So that's next, to develop a very simple web site that will reflect the logged in status of the user.
User Datastore Interaction
Okay, here's the complete code (in RPXResults.java) for creating users only if they don't already exist:
We could do more about populating User fields from the RPX profile, but this is reasonably complete otherwise. Does this mean the user is logged in?
Unfortunately, no.
By the end of the code, we have loaded the User instance from the datastore (or created it). To get the other pages in the application, and the GWT widgets, to know about the user we have to start a session.
Next post, sessions.
PersistenceManager pm = PMF.get().getPersistenceManager();
Query query = pm.newQuery("select from omega.server.User where identifier == identifierParam");
query.declareParameters("String identifierParam");
try
{
List results = (List) query.execute(identifier);
if (results.size() == 0)
{
User user = new User ();
user.setIdentifier(identifier);
pm.makePersistent(user);
writer.println ("Creating user for " + identifier);
}
else
{
User user = results.get(0);
writer.println ("Found user for " + identifier);
}
}
finally
{
query.closeAll();
pm.close();
}
We could do more about populating User fields from the RPX profile, but this is reasonably complete otherwise. Does this mean the user is logged in?
Unfortunately, no.
By the end of the code, we have loaded the User instance from the datastore (or created it). To get the other pages in the application, and the GWT widgets, to know about the user we have to start a session.
Next post, sessions.
Subscribe to:
Posts (Atom)
