Wednesday, March 3, 2010

GWT Basics

Okay, let's do a simple GWT interaction to see how it works. Note that this will depend on data being in the datastore, data that the site doesn't have yet. I can just create a simple GWT call to populate the datastore so what we're developing can return some results.

The servlet model was a page load model. Each page load called a servlet, which returned the HTML to display. So when designing your servlet based site, you thought in terms of the pages the site needs to display.

The GWT model is a remote procedure call model. Picture the client and server both containing objects that talk to each other (primarily the client objects making calls on the server objects to get information). The application user interface itself is contained in the client, and looks more like a regular Java application than a web page.

So we have to shift our thinking from the servlet model. To identify GWT interactions, think about the sorts of information the client might need to get from the server.

I'll start with something simple. I want the front page of the site to display a list of contests. So that list of contests is information the client needs to get from the server.

Think of that client/server interaction as a method call between objects. Let's say that I just want that call to return the names of the current contests. I might write the method interface like this:


String[] getCurrentContests ();


To convert that into Java code for your project, click on the project.client package and choose File->New->Interface. Next to "Extended interfaces", click the Add button. Type in RemoteService and choose to add that interface.

Don't forget to give your interface a name. I'll name my interface something like Contests.

Click Finish and the interface stub is created. It should look something like this:


import com.google.gwt.user.client.rpc.RemoteService;

public interface Contests extends RemoteService
{

}


We want to add an annotation before the interface line to say what the URL path is on the server. It'll end up like this:


import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("getContests")
public interface Contests extends RemoteService
{

}


Now we need to add in the actual method interface:


import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("getContests")
public interface Contests extends RemoteService
{
String[] getCurrentContests ();
}


That's it for creating the interface. What we've defined is the interface between the client and server.

What we still need to define is what the client does to call this interface, and what the server does to implement it.

More next post.

Wednesday, February 17, 2010

GWT Integration

This is the time when I want to start covering GWT integration.

I could start using GWT in small ways in the mainly servlet based application I have, but I figured I'd swing to the other extreme and start a new application based entirely on GWT. The techniques I show will still be applicable to a mainly servlet based application, but I'll also get to cover some GWT specific techniques (such as logging in via GWT).

So for this next series of posts, I'm creating a new web application in Eclipse. Everything from this point on until I post otherwise will be in reference to that new application.

It's worth looking at that basic application the plugin creates to understand a little about how GWT works.

The basic idea is that GWT widgets run as Javascript in the browser, and instead of loading a new web page to show results to the user you instead make a Javascript call to the server and then update only specific elements of the web page you're currently viewing.

Look in the project.server package and you'll find the server side of the GWT sample application. It looks a little different than what we're used to in terms of servlets. Instead of extending HttpServlet, it extends RemoteServiceServlet and implements GreetingService.

If the implements keyword is new to you, it means that the class implements an interface. An interface is (mostly) just a set of methods that the class must implement to be considered an implementation of the interface.

You can see the GreetingService interface in the project.client package. Look at that, and you'll see that the interface is just one method that the server object must implement.

GWT uses interfaces to define the set of possible calls that can go from client to server. In the sample application, there's only one possible call: greetServer that passes in a String. In a real application you'll have a larger number of possible calls in various interfaces.

Back to GreetingServiceImpl in the server package. It implements the greetServer method, and takes in the String the interface said it would need to take. This method looks a lot like a regular Java method, not a servlet. It does some error checking and throws an exception if there's a problem (you'll see in the interface that the exception is listed as being thrown). When there is no error, it simply returns a String, again as the interface specified.

There's no HttpServletRequest, no HttpServletResponse, just some odd calls to get information about the user's browser and the server itself.

At its most basic, the server implementation of interface methods must simply provide information back to the client. The display of that information is then up to the client code.

Now let's look at the project.html in the /war directory of your project. Skip down to the bottom of the file to see the actual HTML. Notice that it's a table with three table cells: one for them to type in a name, one for a submit button, and one for an error message to be displayed. But the table cells are empty!

If you run the project as a web application, you'll see an edit box and submit button. If you click submit without entering a name, you'll get an error displayed.

