Reference » Model Lifecycle

Model Lifecycle

Last modified by Andreas Hahn on 2011/06/09 10:22

In this documentation the terms 'model' and 'entity' refer to the same thing that is a Java Persistence entity object denoted by a @Entity annotation. Shepts ModelCreation and ModelDeletion interfaces are lean helper interfaces that ease entity handling during the entities lifecycle.

Creation

The ModelCreation interface is used by the shept framework when displaying models and when creating new models. It demands implementation of 2 methods:

public interface ModelCreation {
 public abstract boolean isCreationAllowed (Object editedObject);
 public abstract boolean isTransient();
}

Checking transience

When showing a model in a view #isTransient() allows for quick implementation independent checks of the models persistence state.

Typical implementations:
When the key is a generated value

Compound keys are transient when any of its parts is not yet set (evaluates to null).

@Transient
public boolean isTransient() {
 return getId() == null;
}
@Transient
public boolean isTransient() {
 if (getId() == null) return true;
 return getId().isTransient();
}

Note the @Transient annotation is completely separated from this. It is required to tell the persistence layer that this is an extra property not to persist into the database.

Checking transition

The state transition check is a simple way to check a transient entity if it is allowed to turn into a persistent state.

 public boolean isCreationAllowed (Object editedObject) {
   return ! editedObject.equals(this);
 }

When a new model is about to be created the #isCreationAllowed(Object editedObject) is sent to an initialized template instance of the model with the edited object as an argument. It depends on individual requirements to check for allowance. A simple implementation could be to check both for equality:

 public boolean isCreationAllowed (Object editedObject) {
   return ! editedObject.equals(this);
 }

In other words - if a template that is determined by the setup of the framework and the users interactions - equals the editedObject then this object should not be saved because the user has not entered any new information that was present before. The typical use case is a table populated with persistent and one or more transient rows. When the table gets saved it needs to be decided if the transient rows are eligible for saving. The template may well contain hints to ease valid data entries e.g. 'Enter name ...'.

The implementation above however requires that you have already properly defined the #equals() (and #hash()) methods of the entity. For Java developers this is usually common sense and there is a seperate documentation on how to do this. Typically you will want to implement additional checks for instance if the 'name' attribute of a person is empty and the like.

This example is from the SheptDemo. Address creation is allowed if the Address argument at least has a 'city' attribute that is different from the template.

public boolean isCreationAllowed(Object arg0) {
  Address address = (Address) arg0;
 if ( ! StringUtils.hasText(address.getCity())) return false;
 if (StringUtils.hasText(city) && city.equalsIgnoreCase(address.getCity())) {
   return false;
 }
 return true;
}

Deletion

The ModelDeletion interface is an optional interface that can be implemented if the application shall support phantom deletes. Instead of deleting the object is saved with a deletion mark.

It's a common use case that you don't want a deletion to take place instantly. A common requirement is that you want to keep references to deleted entities in a history. There are numerous database solutions to that (for example with triggers and stored procedures) but they usually depend on your database and you are leaving your application domain. 

The ModelDeletion interface jumps in if you have simple requirements e.g. you don't need a history for every entity of your data model. Let's have a look at the interface

public interface ModelDeletion {
 public abstract boolean setDeleted (boolean delete);
 public abstract boolean isDeleted();
}

With #setDeleted(true) you can flag an entity as deleted and with #setDeleted(false) you can undelete a deleted entity. You need to implement an additional field to hold the deletion flag in the entity class. The method returns true if the operation was successful.

Simple implementation
Flag is a boolean value

More sophisticated implementation
Flag is of same type as the prime key

private boolean bDel=false;

@Override
public boolean setDeleted(boolean delete) {
 this.bDel = delete;
 return true;
}

@Override
@Transient
public boolean isDeleted() {
 return this.bDel;
}
private Integer bDel;

@Override
public boolean setDeleted(boolean delete) {
 if (delete) {
   this.bDel = getId();
   return getId() != null;
 } else { // undelete
   this.bDel = null;
   return true;
 }
}

@Override
@Transient
public boolean isDeleted() {
 return this.bDel != null;
}
Note that your filters need to honor deletion flags no matter how they are implemented. In most cases you will only want to query for objects that are not deleted.

Again note the @Transient annotation to declare that there is no isDeleted row to be saved in the databse.

Initialization

While entity initialization is not part of a lifcycle interface it is usually part of the entities lifcycle though.

A typical filter will initialize a model object once it creates a template and in this situation the model will be initialized by a preceeding (so to say 'parent') model.

When we navigate from Person to Address an empty address object gets initialized with the entity where we came from:

#initialize(Person aPerson)

It is recommended that for all relationships beween entities an initialize(sourceClass object) method is implemented for your entities.

 You should define as many #initialize() methods as there are model objects that your entity embeds to be sure you have covered all possible chaining conditions.
Note that in future versions of shept we expect to implement additional configuration checks during startup of the container that enforce the declaration of initialize(sourceClass object) methods for all configured chains. 

Optionally you might implement another init method holding a String as its single argument:

#initialize(String text)

When it exists this method will be called by the framework to provide some default text to indicate an empty model in a not-yet-edited entity to the user. The default argument string that is passed by the framework are 3 questionmarks ???. This is supposed to be configurable locale dependent in future versions. Use the argument to populate the most important String type field of the entity e.g. 'name', 'city' ... 

#clone()

There are occasions where an existing model needs to be duplicated for providing another instance that contains the same data. A typical use case is to copy a new model (newModelTemplate) into n empty rows for user editing. Each row shall contain the same data but of cause they may not be the same instance. Also copies shall not contain id's and versioning information. By default shept provides a best practice approach through ModelUtils#copyModel which is fine for most use cases. However there are situations where this approach doesn't fit and you should provide your own procedure for copying a model. The default processing will first check for an existing #clone() method on the entity object and execute that. If execution doesn't throw an exception processing will go ahead with the returned copy. An example from the orderDemo is given here:

@Override
public Object clone() throws CloneNotSupportedException {
Order copy = BeanUtils.instantiate(this.getClass());
BeanUtils.copyProperties(this, copy, new String[]{"id", "version"});
copy.setNumber(new OrderNumber());
copy.setDate(Calendar.getInstance());
return copy;
}

Of course we might have implemented #clone() by 'hand' by creating a new instance and copy all relevant fields. But this approach has the disadvantage that when extending the entity object with new fields you have to keep that in mind and modify clone() accordingly.

Tags:
Created by Andreas Hahn on 2011/01/08 17:37

© 2011 shept.org - Andreas Hahn