An Example Webpage Request - Hillsborough-Transit-Authority/onebusaway-application-modules GitHub Wiki

The technology stack for OneBusAway is pretty deep. Here we describe a typical web request from start to finish, touching on all the different pieces of the response pipeline.

Consider the following request:

http://onebusaway.org/where/standard/stop.action?id=1_25140

This is the standard departures page for stop # 25140 for the agency with id 1.

The Servlet Container

When a request comes in, a Tomcat servlet container receives the request. The onebusaway-webapp module has all the code that power the web interface for OneBusAway, including the code for setting up our webapp. Our webapp is specifically configured with the standard web.xml file located at

src/main/webapp/WEB-INF/web.xml

Struts

The next step of the pipeline is Apache Struts 2.x, a web MVC framework. A filter is setup in the web.xml file to pass every incoming request to Struts:

<filter>
    <filter-name>action2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
<filter-mapping>
    <filter-name>action2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

The Struts action handling code takes over at this point. Struts actions are configured in the following file:

src/main/java/struts.xml

The struts.xml file is the MVC glue. Specifically, it maps incoming HTTP requests to Java classes (called Actions in Struts) that will service the request and then maps the response to a rendering component (JSP in our case).

Historically, Struts actions each had to be mapped directly in the struts.xml file, which can become quite tedious to maintain for large projects. Instead, we are using the Struts Convention plugin, which uses Java @annotations to configure action classes in place. Actions are automatically scanned on the classpath with the following configuration option:

<constant name="struts.convention.package.locators.basePackage" value="org.onebusaway.webapp.actions" />

If we browse to the org.onebusaway.webapp.actions, we see all the Action classes for the OneBusAway webapp, including org.onebusaway.webapp.actions.where.StopAction, the action that handles the stop request.

The Action Handler Class

If we go examine the class, we'll see it's just a simple Java bean with getters and setters and one entry point method:

public String execute() { ... }

The execute method is the Action entry point and it's called automatically by Struts. Before it does, however, Struts automatically maps any request parameters of the URL to properties of the Action bean. Thus, the id=25140 request parameter gets mapped to the public void setId(List<Integer> ids) setter.

The Spring IOC Container

Note that in addition to bean properties automatically populated by Struts from the URL, there are also bean properties automatically setup by Struts and its integration with the Spring framework. Spring is an Inversion Of Control (IOC) container that connects together much of the business logic in OneBusAway. Consider:

@Autowired
public void setTransitDataService(TransitDataService service) {
  _service = service;
}

The @Autowired annotation tells Spring that it should automatically supply the appropriate services classes to the bean. In this case, that would be the TransitDataService service class, which is the main entry point into business logic from the web control layer.

At this point, once the Action bean has been properly initialized, the execute method is finally run. The execute method make a call to the TransitDataService to retrieve the StopsWithArrivalsBean bean, which includes information about the requested stop plus all the predicted arrivals at that stop.

_result = _service.getStopsWithArrivalsAndDepartures(ids, new Date());

TransitDataService and FederatedService

The org.onebusaway.transit_data.services.TransitDataService, defined in the onebusaway-transit-data module, hides most of the implementation detail of collecting actual stop and arrival information, allowing a clean separation between the UI layer and the data layer. The separation is so complete that the implementing class for the TransitDataService might actually be running on another server!

When a class is made to TransitDataService, a local proxy actually handles the request. That proxy is typically an instance of org.onebusaway.federations.FederatedService, defined in the [ModuleFederations onebusaway-federations] module. A FederatedService is once that can look at the arguments on an incoming method call and route them to an appropriate handler on a local or even remote machine. In our case, we break transit data up into distinct geographic regions (Puget Sound vs Portland, for example) and have a TransitDataService handler for each. The FederatedService proxy looks as the method arguments (lat-lon coordinates, agency ids, etc) to decide which TransitDataService is best equipped to service the response.