Where are these things coming from, if not the HTML?

Back in the project.client package, look at the file that's named the same as your project. Go down to the onModuleLoad method. This is the method that integrates the Java code with the HTML.

If you've done any Java Swing programming, the code here should look familiar. You create widgets in GWT the same way you do in Swing. The widgets themselves may be different, but the basic model of using them is the same.

Notice the line that reads:


RootPanel.get("nameFieldContainer").add(nameField);


That puts the widget represented by the variable nameField into the HTML in the HTML element that has the id nameFieldContainer. If you check the HTML file, that's one of those empty table cells. This is how the empty cells are filled with widgets.

Notice also that GWT uses the Swing methodology of creating widgets and adding them to the application, but then defining methods for what happens when the user interacts with those widgets.

If you haven't read Swing code before, this might seem pretty complicated. You've got some inner classes being defined, one of them anonymous. Without Swing experience, you might not even know how that all works.

I'd recommend finding a good Java Swing tutorial to get the basics down before trying to use GWT. Ultimately, though, it's the code defined for the submit button click that will make the call to the server greetServer method and receive the response back.

Tuesday, February 9, 2010

Encrypting User Passwords

The basic idea behind password encryption is that the encryption must be one-way. You should be able to turn plain text into encrypted text, but there should be no way to get back to the plain text only given the encrypted text. This ensures the security of the user's password on your system. To see if the password they've provided matches their original password, you encrypt the password and see if the encrypted versions are the same.

For more on this process, read this excellent post on How To Encrypt User Passwords. The post is great on concepts and rationale, but light on actual code, so I'll present some possible code here.

The basic steps:

1) Generate a string to encrypt by combining the password with a random number

2) Encrypt the string using a digest mechanism (e.g. like we did for the email verification code)

3) Store the encrypted password and the random number in the datastore

#3 should be familiar to you, so I'll provide a sample for 1 and 2. This isn't exactly what the web site I linked talks about, but builds on our previous MD5 hashing code for email verification. In fact, I'm going to retrofit the more secure mechanism back into the email verification code so that I have only one implementation of encrypting in a utility class.

I'll leave out some of the bits that we've already covered with email verification:


//Create the MD5 MessageDigest instance
Copy the code from previous examples here

// Get the random number
SecureRandom random = SecureRandom.getInstance ("SHA1PRNG");
int number = random.nextInt ();

// Build the string to encrypt and normalize it
String passAndNumber = "" + number + password;
passAndNumber = Normalizer.normalize (passAndNumber);

// Encrypt it
Use the MD5 digest and conversion to hex code from previous examples


What you'll end up with is a hex code string suitable for storing in the datastore as an encrypted password. You'll also store the random number you generated.

When the user tries to login, you'll do a similar process to see if they entered the correct password:


// Read the encrypted password and the random number from the data store
String encryptedPass = ....
int number = ....

// Create the MD5 MessageDigest instance
Already shown in email verification example

// Combine the password they entered with the random number from the data store
password = "" + number + password;

// Normalize the password they entered
password = Normalizer.normalize (password);

// Encrypt the password
Use MD5 and conversion to hex code shown before

// Compare
if (code.equals (encryptedPassword))
// Correct password
else
// Incorrect password


Note that I haven't tested this yet. There may very well be details to work out, but they shouldn't be major. The Normalizer class is only available in Java 1.6, so if you're using Java 1.5 you'll need to upgrade.

Also note that to be safe, when you call the String's getBytes method as part of creating the digest, pass in "UTF-8" as the encoding scheme so you know you'll be getting something consistent. This is different from what we did for the email verification code; I recommend refitting the email verification code to use the same mechanism as password encryption. This means you'll need to store two random numbers in the datastore...the one for the password itself, and the one for the email verification code.

Wednesday, February 3, 2010

Deploying To App Engine

It's a good idea to deploy your code to the actual AppEngine servers to test. Most features work the same between the local development environment and the servers, but now and then you'll run into something that doesn't.

Deploying is fairly simple, although it does require that you finally pick your application name on appspot.com, if you haven't already.

