cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Bryan Lewis <br...@maine.rr.com>
Subject Re: Correctly implementing optimistic concurrency management in Cayenne for web-based computing
Date Thu, 04 May 2006 18:23:17 GMT
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 
>
>  
>


Mime
View raw message