I'm missing something. Why not use Cayenne's built-in optimistic
locking support, with a timestamp or other versioning attribute in each
important entity?
Eric Lazarus wrote:
>Anyone want to write an article on: Correctly
>implementing optimistic concurrency management in
>Cayenne for web-based computing? We will soon have a
>commercial application doing this! I would think it
>would be something that lots of web-developers should
>want to do if they are building a web-based
>application with a domain object model.
>
>Anyway, we still have a few small problems with it.
>We obtain a set of ObjectIds for all objects changed
>in our session, and another set of ObjectIds for all
>objects changed in other sessions. When these sets
>have a non-empty intersection we have an update
>conflict
>and rollback our changes, and issue a message
>containing the offending objects.
>
>The Ids changed in our session come from
>DataContext.modifiedObjects()
>The Ids changed in other sessions come from a set
>maintained by our
>DataContextDelegate, which adds the ids of objects
>passed to its
>"shouldMergeChanges() method. This set is cleared each
>time we commit
>or rollback our changes.
>
>Perhaps we should shorten the period of time when
>conflicts can occur by clearing the changed-elsewhere
>set at the beginning of the page submit cycle rather
>than at the end of the pervious submit. How can we
>tell if that will give us the correct semantics? We
>want to make sure that we are not overwriting new data
>with
>old data. Would we be messing that up if we clear he
>changed-elsewhere set just before
>we begin updating objects in the object model?
>
>What is most troubling for us right now is that we are
>hitting conflicts on objects that we are not
>intentionally modifying, and are having trouble
>tracking down the code that is causing these objects
>to wind up in the "modified"
>collections.
>
>All of our persistent objects descend from the class
>PAXPersistentObject, which extends CayenneDataObject.
>I used this to try to track down the
>updates by overriding writeProperty(),
>addToManyTarget(), removeToManyTarget(),
>setToOneDependentTarget(), and setPersistenceState()
>produce a log of ObjectIds
>and their modified fields (or PersistenceState).
>
>Am I logging in on the correct methods? What else
>might be called that would cause an object to be put
>into modifiedObjects and/or passed to the data context
>delegate?
>
>The problem is that objects are winding up causing
>update conflicts without showing up in this log.
>Strangely we never see setPersistanceState set the
>state to 'Modified' - the only values we see are
>Hollow (5) and Committed (3).
>
>Is there some better way to find which fields and
>records in our database are
>being modified ? It would be great if we could find a
>spot to put a breakpoint
>where a stack trace could lead to the code that is
>doing the modification, and
>where the objectid and property name are availible so
>that we can filter out the many fields that we are not
>conserned with.
>
>To summarize we have 2 questions.
>(1) What is the best way to find places in our code
>that
> are causing un-intended updates?
>(2) Are we constructing the update conflict set
>correctly?
>
>Below you will find the relevent portions of our code.
>Can you help ?
>
>If notice we are missusing Cayenne in some way, please
>let us know. We love it and want to use it right.
>
>Dan and Eric
>
>
>/* This is called after all updates are complete, just
>before the page
> is rendered. It will rollback when an update
>conflict is found, and
> commit otherwise.
> */
>
>public void preRenderNotification(EditorContext
>aContext) {
> try {
> Set aSet= getUpdateConflictSet(aContext) ;
> if(aSet.size()>0) rollbackChanges(aContext,aSet) ;
> else getDataContext(aContext).commitChanges() ;
> } catch(Exception e) {
> getAppModel(aContext).addMessage(e, "Error In
>Database Update") ;
> e.printStackTrace() ;
>getDataContext(aContext).rollbackChanges() ;
> }
>}
>
>/* this calculates the conflict set as the
>intersection of
> the 'changed here' and the 'changed elsewhere' sets
> */
>
>public static Set getUpdateConflictSet(EditorContext
>aContext) {
> Collection
>modified_Objects=getDataContext(aContext).modifiedObjects();
> Set changedHereIDs =
>getIdSetFromObjectCollection(modified_Objects);
> Set changedOtherIDs=
>getAChangeElseWhereSet(aContext);
> changedOtherIDs.retainAll(changedHereIDs);
> return changedOtherIDs;
>}
>
>/* This just gets the object ids from a collection of
>data objects */
>
>public static Set
>getIdSetFromObjectCollection(Collection
>modifiedObjects) {
> Set aSet=new HashSet();
> Object anArray[]=modifiedObjects.toArray();
> for(int i=0;i<anArray.length;i++) {
> DataObject aDataObject=(DataObject)anArray[i] ;
> aSet.add(aDataObject.getObjectId()) ;
> }
> return aSet;
>}
>
>/* This does the rollback, and outputs the error
>message */
>
>public void rollbackChanges(EditorContext aContext,
>Set aChangeElseWhereSet)
>{
> System.out.println("%%%%%%%%%%%%% Can not Commit
>
>%%%%%%%%%%%%%%%%");
> getDataContext(aContext).rollbackChanges();
> AppModel
>anAppModel=(AppModel)aContext.getModelValue() ;
> Iterator i=aChangeElseWhereSet.iterator();
> while(i.hasNext())
>anAppModel.addMessage("Conflicting object
>id="+i.next()) ;
> aChangeElseWhereSet.clear();
>}
>
>/* this gets the 'ChangeElsewhere' set that is shared
> with the DataContextDelegate out of session state
>*/
>
>protected static HashSet
>getAChangeElseWhereSet(EditorContext
>aRootEditorContext) {
> HttpServletRequest aHttpServletRequest =
>(HttpServletRequest)
>aRootEditorContext.getHttpServletRequest();
> HashSet aChangedElseWhereSet ; HttpSession session
>;
> try {
> session = (HttpSession)
>aHttpServletRequest.getSession();
> } catch (IllegalStateException ise) {
> return null;
> }
>
>
>aChangedElseWhereSet=(HashSet)session.getAttribute("ChangedElseWhere");
> if(aChangedElseWhereSet==null) {
> aChangedElseWhereSet=new HashSet();
> aRootEditorContext.set("session:ChangedElseWhere",
>
>aChangedElseWhereSet);
> aChangedElseWhereSet = (HashSet)
>session.getAttribute("ChangedElseWhere");
> }
> return aChangedElseWhereSet;
>}
>
>/* This retrievs the DataContext from session state.
> But on first call it creates the
>DataContextDelegate and passes
> the 'changed elsewhere' set from session state for
>it to fill */
>
>public static DataContext getDataContext(EditorContext
>aRootEditorContext) {
> HttpServletRequest aHttpServletRequest =
>(HttpServletRequest)
>aRootEditorContext.getHttpServletRequest();
> DataContext aDataContext ; HttpSession session ;
> try {
> session = (HttpSession)
>aHttpServletRequest.getSession();
> } catch (IllegalStateException ise) {
> System.out.println("Exceptions If session is
>null"+ise.getMessage());
> return null;
> }
>
> aDataContext = (DataContext)
>session.getAttribute("CayenneDataContext");
> if (aDataContext == null) {
> aDataContext = DataContext.createDataContext();
>
>aRootEditorContext.set("session:CayenneDataContext",
>aDataContext);
> aDataContext = (DataContext)
>session.getAttribute("CayenneDataContext");
>
> OpDataContextDelegate aDelegate= new
>OpDataContextDelegate(getAChangeElseWhereSet(aRootEditorContext));
> aDataContext.setDelegate(aDelegate) ;
> }
> return aDataContext;
>}
>
>/* This is the 'shouldMergeChanges' method from the
>DataContextDelegate */
>
>public boolean shouldMergeChanges(DataObject arg0,
>DataRow arg1) {
> getAChangedElseWhereSet().add( arg0.getObjectId() )
>;
> return true ;
>}
>
>-------------------------------------------------------------------------------
>-------------------------------------------------------------------------------
>
>/* This is the PAXPersistant Object where we log
>updates to try to find
> which fields are causing objects to be marked
>'modified' */
>
>public class PAXPersistentObject extends
>org.objectstyle.cayenne.CayenneDataObject {
>
> public void writeProperty(String key, Object value)
>{
> filterAndLog(" [*"+key+"*]") ;
> super.writeProperty(key, value) ;
> }
>
> public void addToManyTarget(String key,
>CayenneDataObject obj, boolean
>bool) {
> filterAndLog(" [+"+key+"+]") ;
> super.addToManyTarget(key, obj, bool) ;
> }
>
> public void removerFromManyTarget(String key,
>CayenneDataObject obj,
>boolean bool) {
> filterAndLog(" [+"+key+"+]") ;
> super.removeToManyTarget(key, obj, bool) ;
> }
>
> public void setPersistenceState(int n ) {
> filterAndLog(" {>"+n+"<}") ;
> super.setPersistenceState(n) ;
> }
>
> private void filterAndLog(String tag) {
> String name=this.getClass().getName() ;
> if (name.startsWith("demo."))
>name=name.substring(5) ;
> if (omit.contains(name)) return ;
> debuglog(pad(name)+" "+tag) ;
> }
>
> private String pad(String text) {
> if (text.length()>blanks.length()) return text ;
> return (text+blanks).substring(0, blanks.length())
>;
> }
>
> private static PrintWriter out ;
> private static String blanks="
> " ;
> private static SimpleDateFormat df=new
>SimpleDateFormat("MM/dd HH:mm:ss")
>;
> private static String[] omits={"Deal", "Seller",
>"Address", "Quote",
>"DoListItem", "DoListOption", "DoListOptionVar",
>"LogEntry", "CashFlow",
>"CashFlowForSplit"} ;
> private static List omit=Arrays.asList(omits) ;
>
> public static void debuglog(String text) {
> if (text==null) {
> if ( out !=null ) {
> out.println("************CLOSE**** "+df.format(new
>Date())) ;
> out.flush() ; out.close() ; out=null ;
> }
> } else {
> if (out==null) {
> String path="DebugLog.txt" ;
> try { out= new PrintWriter(new FileWriter(path,
>true)) ;}
> catch (IOException x) { throw MessageToUser.error(x,
>"Error Opening Debug
>Log") ;}
> out.println("************OPEN***** "+df.format(new
>Date()) ) ; out.flush()
>;
> }
> out.println(text) ; out.flush() ;
> }
> }
>}
>
>
>
>
>
>__________________________________________________
>Do You Yahoo!?
>Tired of spam? Yahoo! Mail has the best spam protection around
>http://mail.yahoo.com
>
>
>
This archive was generated by hypermail 2.0.0 : Thu May 04 2006 - 14:23:47 EDT