Wednesday, March 9, 2011

Cayenne Context

I’d like to start my narrative about Apache Cayenne by describing the heart of Cayenne API - ObjectContext.

From the application code Cayenne persistent features are accessible through the instances of org.apache.cayenne.ObjectContext interface. ObjectContext provides operations for performing queries, manipulating data objects, performing commit and rollback changes, nesting dependent contexts and so on.
Cayenne Persistence API is used for implementing object persistence in Java applications (http://cayenne.apache.org/doc/cayenne-guide.html) In addition to the “classic” ORM persistence Apache Cayenne provides persistence for distributed client applications. In Cayenne terminology it is called Remote Object Persistence(ROP) http://cayenne.apache.org/doc/remote-object-persistence-guide.html
The applications based on Cayenne Persistence API and ROP applications use different implementations of ObjectContext: DataContext and CayenneContext respectively.
To obtain an ObjectContext in a standalone application (specifics of obtaining context in web applications are discussed below):
Cayenne API 3.0:
import org.apache.cayenne.access.DataContext;
...
DataContext context = DataContext.createDataContext();

To obtain the ObjectContext in a ROP client:
Cayenne ROP 3.0:
String myChatRoom = "room";
ClientConnection connection = new HessianConnection(
        "https://localhost:8080/myapp/mysecureservice",
        "username",
        "secret_password",
        myChatRoom);
DataChannel channel = new ClientChannel(connection);
ObjectContext context = new CayenneContext(channel);

Version 3.1 introduces the dependency injection container to API (which surely will be the topic of one of my following articles), and obtaining of context in 3.1 looks slightly different. In 3.1 instantiating of context is performed behind the scenes, by the corresponding factory: org.apache.cayenne.configuration.server.DataContextFactory (don’t confuse with the old DataContextFactory, from the access package, it is removed in 3.1) and org.apache.cayenne.configuration.rop.client.CayenneContextFactory. So, now to obtain context, you should just create the appropriate runtime object and get the context from it (here and below we will consider examples for Cayenne 3.1):

CayenneAPI:
ServerRuntime cayenneRuntime = new ServerRuntime("cayenne-myDomain.xml");
ObjectContext context = cayenneRuntime.getContext();

Cayenne ROP:
Map<String, String> properties = new HashMap<String, String>();
//define the properties for runtime
String myChatRoom = "room";
properties.put(ROP_SERVICE_URL, "https://localhost:8080/myapp/mysecureservice");
properties.put(ROP_SERVICE_USER_NAME, "username");
properties.put(ROP_SERVICE_PASSWORD, "secret_password");
properties.put(ROP_SERVICE_SHARED_SESSION, myChatRoom);   

ClientRuntime runtime = new ClientRuntime(properties);
ObjectContext context = runtime.getContext();

When working with a web-application, you can bind context to the session using CayenneFilter:

define filter in web.xml:

<filter>
        <!--name of the cayenne config file, ie name of domain-->
              <filter-name>cayenne-myDomain</filter-name>
              <filter-class>org.apache.cayenne.configuration.web.CayenneFilter</filter-class>
      </filter>
      <filter-mapping>
              <filter-name>cayenne-myDomain</filter-name>
              <url-pattern>/*</url-pattern>
      </filter-mapping>

Now we can obtain the context in application:

ObjectContext context = BaseContext.getThreadObjectContext();

As you can see, we get the context that is bound to thread, so here we don’t need to know how the context was configured, it is the duty of the filter.

CayenneFilter enables the session-scope context by default, but if your application needs for another scoping, Cayenne 3.1 provides great possibilities for customization, so you can write your own correctly scoped replacements(the scoping of context also will be the topic of one of the further articles)

After the successful obtaining of the context object, you can use its features: performing queries and persisting the data. All the objects that ObjectContext operates with are “registered” in it. “Registering” means that such an object has the unique ObjectId, the reference to its context, which tracks all the modifications of this object. The objects that are returned as query results are registered automatically, while newly created data objects should be registered explicitly, method ObjectContext.newObject(Class<T> persistentClass), or after the creation: ObjectContext.registerNewObject(Object object). You can create several separate contexts to perform the changes of persistent data independently. After the manipulations with registered objects(create, modify, delete operations) you can store your changes: ObjectContext.commitChanges(), or clean them up: ObjectContext.rollbackChanges(). When changes are committed in one context, all other contexts in system are notified, so it ensures the consistency of data.
It may happen that you’ve fetched the related objects in some other contexts and you need to use them in relationships with objects of the target context. Instead of refetching the same objects from the database, you may just transfer them to the target context by  ObjectContext.localObject(ObjectId id, Object prototype) method. The prototype parameter allows to transfer the object with its uncommitted modifications.
For example, we have a web-application that displays the large amounts of data to review and it is convenient to have some shared context, which will be used to fetch that data. Imagine we display the list of news and we want to add a news item. Let’s assume our new item has the country property storing the location of the news event. Let’s also assume that country is not just text, but the Cayenne entity itself.

So to add an item, we create a separate context(as we don’t want other users to see the changes before the committing).

//Default configuration will return a new instance of the ObjectContext on every call
ObjectContext editingContext = runtimeObject.getContext();

create the new object to store:
NewsItem item = editingContext.newObject(NewsItem.class);

then get the list of possible Country object, already fetched by the application:
List<Country> countries = session.getAttribute(“possibleCountries”);

then let the user fill the data and select the country(may be, from the dropdown list).
Country selectedCountry=..//get the country;

“item.setCountry(selectedCountry)” will cause an exception, we should localise the Country object in the editing context:
selectedCountry=editingContext.localObject(selectedCountry.getObjectId(), null);
item.setCountry(selectedCountry);//now everything is OK.
In fact the localObject method has “prototype” parameter which is mostly intended for internal use by Cayenne to pass objects between parent and child contexts (see below), so most often than not you should pass “null” for the prototype in the application code.
note: be careful when moving the newly created and yet uncommitted objects because after the localizing they will have “committed” state.

One of the unique features of Cayenne besides the ability to create independent contexts, is the ability to create hierarchy of nested contexts. That is you can create the child of a particular context and locally change the data without committing it to database (for ROP without sending them to server).

This feature is used in the complex nested user dialogs or workflows.
Lets imagine we have the stand-alone swing-based application for restaurant-orders management. Consider the views for creating and editing of orders and items of a particular order. 

Let’s review the code(omit the most of methods for brevity, consider only the part related to our nested context example).
These views are displayed by the special controllers, that all are the subclasses of the general abstract EditController:
/**
* Abstract class for all the edit controllers. Implements some listeners to
* perform the functionality of related panel controls.
*
* @author Ksenia
*
*/
public abstract class EditController implements ActionListener/* ,...some other useful isteners...*/{
    /**
    * The related panel where the gui displayed.
    */
   protected ViewPanel viewPanel;

   /**
    * The cayenne object to edit.
    */
   private DataObject record;

   /**
    * Cayenne context to manipulate with persistent data.
    */
   protected ObjectContext context;

   /**
    * Flag that indicates whether the frame of view is independent(if
    * isModal=false), or should be opened as modal window(isModal=true).
    */
   protected boolean isModal;

   public EditController(DataObject record, boolean isModal) {
       this.record=record;
       this.context = record.getObjectContext();
       this.isModal = isModal;
       // init the view.
       initViewPanel();
       // diplay the view
       showViewPanel();
   }

   public EditController(DataObject record) {
       this(record, false);
   }

   /**
    * Initializes the {@link #viewPanel} with the appropriate value.
    */
   protected abstract void initViewPanel();

   /**
    * Depending on {@link #isModal} value, open the frame as independent one or
    * as modal window.
    */
   protected void showViewPanel() {
       if (isModal) {
           //open as modal dialog for current frame
       } else {
           //open new independent frame
       }
   }

   /**
    * Performs the action sent from view.
    */
   public void actionPerformed(ActionEvent event) {
       ActionCommand action = getAction(event);
       switch (action) {
       case SAVE:
           // if the edit is dependent then just commit changes to parent
           // context
           if (isModal) {
               context.commitChangesToParent();
           } else {
               context.commitChanges();
           }
           break;
       case CANCEL:
           // if the edit is dependent then rollback locally, not affecting the
           // parent context
           if (isModal) {
               context.rollbackChangesLocally();
           } else {
               context.rollbackChanges();
           }
       }
   }

   /**
    * Retrieves the action command sent from view.
    *
    * @param event
    * @return
    */
   protected abstract ActionCommand getAction(ActionEvent event);

   /**
    * Retrieves the editing record.
    * @return
    */
   public DataObject getRecord(){
       return record;
   }
}


So, the OrderEditController will be subclass of EditController:
public class OrderEditController extends EditController {

   public OrderEditController(DataObject record) {
       super(record);
   }

     @Override
   public void actionPerformed(ActionEvent event) {
       ActionCommand action = getAction(event);
       switch (action) {
       case ADD_ORDER_ITEM:
           ObjectContext childContext = context.createChildContext();
           Item newItem = childContext.newObject(Item.class);
           //note, we have to localize order for setting it to item
           newItem.setOrder(childContext.localObject(getRecord().getObjectId(), getRecord()));
           new ItemEditController(newItem, false);
           break;
       //.... the other possible actions
       }
       super.actionPerformed(event);
   }  
   //.... overridden methods, newly introduced methods
}

To process the user’s command of creating (or editing) the order, some controller manager should go with the following code:
//create the separate context for order editing
ObjectContext context = cayenneRuntime.getContext();
//create new Order object, or get selected to edit Order object and localize it in just created context
Order newOrder = context.newObject(Order.class);
//the constructor without isModal parameter will create the controller with the view in independent frame
new OrderEditController(newOrder);

            So, above we considered obtaining of the ObjectContext and some points concerned persistence data manipulating. There are still a lot of uncovered topics like context scoping or persistence data cashing which hopefully will be described in the next articles.





Greetings!

Hi there! Welcome to my blog, where I’m going to discuss the points about Apache Cayenne, its features and practices of using this really nice ORM framework!

Hope it will be helpful. So, let’s start!=)