Step 1

Create your application. Go to the Google AppEngine site and log into your account. Click "Create an Application".

Type in the Application Identifier. This doubles as part of the URL for accessing your application at appspot.com, so it has to be unique and will be publicly visible.
Also enter the Application Title, and click Save.

Step 2

Back in Eclipse, we need to hook your project up to the application you just created. Right click on the project and go into Properties. Expand the Google section and click on App Engine. Under Application ID, type in the identifier you entered when you created your application.

Close out of the Preferences.

Step 3

Deploy! Right click on the project, go down to the Google menu and choose Deploy to App Engine. Enter the email address and password you used when you signed up for Google AppEngine, and click Deploy.

It should happen automatically, now you can open a web browser and go to your application id .appspot.com to see your application live on Google's servers.

Step 4

Everything blows up!

Well, maybe not. But if you're using RPXNow for authentication, you won't be able to get logged in unless you followed my advice back in the post on validating user emails. The reason is that part of the HTML in your sign in link is the location of your RPX results servlet. Right now, that probably still is what you had when you were developing it...e.g. http://localhost:8080/rpxresults.

Your application needs to change the http://localhost:8080 part based on whether it's running in development mode or in production mode. With a recent version of AppEngine, you'd use something like this:


if (SystemProperty.environment.get().contains("Development"))
  hostName = "localhost:8080";
else
  hostName = "gamesbyteens.appspot.com";


at an appropriate point where you fetch the host name for inclusion into the data model for your pages.

Friday, January 22, 2010

Implementing User Authentication

Earlier in the blog, I showed how to use a 3rd party OpenId service to allow people to login to your site using their Google account, their Facebook account, etc.

That isn't appropriate for every type of site, so now I'll show how to allow people to register at your site and create a login and password specific for your site.

I'd planned on using Spring Security for this, so that the authentication would be robust. But, Spring Security has some dependencies on the Spring framework. Supposedly getting it to work without Spring is possible, but not knowing either product it probably won't happen in the limited amount of time I have available (sorry for those of you searching for how to do just that).

I do, by the way, highly recommend Spring for industrial strength applications built on Java servlets. It just isn't appropriate for the class that I'm teaching. So, the authentication we'll roll here won't be industrial grade. But it'll be a start.

Let's talk for a moment about the characteristics of a good authentication mechanism.

First, it must be impossible to fool. For example, an authentication mechanism that simply avoided displaying members only links to non-members would not prevent non-members from typing those links in directly. The server must authenticate on any restricted page load.

Second, it must be easy to use. The more places we have to touch to implement authentication for a page, the higher the chance that we'll get it wrong. In particular, if we have to write Java code for each page we want protected, the chances are good that we'll get it wrong sooner or later.

We'll use Java Servlet filters to accomplish this. A filter is basically a piece of Java code that is run before a servlet is run. The filter can allow the servlet to run, or it can display a page itself and block the servlet. This happens without adding code to the servlet itself.

First step is to set up our web.xml file to use the filter (okay, so we haven't written the filter yet, but editing web.xml is the easy part so we'll get it out of the way). Add something like the following to your web.xml:


