In part 1 & part 2 we envisioned an architectural model for data entities that exhibit data related behavior without using JPA and explained how to track and report changes. In this part, we'll look into the ways we can populate and validate these objects.
Data Population
Read-Only entities
By now we can guess that read-only entities should only be populated directly from the database. The reliable and least intrusive way to do this is to start from the root entity which should have a public static factory method that accepts live database cursors from which the entity and its dependencies read their data. An example should clarify. In this example we are looking at a root read-only entity called "DashboardEntity" and its collection member of messages (obviously read-only) called "MessagesEntity".
Please take note of the followings:
Since the code, in both cases, is similar to snippets 1 and 2, I won't provide an example. However, bear in mind that updating editable entities means implicitly invoking aspect code that's woven into your entities. To prevent that from happening while the entity is being populated, you could introduce a volatile flag in the BaseEditableEntity that can be set by the entity when the data population begins and ends. The aspect could then read this flag to decide whether or not it should proceed with its execution.
Another issue to watch out for is the isNew flag (have a look at the BaseEditableEntity's default constructor). Basically, when a new editable object is created, isNew becomes true indicating this object is new and when asked if it's changed (perhaps to be persisted if it is) it'll say yes. However, an editable entity built from a read-only one (or database cursor) isn't new. So when the loading finished, you'll have to flag the entity as "old".
public final class DashboardEntity extends BaseEntity { private static final long serialVersionUID = 5074611836312001925L; @XmlElement private int uniqueTrackingId; @XmlElement private MessagesEntity messages; private DashboardEntity() { uniqueTrackingId = 0; messages = new MessagesEntity(); } /** * Factory method that creates an instance from a live cursor. Entity is not * responsible for the cursor's life cycle. * * @param dashboardRowSet * @param msgRowSet * * @return an instance of DashboardEntity */ public static DashboardEntity CreateInstance( final ResultSet dashboardRowSet, final ResultSet msgRowSet) throws SQLException { DashboardEntity obj = new DashboardEntity(); try { obj.loadData(dashboardRowSet); obj.messages.loadData(msgRowSet); } catch (SQLException ex) { throw ex; } return obj; } /** * Loads its data from a live cursor as a MEMBER class as opposed to a ROOT * class * * @param dashboardRowSet * @throws SQLException */ private void loadData(final ResultSet dashboardRowSet) throws SQLException { try { if (dashboardRowSet.next()) { uniqueTrackingId = dashboardRowSet.getInt("uniqueTrackingId"); } } catch (SQLException ex) { throw ex; } } }
Snippet 1: Root read-only entity that accepts all live cursors needed to populate itself and its members |
public final class MessagesEntity extends CollectionBaseEntity{ private static final long serialVersionUID = 1124863903497528641L; MessagesEntity() { } /** * Loads its data from a live cursor * * @param msgRowSet * @throws SQLException */ void loadData(final ResultSet msgRowSet) throws SQLException { try { while (msgRowSet.next()) { MessageEntity obj = new MessageEntity(); obj.loadData(msgRowSet); rows.add(obj); } } catch (SQLException ex) { throw ex; } } }
Snippet 2: Member read-only collection entity creating its members by giving them the cursor |
Please take note of the followings:
- MessagesEntity's constructor is package scoped. It could've been made private and have the entity to expose a static factory method to which the DashboardEntity would pass its corresponding cursor. But usually you'd be controlling the entire stack of entities and that level of complication won't be necessary.
- Please note that MessagesEntity isn't responsible for loading MessageEntity members. It simply pass the cursor to them to load themselves. It should be easy to figure out what MessageEntity looks like (hint: it has a "loadData" method similar to what DashboardEntity has).
- Entities aren't responsible for the life cycle of these cursors. That's controlled by the mediator that received them from the database (e.g.: a DAO).
- While these entities are loaded, these cursors are kept alive. Any interruption would cause a live useless resource to linger in the database for a while. The resource would be released eventually though. In some databases such as Oracle you could read the data into a user-defined type (defined in the database schema) and return that as a disconnected structure. That solution, however, requires more maintenance and more space. Moreover, it's less flexible and amenable to optimization. You ought to read the data out anyway. Whereas with cursor, you'd have the option not to read it in the application.
Editable entities
Depending on your point of view of changes throughout a particular application, editable entities could be loaded from a read-only entity (optimistic) or directly from the database (pessimistic). In other words, to update the data you could either populate an editable entity using a database cursor in the backend very much like the read-only entities (which is then presented to the front-end to be edited and sent back to be persisted) or populate it from a read-only entity and trust that the data hasn't been changed ever since. The latter can be improved by introducing the time element. That is, an editable entity built from a read-only one could only be valid for certain amount of time before it's persisted.
Since the code, in both cases, is similar to snippets 1 and 2, I won't provide an example. However, bear in mind that updating editable entities means implicitly invoking aspect code that's woven into your entities. To prevent that from happening while the entity is being populated, you could introduce a volatile flag in the BaseEditableEntity that can be set by the entity when the data population begins and ends. The aspect could then read this flag to decide whether or not it should proceed with its execution.
Another issue to watch out for is the isNew flag (have a look at the BaseEditableEntity's default constructor). Basically, when a new editable object is created, isNew becomes true indicating this object is new and when asked if it's changed (perhaps to be persisted if it is) it'll say yes. However, an editable entity built from a read-only one (or database cursor) isn't new. So when the loading finished, you'll have to flag the entity as "old".
Validation
The ultimate goal of validation is to ensure that the values of an entity's data attributes comply with certain predefined rules before the entity is persisted1. This simply means that validation only applies to single2 editable entities.We won't try to reinvent the wheel since JSR-303 has already introduced validation. What we'd like to do, however, is to find a way to get entities to report their state of validity when asked. The solution should be obvious by now. To summarize, introduce an aspect which validates any field that is annotated with annotations from "javax.validation.constraints" when is changed and record the "broken rule" in the entity itself.
Following snippet shows the basic validation aspect.
@Aspect public class AspectValidation { @Around("set(@(javax.validation.constraints..*) * *) && this(BaseEditableEntity) && this(k) && args(newVal)") public void aroundSetField(JoinPoint jp, Object k, Object newVal) throws Throwable { BaseEditableEntity trackingObj = ((BaseEditableEntity) k); if (trackingObj.getSkipInterceptors()) return; Signature signature = jp.getSignature(); String fieldName = signature.getName(); // build default validator factory Validator validator = Validation.buildDefaultValidatorFactory() .getValidator(); Set> constraintViolations = validator .validateProperty(trackingObj, fieldName); if (!constraintViolations.isEmpty()) { for (ConstraintViolation violation : constraintViolations) { trackingObj.brokenRules.add(BrokenRule.createInstance( fieldName, violation.getMessage())); } } } }
Snippet 3: Validation aspect |
The followings require clarification:
- getSkipInterceptors refers to what I pointed out in previous section regarding population of editable entities.
- brokenRules is a List of BrokenRule in the BaseEditableEntity.
- BrokenRule is simply a class with 2 fields that holds the name of the field in the entity to which there is a validation violation is associated and the validation violation itself.
This concludes the "Responsible Entities". Please feel free to take it apart and point out the shortcomings.
1. The entities that are loaded from database and not changed ever since are assumed to be valid (in term of rules) simply because the records in database are.↩
2. The assumption we made in part 2 about collection entities being solely containers still stands. If that's not the case (if they have data attributes that need validation) the approach explained here for validating single entities should apply to them, as well.↩