Showing posts with label jdo. Show all posts
Showing posts with label jdo. Show all posts

Monday, June 15, 2009

User Datastore Interaction

Okay, here's the complete code (in RPXResults.java) for creating users only if they don't already exist:


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);
 }
 else
 {
  User user = results.get(0);
 }
}
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.

Searching For A User

Searching for a user in the datastore uses the Query interface. We get a Query by aking for one from a PerstistenceManager. There are several ways to populate a Query...I'll use the SQL like form:


Query query = pm.newQuery("select from project.server.User where identifier == " + identifier);


Note the use of the project.server.User notation to say which class you want to load. Change the project to be the name of your project.

If your first thought on seeing that wasn't, "What about SQL injection attacks?", then you should read this tutorial.

In this particular instance, the likelihood of suffering an SQL injection attack is low, since the data is coming from a request to RPX. Nobody can just load up the RPXResults servlet in a browser and inject an arbitrary identifier.

But, it never hurts to be safe, so let's use a parameter to lessen the risk:


Query query = pm.newQuery("select from project.server.User where identifier == identifierParam");
query.declareParameters("String identifierParam");


Note that parameters may lessen your risk of an SQL injection attack, but it depends on the implementation of JDO. I'd like to think Google is on top of its game with App Engine security, but we should be wary of relying on that for pages that are susceptible to SQL injection. When we get to a page that's more vulnerable, we'll look at ways to protect the page.

Now that we have a Query, we need to execute it and provide the value for the parameter:


try
{
  List results = (List) query.execute(identifier);
}
finally
{
  query.closeAll();
}


We can then use normal List methods for iterating over elements of the list of Users that has been returned. Note that since our identifier field is not a unique field, the List could theoretically have more than one element. But we'll write the code so that we only have one User per identifier, so the List will either have zero elements (for a new user) or one element.

We'll put the pieces together in the next post.

Creating A New User

Back in RPXResults.java, we've gotten our identifier and need to create a User. With JDO, that's a two-step process. First we create the User instance and populate it. Second we store that data to the datastore.

Creating the User is nothing new:


String identifier = info.getElementsByTagName("identifier").item(0).getTextContent();
User user = new User ();
user.setIdentifier(identifier);


We could search through the profile data to see if any of the various name fields were provided and use those to pass to user.setName, but I'll opt for the simple route of asking them for their name later. For now we'll leave it null.

Now for storing the User to the datastore. We need to get an instance of a PerstistenceManager from the factory:


PersistenceManager pm = PMF.get().getPersistenceManager();


Then we call makePersistent:


try
{
  pm.makePersistent(user);
}
finally
{
  pm.close();
}


The odd try/finally usage is worth a comment. There's no catch block needed, because there are no checked exceptions thrown by makePersistent. So why a try block in the first place? I'd guess it's because makePersistent can throw some runtime exceptions, and the finally block is a way to ensure the manager gets closed even if a runtime exception is thrown.

That saves the User to the datastore. In the next post we'll add in querying the datastore to see if the user already exists, so we only create the user if this is their first time using our application.

Storing Data In App Engine

My students will have had a database course by now, so they'll be familiar with (or at least exposed to) SQL and relational database design.

The bad news is that App Engine doesn't support SQL and it isn't a relational database. Instead it supports an object oriented database and you use Java Data Objects to interact with it.

The good news is that this means you don't have to fool with a database design, you just write Java classes in a certain way, and App Engine takes care of the rest.

The bad news is that Java Data Objects use annotations, a Java feature you won't have seen yet. Annotations allow you to add features to a Java class without using inheritance.

The good news is that JDO does allow an SQL like syntax for querying, so that database course will come in handy (or maybe that's more bad news).

Enough of the good news/bad news bit. Let's dive into JDO.

An object that is stored into the database is called an entity. Entities have properties and a unique key (which can be autogenerated by App Engine, if you like). Oh, and we'll start to use the JDO terminology for database, which is datastore.

To specify a class as an entity, use the annotation @PersistenceCapable on the line before the class definition. For example, we'll need an entity to store information about a user (create this class in Eclipse in your project.server package):


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class User
{
}


That marks User as an entity that can be stored in the datastore. We also have to mark each field in User that we want stored in the datastore as @Persistent, and one of them as a @PrimaryKey.

I know that I'll need an autogenerated user id as a primary key (because the identifier returned by RPX is too long to be suitable as a primary key), and I'll need some other basic info. Here's what I came up with as a first pass:


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class User
{
  @PrimaryKey
  @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  private Long id;
  
  @Persistent
  private String identifier;
  
  @Persistent
  private String name;
}


The valueStrategy argument on the id means it'll be automatically generated for me when I create new Users. Since the fields are private, I'll need public get methods for them, and set methods for the name and identifier. I won't show those here, but go ahead and add them to your version (this is a good time to figure out how to use Eclipse's Refactor->Encapsulate Field option to save yourself the typing).

To work with the datastore itself, we need an instance of PersistenceManagerFactory. The Google docs suggest using a singleton pattern for this, since the class is expensive to instantiate. Go ahead and use their PMF class as is in your project.server package.

In our RPXResults servlet, we'll need to use the identifier RPX provides to see if we already have the user in the datastore. If so, we'll load the existing data. Otherwise, we need to create the User instance and store it to the datastore.

We'll start in the next post with creating a new User and storing it to the datastore.