For method calls across JVMs and machines, we use [http://hessian.caucho.com/ Hessian] as our RPC serialization method. For details on how a TransitDataService is exported using Hessian, check out the [ModuleTransitDataFederationWebapp onebusaway-transit-data-federation-webapp] module.

TransitDataFederation

The actual implementation for TransitDataService that does the heavy lifting of processing a request can be found in the [ModuleTransitDataFederation onebusaway-transit-data-federation] module. Specifically, the org.onebusaway.transit_data_federation.impl.federated.TransitDataServiceImpl is the implementing class.

Here things get pretty complex, as the onebusaway-transit-data-federation defines a lot of service interfaces and implementations that handle the bulk of the business logic in the module. Generating the response to the getStopsWithArrivalsAndDepartures(...) call involves calls to a number of services:

  • StopWithArrivalsAndDeparturesBeanService.getArrivalsAndDeparturesForStopIds(...)
    • StopBeanService.getStopForId(...)
      • GtfsRelationalDao.getStopForId(...)
      • RouteBeanService.getRouteForId(...)
    • ArrivalsAndDeparturesBeanService.getArrivalsAndDeparturesByStopId(...)
      • StopTimeService.getStopTimeInstancesInTimeRange.(...)
        • TripPlannerGraph.getStopEntryForId(...)
        • CalendarService.getServiceDatesWithinRange(...)
      • StopTimePredictionService.applyPredictions(...)
    • NearbyStopsBeanService.getNearbyStops(...)
      • GeospatialBeanService.getStopsByLocation(...)

At this point, your eyes have probably glazed over, and I haven't even mentioned the implementing classes for all those services. Why so many service interfaces? Why the insistence on separation into an interface and implementing class when there is often a one-to-one mapping? I have a number of reasons:

  • Allows me to easily insert caching, logging, and other aspects without modifying the implementing class using Java interface proxies
  • Easier unit testing
  • Implementation detail hiding

Unfortunately, the result is that it can be tricky to trace through the code to figure out what exactly is going on. Using a good IDE like Eclipse can ease some of the pain, but there it is.

When everything is done executing, a StopsWithArrivalsAndDeparturesBean response bean will have been properly populated and returned from the TransitDataService implementation.

The View Layer: JSP

At this point, control returns to Struts, which passes control now to the view layer. Because we are using the Struts Convention plugin, the Struts library automatically looks for a view layer handler in a standard place. Specifically, it looks for:

src/main/webapp/WEB-INF/content/where/standard/stop.jspx

If we examine the JSP file, you'll find the standar mix of HTML and custom JSP tags. The tag prefixs are defined using standard XML namespace declarations at the top of the file:

<html xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:s="/struts-tags">

We are using three sets of taglibs:

  1. <jsp:tags /> - the JSP xml-specific taglibs
  2. <c:tags /> - the JSP Standard Tag Library
  3. <s:tags /> - the Struts taglibs

The Struts tags are the most complex tags but also the most frequently used, so we will discuss them in detail.

Struts Taglibs and the ValueStack =

Struts internally maintains a stack of objects that are automatically referenced by tags (called the ValueStack). When we first enter the JSP page, that stack includes just one object: the Action bean that was just used to handle the request. Consider the tag:

<s:set name="stop" value="result.stop" />

This tag is going to set a variable named stop using the value expression above. That expression automatically get generated to a Java call like:

STACK.getTop().getResult().getStop()

Remember that our StopByNumberAction is at the top of the stack, so getTop() will return it. The getResult() method of StopByNumberAction will next return a StopWithArrivalsBean bean, and then the call to getStop() will return a StopBean and so on and so forth.

The Java bean property syntax used above is handy, but you can also just call straight up methods as well. The tag snippet:

<s:set name="now" value="getNow()" />

ends up as a call to StopAction.getNow(), for example.

All those calls to <s:set .../> are just defining a few convenience variables, so that information can be accessed later on. For example:

<s:property value="#stop.name" />

Note the use of the #. This tells the tag to look for a variable named stop which we defined earlier, as opposed to looking for a stop property on the StopAction.

We keep mentioning the value stack, so let's mention an instance where an object gets pushed onto the stack. Consider:

<s:iterator value="result.predictedArrivals">
   ...
</s:iterator>

The <s:iterator .../> tag is a simple loop construct. Specifically, it ends up iterating over the List<PredictedArrivalBean> returned by the StopWithArrivals bean returned by the getResult() call to the StopByNumberAction bean currently at the top of the stack. For each iteration, the PredictedArrivalBean is pushed onto the top of the stack and popped off at the end. Now when additional calls are made to <s:property .../> or other tags, the PredictedArrivalBean will be consulted first as the top of the stack.

You should hopefully be able to trace through the remainder of the JSP page at this point and have a rough grasp of how beans produced in the Action phase of the process pipeline are accessed in the JSP view phase of the pipeline.

The SiteMesh Decorators

Once the HTML is generated, we are almost done. The generic look and feel of the site is now applied using the SiteMesh framework. SiteMesh allows you to apply decorators that apply additional templating to a rendered JSP page.

Our decorators are defined in:

src/main/webapp/WEB-INF/decorators.xml

The decorators.xml file matches various URL patterns to templates. The relevant section for our request is:

<decorator name="main" page="main.jspx">
    <pattern>/*</pattern>
</decorator>

which defines a mapping to the main.jspx template. All the templates are in:

src/main/webapp/WEB-INF/decorators

The template is just another JSP page that renders like any other JSP, with the addition of the <decorator:* /> tag lib. There are really only two tags to worry about:

<decorator:head />
<decorator:body />

These just include the contents of the <head /> and <body /> tags from previously-rendered JSP (stop.jspx in our case) and insert them into the new JSP, effectively wrapping the original content.

Now, finally, the response is ready to be sent back to the client.

⚠️ **GitHub.com Fallback** ⚠️