<filter>
<filter-name>AuthenticationFilter</filter-name>
<filter-class>omega.server.AuthenticationFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>AuthenticationFilter</filter-name>
<url-pattern>/members/*</url-pattern>
</filter-mapping>


This is like registering a servlet, in that you specify a URL pattern that triggers the filter. In my case, I used /members/*. This means that any URL under the members directory will trigger the filter. This will require changing my servlet URL mappings around a bit, so that any page that should be restricted to logged in users is mapped under /members, and any page that should be visible to anyone is not. So far for me, that's basically just the ProfileServlet, but I'll make a mental note to put future members only pages under /members.

Now we have to write the AuthenticationFilter class. This is similar to writing a servlet. Create a new class in your server package. There's no superclass for this one, but you will implement an interface. So in the new class dialog, click on the Add button to the right of the (empty) interface list, and type in Filter. You want the javax.servlet.Filter interface.

You'll get some Java code that leaves stubs for three methods: init, destroy, and doFilter. The primary purpose of the init is to save off the FilterConfig passed in, so create a private data member for doing that:


private FilterConfig filterConfig = null;

public void init(FilterConfig arg0) throws ServletException {
filterConfig = arg0;

}


You may not need that FilterConfig instance, but it can be used to get the ServletContext of the servlet that you're filtering.

The actual work of authentication will be done in the doFilter method. Here's the basic idea (not working code, but algorithm):


if (user authenticates correctly)
  chain.doFilter(request, response);
else
{
  User the response argument to generate error output, or redirect to a login form
}


So calling chain.doFilter passes the request on to the next filter in the chain, meaning that the AuthenticationFilter is happy with the request. If authentication fails, do something other than calling chain.doFilter.

The details of pulling a user's password from the datastore and comparing it to one passed into the HTTP request we've already covered. You should also be able to write the registration form and servlet to allow users to create their user account in the first place (and you can do verification of their email, too). You should also know what you need to know to have a "I forgot my password" link that will email a new password to them.

The one bit that we do still need to cover is storing passwords in an encrypted form. Storing user's passwords in plain text is bad form, so we won't do it.

Next post, encrypting passwords.

Tuesday, January 19, 2010

Signing A User Out

I realized that I haven't shown how to sign a user out yet. I have a link to /signout in my navigation template, but nothing mapped to that URL. This is where having a project plan (or at least a to-do list) would help!

So, I'll create a Signout servlet mapped to /signout. In that servlet, I need to get the session object and call the invalidate method on it. Afterward, I'll redirect the user to the front page.

It'll look something like this:


HttpSession session = req.getSession(false);

if (session != null)
session.invalidate();

resp.sendRedirect("/front");


and that's it. Not a big deal, but something we needed to implement.

Monday, January 11, 2010

Validating User Emails, part 2

The last two steps of validating user emails involved changing the profile servlet to display the link to resend the verification email when they already have an unverified email, and to send the verification email when they change their email.

Since both of those require sending the verification email, I'll put that in a utility class. I already have MailUtil, and this is a special sort of email, so I'll stick it there.

The main problem so solve before I can send the verification email is generating the unique verification code. I mentioned that MD5 is fine to use here, so we'll use that. The MessageDigest class in Java is what we use to generate the MD5 value, like this:


MessageDigest md5 = null;

try
{
md5 = MessageDigest.getInstance("md5");
} catch (NoSuchAlgorithmException e)
{
// Should never happen, but just in case
return "Unable to access md5 digest algorithm";
}

byte [] digest = md5.digest(user.getEmail ().getBytes());


Note the exception handling...that exception should never happen, because MD5 is a standard digest algorithm. But since the exception can be thrown, Java requires we handle it. This assumes the method returns a String that's an error message if something went wrong.

The byte array we get from the MessageDigest is not suitable for putting into an email yet. We need to convert it into hexadecimal first, like this:


String code = "";

for (byte element : digest)
code += Integer.toHexString(element & 0xFF);


If you're wondering about the use of "& 0xFF", it's because of the way that integral values are stored in Java. Think in terms of twos-complement sign extending from 1 byte to 4 bytes, and getting a bunch of extra 1s. We strip off anything extra by anding it with a single byte of 1s.

And if none of that makes sense, just believe me that you need it (or don't believe me, and remove it, and you'll see the difference in the hexadecimal generated).

So what I end up with is a hexadecimal string suitable for use as a verification code.

I also want to store the email I'll send as a Freemarker template, rather than have it embedded in Java code. This means a couple of things: I'll need to pass the Template object into the MailUtil method, and when I process the template I'll need to use a StringWriter so I can get the results as a String. The StringWriter serves in place of how we've been using PrintWriter for the templates.

I'll leave that, and finishing off the send verification email method as an exercise for the reader.

The last bit I'll mention here is that we'll need to include a link to the servlet we wrote last post that handles the verification clicks in the email. In the template we'll use something like this in the href arg of an a tag:


http://localhost:8080//validate-email?code=${code}


We'll use localhost for testing, but will want to change that when we're deploying the application. For those keeping track, we now have two places where we've embedded localhost and made notes to change it when we deploy.

Might be a very good idea to include a programmatic way to switch this depending on whether the application is running on the server or on the local machine. You can check that like this:


if (SystemProperty.environment.get().contains("Development"))


That if statement is true when you're running on the local host, and false otherwise. Use that if statement at appropriate points to change the host name used in URLs. The SystemProperty class used there is an App Engine provided one.

(Update: I just realized that the SystemProperty class doesn't exist on the App Engine version installed in the computer lab. So we'll likely update to the most recent version at some point, to keep things in sync with what students would have at home.)

Okay, so if you've done the exercises left for the reader, you have a MailUtil method that will send a verification email. We need to invoke that method in the profile servlet when the user changes their email, using code like this:


private String sendVerificationEmail (User user) throws IOException
{
Configuration config = ConfigFactory.get(getServletContext ());
Template template = config.getTemplate("emailVerify.ftl");

try
{
MailUtil.sendVerificationEmail(user, template);
}
catch (Exception e)
{
return e.getMessage ();
}

return "";
}


This is in the profile servlet, and will be called from the code that updates the email in the User. I'm not showing those parts, because they're basically the same as we did for updating the name.

The last bit we need is to display the link to resend the verification email when the user is editing their profile and their email isn't verified.

I'm going to add a query parameter to the ProfileServlet that will tell it to resend the verification email. It'll be something like this:


if (req.getParameter("resend") != null)
{
// Only resend if the email isn't verified already
if (! user.getEmailVerified())
{
message = sendVerificationEmail (user);
}
}


Now that the profile servlet will handle that query parameter, put the link in when the email isn't verified. I'll add the link to the ProfileLoggedIn template, just as a variable, e.g. ${resendLink}. Then in the profile servlet, when the email isn't empty but isn't verified, I'll set that variable to the resend link in the data model. When the email is empty, or verified, I'll set that variable to an empty string.

Since that uses only techniques we've already seen, I'll leave that as an exercise for the reader. Note also that I haven't tested the above, so it might very well blow up. When I get a chance to test it I'll update this post to reflect any fixes needed.

Saturday, January 9, 2010

Validating User Emails

First step in adding user email addresses is pretty basic.

We need to add an email field to the User object, and add it to the ProfileServlet form and processing, so that the user can enter the email address. We'll also need a field that tells us if the email has been validated or not. That field won't be user editable via the profile form.

I'll leave the first step as an exercise for the reader (it uses only techniques we've already seen), and move on to email verification.

The basic strategy with email verification is threefold:

1) The user enters their email address in their profile

2) The web site sends an email to that email address, with a link to click

3) The user clicks that link, taking them back to the web site

That way, we know that the user entered a valid email address they control.

Step 1 is pretty basic and uses techniques we've already covered. Step 2 requires sending email, which we'll look at. Step 3 requires having a page the link in the email goes to, where we can mark the email as verified.

Tying together step 2 and step 3 means we'll have to generate a unique code for this particular email verification. This unique code needs to be impossible to predict, so users entering false email addresses cannot simply guess the verification code. It also needs to be something that can be used as a query parameter in a URL. An MD5 hash is a good candidate, and we can check the database to make sure it's unique before sending the email.

Okay, lots to do!

1) Create a class to send the activation email

2) Create a servlet to receive the click from the activation email. Either mark the email as verified, or tell the user the verification email cannot be found

3) Change ProfileServlet to automatically send the activation email when a new email is entered, and to set the verified status to false

4) Change ProfileServlet to provide a link to resend the activation email (only display when the email isn't verified)

Also, the GAE Mail API allows an application to receive email. The email gets transformed into an HTTP request to a servlet, which can then save the email text in the datastore or take some other action based on the email. So you can do some pretty sophisticated email driven functionality (such as allowing people to send an email to your app to get a listing of contests in their area). For now, we'll use this feature just to handle when the verification email bounces.

Sending Emails

Since this is something we might have to do often, I'll write a MailUtil class that will hide a little of the complexity. Error handling will still be exposed, since we'll need to display an error message on the web page. Here's what the only method so far in the MailUtil class will look like:


public static void sendOne (String subject, String body, String name, String address, String fromName, String fromAddress) throws Exception
{
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);

Message msg = new MimeMessage(session);

msg.setFrom(new InternetAddress(fromAddress, fromName));
msg.addRecipient(Message.RecipientType.TO,
new InternetAddress(address, name));
msg.setSubject(subject);
msg.setText(body);
Transport.send(msg);
}


To put multiple lines in your message body, use the standard \n escape in the body text.

Note that running this in Eclipse doesn't seem to actually send email. Before this will work, you'll have to get an actual application created on Google App Engine for Java, and deploy the application. But you can safely call the method to send email when running it in Eclipse...it just won't send the email.

Verification Servlet

It might seem odd to create the servlet to respond to the user clicking the link in the email when we haven't written the code that generates that link yet. But it all has to be done in some order, so I'll do the processing of the click first.

I'll start by copying FrontPage.java and calling it ValidateEmail. I'll also add in web.xml mappings so that /validate-email calls the ValidateEmail servlet.

To recap the changes to User to support all this, I added in an email field (a String), an emailVerified field (a boolean that starts out as false), and a verificationCode field (String).

In the verification servlet, I want to look at the verification code passed in as a query parameter, and use it to look up the matching User in the datastore. The verification codes will be unique, so I'll only get zero or one User matching it.

If it's one User, I'll mark the email as verified. Otherwise, I display an error message. Here's a portion of that code.


Map root = new HashMap();

String code = req.getParameter("code");

query = pm.newQuery("select from omega.server.User where verificationCode == codeParam");
query.declareParameters("String codeParam");

try
{
results = (List<User>) query.execute(code);

if (results.size () == 0)
root.put ("errorMessage", "Verification code not found. Try resending the verification email.");
else
{
user = results.get(0);
user.setEmailVerified(true);
pm.makePersistent(user);
root.put ("errorMessage", "Email verified!");
}
}
finally
{
query.closeAll();
pm.close();
}

root.put("heading", "Email Verification Results");
root.put("loggedIn", loggedIn);
root.put("name", name);
root.put("userLevel", userLevel);
root.put("title", "GamesByTeens Email Verification");

Template temp = config.getTemplate("error.ftl");

All of that is added toward the bottom of the copy of the FrontPage servlet (the root.put statements show where the overlap is.

To test this I first load the page with a bogus verification code, to make sure it displays the error. Then I would need to force a valid verification code into the datastore. I can do this up at the start of the verification servlet itself, to make sure that the code I wrote works. Just don't forget to remove your testing code after you're done (or at least comment it out in case you need to use it again later).

This highlights an important part of any development, but web development especially. Make sure your code works on its own before you try to hook it to other parts of your site.

That's enough for one post. In the next I'll come back to the last two steps, both of which are modifications to the ProfileServlet.

Friday, January 8, 2010

Unique Display Names, Part 2

Okay, now we finally get to implementing unique display names.

In RPXResults.java, we have a section where we see if the user is already in the system. There are three possibilities now that we want unique display names:

1) The user is in the system, and has a unique display name.

2) The user is in the system, but hasn't updated their profile with a unique display name

3) The user is not in the system yet

For option 1, we can just do what we're already doing, redirect to the appropriate destination URL.

For option 2 and 3, we need to instead redirect to the profile page so they can enter their unique display name. Once they have entered a unique display name, we want to continue to the original destination URL.

That requires a change to RPXResults.java to redirect to the profile page in appropriate circumstances, and a change to ProfileServlet.java to redirect to the destination URL.

Note that we have a slight conflict here, because ProfileServlet.java wants to set a destination URL cookie, too, which will overwrite the one set by whatever page they were on when they clicked Sign In. Rather than complicate things by having multiple cookies for destination URLs, we'll just make ProfileServlet hide the complexity. It'll set its own cookie only when it is displaying its error template (which is the only time the Sign In link will show).

So we have these changes to make:

1) RPXResults must redirect to the profile page when appropriate

2) ProfileServlet must redirect to itself only when displaying the error template

3) ProfileServlet must require the user name be unique

4) ProfileServlet must, on a successful save of a unique user name, redirect to the destination URL

I won't show the code for 2, since that's just moving the two Cookie related lines into the if statement for showing the error template.

We'll start with the RPXResults changes. Right now we have logic like this:


if (results.size() == 0)
{
// create new user and store it
}
else
{
// Existing user, update needed values
}


We're going to change to something like this:


if (results.size() == 0)
{
// create new user and store it
// Redirect to /profile
}
else
{
if (user name is not set)
// Existing user no name, redirect to /profile
else
// Existing user name okay
}


For the purposes of making the code I post as simple as possible, I'm going to strip out the else portion where the existing user name is okay. The only reason to put code there is to update user records from previous versions during development, and now that we're trending more toward having the user enter their profile information rather than getting it from RPX, we won't need that as much.

I'll move the declaration of String destination = null up above the try block in RPXServlet, so that I can set an appropriate destination page manually if needed.

Here's the changed code for the case where the user doesn't exist in the system yet:


if (results.size() == 0)
{
user = new User ();
user.setIdentifier(identifier);
user.setName("");
user.setUserLevel (0);
pm.makePersistent(user);
destination = "/profile";
}


Note that I don't set the user name from the RPX data now. I'll let the user type in whatever they want in their profile.

The else part of that if is for when they're already in the system, and we just need to make sure that they already have a unique user name set:


else
{
user = results.get(0);

if (user.getName().equals(""))
destination = "/profile";
}


This assumes, of course, that ProfileServlet won't let them set a user name unless it's unique. So let's make that change now.

In ProfileServlet, we have code like this:


if (req.getParameter("submit") != null)
{
if (req.getParameter("name").trim().length () == 0)
message = "You must enter a name!";
else
{
user.setName(req.getParameter("name").trim());
pm.makePersistent(user);
}
}


We're going to add some more error checking, so that pm.makePersistent is only called when the user name is both non-empty and unique. To see if the name is unique, we need to build a new query based on the name.

Inside the else from above, I'll put:


Query nameQuery = pm.newQuery("select from omega.server.User where name == nameParam");
nameQuery.declareParameters("String nameParam");
List<User> nameResults = (List<User>) nameQuery.execute(req.getParameter("name").trim());

if (nameResults.size () != 0)


If the nameResults has some records, that still doesn't mean that the name is not unique. After all, maybe they pressed Submit without changing their name...then we'd get exactly one result, and it would be them. So we need to make sure the result we're getting isn't them.


if (nameResults.size () != 0)
{
if (nameResults.get(0).getId() != user.getId())
message = "That name is already taken, please try another";
}

if (message.equals(""))
{
user.setName(req.getParameter("name").trim());
pm.makePersistent(user);
}


And finally save the user name is it turns out that none of our error checking found problems.

That still leaves us with needing ProfileServlet to redirect appropriately. There's actually a bit of complexity here. We have a few cases to deal with:

1) They clicked on the Profile link, so our destination URL cookie is set to /profile

2) They clicked on Sign In and got here via RPXResults, so our destination URL cookie is set to something else, but their name wasn't unique so we need to stay here for now

3) They clicked on Sign In and got here via RPXResults, so our destination URL cookie is set to something else, and their name is okay so we can redirect

I'll focus my code on the else, just after the call to pm.makePersistent. It won't be pretty, but it'll work (this, by the way, is how unmanageable code evolves, with ongoing changes to existing code...it's a good idea to stop and refactor the code now and then to clean it up).

Just after the call to pm.makePersistent, I'll get the destination URL from the cookie and see if it's /profile. If it isn't, I'll redirect to it. It'll look something like this:


if (message.equals(""))
{
user.setName(req.getParameter("name").trim());
pm.makePersistent(user);

String destination = null;

Cookie [] cookies = req.getCookies();

if (destination == null && cookies != null)
{
for(int i=0; i<cookies.length; i++)
{
Cookie cookie = cookies[i];

if ("destinationURL".equals(cookie.getName()))
destination = cookie.getValue();
}
}

if (destination != null && ! destination.equals ("/profile"))
{
resp.sendRedirect (destination);
return;
}
}


Be prepared for there to be bugs with this, since we have enough cases that you should be worried about the stability of the code. Before going much farther, you really should redesign ProfileServlet to be more straightforward.

But, it should mostly work and shows the techniques to create the same sort of experience you're used to on well done membership sites.

Next up, I think, will be adding an email address to the user's profile, and requiring that their email address be verified before it's used by the system. This will introduce us to the Mail API.

Unique Display Names

I'm going to focus on new techniques in the coming posts, because the semester is starting.

I've decided to make display names unique in the system. I'm not especially happy with this, since it leads to funky display names like Dave5386. But, if I'm going to provide some sort of forum interaction eventually, I also need a way to differentiate between people who might decide to use the same display name.

I could simply require and display some context to the display name, such as "Jay, Muskingum University". But, providing information online for kids that might be used to locate them is not a good idea.

So, I'll go with unique display names. This means I need to change what I already have a bit.

RPXResults.java is what first puts the user into the datastore. Right now it's getting the user name from the RPX records. That isn't guaranteed to be unique. I could auto-generate a unique name, but then they might not go back and edit it.

So instead, RPXResults.java will, when a user logs in for the first time, redirect to the page for editing their profile. They will be required to update their profile with a unique name before doing using the rest of the site as a logged in user.

While I'm at it, I also want to modify RPXResults.java so that instead of redirecting to /front when a user logs in and they do have their profile info set, it redirects to the page they were trying to access. So if they save a bookmark on My Contests, and try to go to that after the session has expired, it'll take them to the login page and then to My Contests after they log in. That's more user-friendly, and best to do now before I have too many of those sorts of pages to edit.

Redirect to Target Page

We'll do the redirect to the target page first. This requires me to pass into /rpxresults the target page. Here's a page at RPXNow about passing query parameters through the token URL.

Unfortunately, that didn't work for me. The extra query parameter simply never came through to RPXResults.java.

Luckily we do have another option. We can store the destination URL as a cookie on the local machine and fetch it from RPXResults.java.

We'll do FrontPage.java first as an example of how to modify the servlets. Here's the code to save the destination URL as a cookie:


Cookie cookie = new Cookie ("destinationURL", "/front");
resp.addCookie(cookie);


I added this to just before the template is displayed. Now we need to modify RPXResults.java to use the value of this cookie as the destination URL. Here's the code that would replace the resp.redirect ("/front") line:


String destination = null;
Cookie [] cookies = req.getCookies();

if (destination == null && cookies != null)
{
for(int i=0; i<cookies.length; i++)
{
Cookie cookie = cookies[i];

if ("destinationURL".equals(cookie.getName()))
destination = cookie.getValue();
}
}

if (destination == null)
destination = "/front";

resp.sendRedirect(destination);


Now that RPXResults.java is set up, I can go back and modify the ProfileServlet and AdminServlet classes to add the cookie in the same way that the FrontPage servlet does.

Now if I remember to do that for every servlet I add after this, the user will always get to the page they wanted to get to, even if they have to log in first.

That's probably long enough for one post, so I'll do unique user names in the next.

Wednesday, January 6, 2010

Template Updates

A quick post about some template updates.

Following the pattern I used for navigation.ftl, I created header.ftl and moved everything up to and including the opening body tag into it. I replaced the page title with a Freemarker variable, so that each servlet could specify the page title (I then modified each servlet to set that page title). Every other template then included header.ftl at the top.

I did the same with footer.ftl, putting into it just the closing body and html tags for now. Later I'll have more to put there, and will have a central place to put it.

As an example of the end result, here's what my error.ftl looks like now:


<#include "header.ftl">

<h1>${heading}</h1>

<#include "navigation.ftl">

<p>${errorMessage}</p>

<#include "footer.ftl">


Nothing complicated here, just taking a moment to set the templates up for easier modification later on.