Push-Ups
Early handling of includes
Splitting up a page into multiple controllers is a good way to separate
concerns. One drawback of this approach is, that includes can't send
headers to the client, as the response is already committed when the
include is processed. In contrast to portlets, servlets don't know the
concept of a separate processing/rendering phase. This means that the
processing order is bound to the order of appearance in the document,
which makes it sometimes hard for controllers to collaborate.
Riot therefore introduces the concept of push-ups.
When a handler is pushed up, it is taken out of the regular processing
flow and invoked immediately, before any output is sent back to the
client. Unless the handler sends an error or redirect, the output is
captured and re-inserted at its original place once the processing
reaches the place where the handler is included.
There a two typical scenarios where you might want to use push-ups:
1) Forms that implement the redirect-after-post pattern
2) A title tag that contains information provided by the "main content include"
For the first scenario you should use a PushUpInterceptor.
The interceptor will push up the controller that rendered the view of
the submitted form. This will even work on pages with more than one
form.
For the second scenario you can use the push-up attribute of the template:insert tag (see How to use Templates). This allows you to define the processing order, independent of the appearance within the document.
Collaboration between controllers
The second scenario described above is a nice example for collaborating controllers (or views). The pushed-up controller loads data from the database and makes it available to the view that renders the document title. A simple and straightforward way to exchange the data would be to set a request attribute, but for two reasons this approach would not work:
The Spring DispatcherServlet performs an attribute clean-up after each include, so included controllers can't expose request attributes to other controllers, unless you turn this feature off, which usually isn't a good idea.
The other reason is that you can't cache the pushed-up controller using this approach. If a cached version was served, the controller's handleRequest() method would not be invoked (which after all is the purpose of a cache), thus the controller had no chance to set any request attributes at all.
One solution would be to cache the dependent controller too. But in this case we would have to ensure that the cached version of the first controller is invalidated as soon as the dependent cache item is removed from the cache. As there is no easy way to achieve this, the SharedProperites class uses another approach:
The SharedPropertiesInterceptor exposes a HashMap as request attribute before the top-level handler is executed. A controller that wants to hand data on to a subsequent controller can use the setProperty(HttpServletRequest, String, String) method to add entries to this map. Because the map is already present before the the include is performed, it won't be cleaned up by the DispatcherServlet. Another controller can now invoke getProperty(HttpServletRequest, String) to retrieve the previously set values.
Currently shared properties are limited to Strings. The reason for this is that Cachius is aware of shared properties and caches them along with the actual output. It's basically a precaution to prevent users from caching their business objects. The fact that Cachius knows about shared properties allows us to serve cached content and still expose data to others.
Note: You can easily access shared properties in your FreeMarker views via the common.getSharedProperty() function and the common.setSharedProperty() macro.