The "One True Path" (OTP) philosophy (or convention) is the cornerstone of the treatment of ORM (Object-relational mapping) in RSF. It states that for every (persistent) entity in the system, it can be found at one "true" or "canonical" EL path. This is derived from its entity name (as defined by the ORM solution, e.g. Hibernate) and its entity ID in a simple way - for example, an entity of type Recipe with an id of ea6b can be found at the (request) EL path
#{Recipe.ea6b}
Note that this "same" entity will obviously appear at a great many other EL paths (for example as members of (HQL) query beans). The OTP path is generally special since it is typically the only path at which data can be 'written', and also enjoys some guarantees of quick access. However note that for some query patterns, individual queries by id will not be the most effective access pattern, and the OTP pattern might need to be adapted. The OTP path is generally the shortest EL path at which a particular entity can be accessed.
This approach is practical in RSF because of its combination of RSAC and EL bindings which allow dependencies on request entities to be injected INTO beans representing application logic, rather than the other way round as happens in so many other frameworks.
Quick OTP using the EntityBeanLocator#
EntityBeanLocator is a standard framework interface in RSF, that comes with a default implementation which makes writing an OTP space for an entity very easy, given just a standard "DAO-type" API managing the entity. This API would expose standard methods such as "find by ID", "save", "remove" etc. Go to the dedicated page on EntityBeanLocator for the "quick path", or carry on reading here to learn more about low-level mechanics for OTP.
An OTP BeanLocator written out longhand#
Although RSF automates the creation of OTP "locators" in many cases (for example, when using RSFHibernate, or EntityBeanLocator), it's useful to see a complete locator written out longhand:public class ScaleBeanLocator implements BeanLocator { public static final String NEW_PREFIX = "new "; private LocalScaleLogic localScaleLogic; // Injection method for application-scope "service" which manages the entities public void setLocalScaleLogic(LocalScaleLogic localScaleLogic) { this.localScaleLogic = localScaleLogic; } // Local cache of entities which have been referenced via EL, indexed by Id private Map delivered = new HashMap(); // Implements the BeanLocator interface public Object locateBean(String path) { Object togo = delivered.get(path); if (togo == null) { if (path.startsWith(NEW_PREFIX)) { togo = localScaleLogic.newScale(); } else { togo = localScaleLogic.fetchScale(path); } delivered.put(path, togo); } return togo; } public void saveAll() { for (Iterator it = delivered.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); EvalScale scale = (EvalScale) delivered.get(key); if (key.startsWith(NEW_PREFIX)) { // could be a special case in some models } localScaleLogic.saveScale(scale); } } }
The key point to notice is the private HashMap delivered of the EvalScale entities which this locator deals in. This is a private cache which is built up in this request-scope locator of all the entities which have been so far referred to via EL - every time a previously unreferenced EvalScale is referred to over the request, the backing service LocalScaleLogic is queried for the EvalScale, and it is stashed in the delivered map before it is returned.
At the end of a request, where we know some EvalScale modifications have been done, we call call the saveAll() method (perhaps using a UICommand method binding of something like #{ scaleBeanLocator.saveAll} which will then traverse the map, using the backing service again to save every value which has been modified.
"New" entities convention#
Part of the OTP standard is the treatment of "new" entities that are being created during the request cycle. By convention, these are all given names beginning with the string "new " (that is, lower-case "new" followed by a single space) - the string following this prefix is arbitrary, with the natural idea that mentioning the same suffix will refer to the same "new" bean within the request cycle. At the end of the cycle, "new" beans are committed/persisted by some ORM-dependent semantics. For example, when using RSFHibernate, they are registered with the receiveNewEntity method of HibernateEntityBeanManager, and then all passed in sequence to session.save() on successful action request end. This is reasonable behaviour for a wide range of applications, but as you can observe these are all pretty short classes that are easy to reimplement and replace. The key point is that adhering to OTP EL semantics enables an enormous variety of implementations to be supported without altering application code or idioms.Examples and related concepts#
See page 4 of the "Hibernate Cookbook" app for an example of OTP in action, in this case using the automatic locators created by RSFHibernate.OTP is related to the concept of BeanExploders - the address space provided by both schemes look identical to users, but where each OTP bean is different as a result of being fetched from persistent store, each exploded bean is a "throwaway" copy of the "same" bean.
OTP can be seen as part of the BeanReasonableness initiative, in that it is designed to avoid the "same" path referring to "different" beans[1]. .It enables the bean classes produced by an ORM solution such as Hibernate to be directly usable within a web application without the usual need for a "controller" or "bridging" layer designed to abstract away their usually obnoxious semantics.
There is some more historical and less well-focused discussion on OTP in the page on ObstinateMaps.
[#1]In computer-theoretic terms, BeanReasonableness could be seen as another way of looking at referential transparency