I describe the mypages framework here because I designed and implemented it from scratch and I'm quite proud of it. I've never written anything better (as of May 2001). It's modular, flexible, easy to modify, and has many great systems which make adding new functionality to it relatively painless.

The working system can by seen at http://my.ign.com.

mypages/registration framework overview

The goal of the framework is to provide a registration system which has a modular way to add services which may require registration while hiding as much of the busywork from those services as possible.

The framework was developed with flexibility in mind. An important aspect of that flexibility is that engineers who need to add new capabilities to the system don't need a full knowledge of the entire framework in order to be flexible. Furthermore, the framework provides convenient interfaces to accessing information from the DB. The result is that many things can be controlled simply by adding or modifying entries in database tables.

There are a number of important systems used throughout the mypage/registration framework.
Those systems are:

contexts

There are many things which can vary between different applications which make use of the mypages framework. For instance: the database that's used, the type of information which is stored in the database for each user, the format and content of the cookie which is used to identify the user, etc. Also, if we wish to run multiple applications on the same system in the same JVM then any static variables used will need to be application independent.

Contexts solve all of these problems. There exists exactly one instance of a context per application. The context object is used by everything in the framework to do any of the activies that vary between applications. Furthermore, the context object can be used to store "static" objects. Since there's only one instance of the context per application, every value stored in the context will exist only once per application and will not interfere with the static values stored for other applications. And yes, storing values this way is multithreading safe because the context object is also used to get locks which can be synchronized on when setting and getting values from the context.

If we didn't use contexts there would be a lot of code that would need to be modified if we wanted to set up a different registration system for a different DB where the User object is different and the user cookie is different. As it is, only a relatively small number of new classes need to be implemented to use the framework in a new application.

actions

When you want to add a behavior to the mypages you might think that you would want to create a servlet. You would be wrong. Instead you subclass one of the Action interfaces and add the name of the class to the actions.xml file. The Action interface requires that you implement the getKey() function. So, if getKey() returns "simpsons" then then the doAction() method of the action will be executed when action=simpsons is specified as a parameter to the main servlet.

There are a number of things about actions which make them better than implementing a separate servlet:

  1. A context object is passed to the doAction() function so that the action can be coded without application dependencies.
  2. The ActionRequiring_req_res_User_tempParams has a "params" parameter passed to it. That parameter will contain the same name-value pairs regardless of how that action was called. If the user wasn't logged in when they tried to access the page by going to "http://my.ign.com/my/sb?action=simpsons" then the user will be sent to the login page first. Once the user logs in the action will be executed. If instead of logging in the user goes to the registration page because they don't have an account then they'll go through registration then at the end of registration the simpsons action will be executed. If you tried to grab parameters from the HttpServletRequest object then you'd get different values if the user went to the login page before the action was executed (you'd get the parameters for the login page, not the parameters that were originally specified).
  3. A DB conn is checked out from a central connection pool and passed to the action. Losing connections is bad because if a user forgets to check a connection in it's necessary to look everywhere conns are used to figure out which one forgot to check the connection in. This way there's only one central database connection checked out.

services

Currently there are 3 types of services. These are: Opt_boolean, Opt_multistate, Box. An Opt_boolean service usually represents a checkbox. An Opt_multistate service usually represents a group of radio buttons or a <SELECT> list. A Box service is a section of a page which is included from somewhere else (usually a JSP, but it could be a servlet or an html file or anything that can be referenced by a local URL).

Services are controlled primarily from the DB:

The "serviceavailable" table in the DB is used to control which services are available to a particular page and pagepart for a particular network and in what order those services should be listed. The com.snowball.ureg.service.ServicesAvailable class encapsulates services. There are other objects which load services from the DB, but the only interface to getting services is ServiceAvailable. Furthermore, the SERVICERESTRICTION table specifies some restrictions for particular services (minage, maxage, startdate, enddate, country, etc.) and the ServicesAvailable object, given a pagenum, pagepartnum, network, and User object, determines which services to return.

pages, pageparts, and validators

Pages (which implement com.snowball.ureg.page.SubmittablePage) and pageparts (which implement com.snowball.ureg.page.PagePart) are the Controller part of the Model-View-Controller (MVC) organization while the corresponding JSP is the View. Pageparts are controllers that represent a subset of the fields on the page. For example, there's a pagepart that represents an address (com.snowball.ureg.AddressPagePart) and a pagepart that represents a birthday (com.snowball.ureg.BirthdayPagePart).

Pages are usually derived from com.snowball.ureg.page.Page_MultiPart. In the constructor of each page object, the pagepart objects which make up that page are added to the Page_MultiPart's list using the add() function.

When Page_MultiPart.setPageValsTo() is called it tells each pagepart to set their values to the page but also then gathers up the error messages from all the pageparts and sets the attribute "errMsgsMap" to the HttpServletRequest. The errMsgsMap is a java.util.Map object. It won't ever be null. If there are no error messages then the Map will just be empty. The Map is a mapping of field names to error messages.

When getValsFrom(HttpServletRequest) is called it calls getValsFrom(HttpServletRequest) on each pagepart. When validate() is called on the Page_MultiPart it calls validate() on each pagepart. The Page_MultiPart.hasErrs() function returns true if any pagepart has errors.

An extremely common pagepart is a pagepart which represents a set of services (com.snowball.ureg.service.ServicesPagePart). The services pagepart takes parameters like pagenum, pagepartnum, network, and User. It then uses the ServicesAvailable object to get a list of services that it should control. It automatically retrieves the values for all the services in its list when getValsFrom() is called. When setPageValsTo() is called it sets an attribute to the page which is a java.util.List of its services. The name of that attribute is the name specified for the serviceListName parameter in the constructor of the ServicesPagePart.

One additional capability of the ServicesPagePart is that it executes silent service actions (com.snowball.ureg.service.SilentServiceAction) at various points. (The silent service actions are specified in the silentServiceActions actionList in the actions.xml.) The SilentServiceAction interface is simply a set of hooks which are called by the ServicesPagePart.

To have a SilentServiceAction hook to service number 76 (this assumes that a service exists in the SERVICE table which has 76 as its ID) then have the getKey() function of the SilentServiceAction return the string "service76".

The SilentServiceAction can control whether or not a service will display. For instance, if you don't want a service to be displayed if they selected a particular interest during registration then implement the SilentServiceAction and in the serviceIsAvailable() function return false if the User has selected that interest. The serviceWasSavedToUser() function is called once the page was successfully submitted and values were saved to the User. That function is where you would send out an email to the user for an Opt_boolean service where the user checked the checkbox to sign up for something.

NOTE: If you implement serviceWasSavedToUser() make sure that you check the value of the Service passed to it to determine if the user actually selected the correct value for that service. The serviceWasSavedToUser is called whether or not the user selected the checkbox or clicked on the radio button. If the Service is an Opt_boolean you can do this by looking at the value of ((Opt_boolean)svc).getCurState() which will be true if the user selected the checkbox and false if the checkbox was not selected.