Thanks for the review Craig. Please find attached a cumulative patch that addresses your review comments. Please note that this patch contains some additional changes that I had missed last time. Following is a summary of additional changes.
HibernateBookMarkManagerImpl:
removeBookmark(BookmarkData bookmark) now removes bookmark from the parent folder's collection of bookmarks also
FolderData, UserData,WeblogEntryData:
  Replaced the cascade="all-delete-orphan" with cascade="all"

All the business unit tests passes with the attached patch. ( I am getting a failure while running org.apache.roller.ui.UIPluginManagerTest with or without these changes on a freshly checked out workspace)

Thanks,
Mitesh
Craig L Russell wrote:
Hi Mitesh,

This is a good analysis of a problem that I've seen as well. Comments follow...

On Dec 18, 2006, at 11:03 AM, Mitesh Meswani wrote:

Apache mail host seemed to remove the text of the mail. Resending.........

Hi,

I am working to port Roller's backend code to use JPA/JDO (see trunk/sandbox/jdobackend). I ran across a dependency on Hibernate specific feature that can be easily removed with some refactoring.

This Hibernate feature is not portable to either JDO or JPA, and I think it's a bit confusing when reading the code, since you have to either be a Hibernate expert or look up the behavior in the Hibernate documentation to figure out what is going on.

So I support the notion of making relationship handling explicit and thereby removing the confusion.

For Hibernate's "orphan" relationships, both sides of the relationship are affected by the operation, which I'd characterize as a "delete" operation that has a side effect of removing the deleted instance from the collection on the "owner" side. This is in contrast to Hibernate's definition of removing an instance which has a side effect of deleting the instance from the database.

If the deleted instance had its own XXXManager, I'd favor putting the behavior into the XXXManager interface, since there is supposed to be no knowledge of the persistence layer inside the pojo layer. And in fact, it's unlikely that such a pojo would have such a behavior since the nature of the relationship is a composition relationship, and it makes sense that the behavior be in the aggregate instance manager.

So I agree with your proposal to remove the method from the pojo and instead add it to the XXXManager interface that can both remove the owned instance from the collection and remove the owned instance from the database.

As a performance optimization, we should also consider changing the relationship from a Set to a Map. This would avoid the issue of iterating the entire Set just to find the right entry. This is a portable JPA/JDO/Hibernate construct (to be further looked at later).

In the code that follows, sometimes you remove the entity from the collection and then remove it from the database. In other cases, you remove it from the database and then remove it from the collection. We should probably be consistent in the order (although it should not matter; I don't believe that either JDO or JPA requires an order).


...snipped

Index: src/org/apache/roller/pojos/UserData.java
===================================================================
--- src/org/apache/roller/pojos/UserData.java    (revision 488141)
+++ src/org/apache/roller/pojos/UserData.java    (working copy)
@@ -434,38 +434,14 @@
        return false;
    }
//Moved the method revokeRole to HibernateUserManagerImpl
-       /**
-     * Revokes specified role from user.
-     */
-    public void revokeRole(String roleName) throws RollerException {
-        RoleData removeme = null;
-        Iterator iter = roles.iterator();
-        while (iter.hasNext()) {
-            RoleData role = (RoleData) iter.next();
-            if (role.getRole().equals(roleName)) {
-                removeme = role;
-            }
-        }
-       -        /*
- * NOTE: we do this outside the loop above because we are not allowed - * to modify the contents of the Set while we are iterating over it.
-         * doing so causes a ConcurrentModificationException

This looks like a bug to me. The algorithm iterates the collection and actually ignores all but the last role found. Since the semantic is that there should be only one such instance, it should stop at the first match of role name and therefore there is no ConcurrentModificationException.

    public void savePage(WeblogTemplate data) throws RollerException;
Index: src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java
===================================================================
--- src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java (revision 488141) +++ src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java (working copy)
@@ -26,6 +26,8 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
+import java.util.Collection;
+
import org.apache.roller.pojos.StatCount;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
@@ -58,6 +60,7 @@
import org.apache.roller.pojos.WeblogCategoryData;
import org.apache.roller.pojos.WeblogEntryData;
import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.pojos.RoleData;
import org.hibernate.Query;
@@ -406,6 +409,29 @@
        website.removePermission(target);
        this.strategy.remove(target);
    }
// Method moved from UserData to here
+
+ public void revokeRole(String roleName, UserData user) throws RollerException {
+        RoleData removeme = null;
+        Collection roles = user.getRoles();
+        Iterator iter = roles.iterator();
+        while (iter.hasNext()) {
+            RoleData role = (RoleData) iter.next();
+            if (role.getRole().equals(roleName)) {
+                removeme = role;

I'd think that the remove operations should be here, followed by break;

The rest of the code looks good.

Craig

Craig Russell
Architect, Sun Java Enterprise System http://java.sun.com/products/jdo
408 276-5638 mailto:[EMAIL PROTECTED]
P.S. A good JDO? O, Gasp!

Index: src/org/apache/roller/pojos/FolderData.java
===================================================================
--- src/org/apache/roller/pojos/FolderData.java (revision 488141)
+++ src/org/apache/roller/pojos/FolderData.java (working copy)
@@ -274,7 +274,7 @@
      *
      * @roller.wrapPojoMethod type="pojo-collection" 
class="org.apache.roller.pojos.BookmarkData"
      *
-     * @hibernate.set lazy="true" order-by="name" inverse="true" 
cascade="all-delete-orphan"
+     * @hibernate.set lazy="true" order-by="name" inverse="true" cascade="all"
      * @hibernate.collection-key column="folderid"
      * @hibernate.collection-one-to-many 
class="org.apache.roller.pojos.BookmarkData"
      */
@@ -296,15 +296,6 @@
         getBookmarks().add(bookmark);
     }
     
-    
-    /** 
-     * Remove a boomkark from folder.
-     */
-    public void removeBookmark(BookmarkData bookmark) {
-        getBookmarks().remove(bookmark);
-    }
-    
-    
     /**
      * @roller.wrapPojoMethod type="pojo-collection" 
class="org.apache.roller.pojos.BookmarkData"
      *
Index: src/org/apache/roller/pojos/UserData.java
===================================================================
--- src/org/apache/roller/pojos/UserData.java   (revision 488141)
+++ src/org/apache/roller/pojos/UserData.java   (working copy)
@@ -404,7 +404,7 @@
     
     
     /**
-     * @hibernate.set lazy="true" inverse="true" cascade="all-delete-orphan"
+     * @hibernate.set lazy="true" inverse="true" cascade="all"
      * @hibernate.collection-key column="userid"
      * @hibernate.collection-one-to-many 
class="org.apache.roller.pojos.RoleData"
      */
@@ -434,38 +434,14 @@
         return false;
     }
     
-    
     /**
-     * Revokes specified role from user.
-     */
-    public void revokeRole(String roleName) throws RollerException {
-        RoleData removeme = null;
-        Iterator iter = roles.iterator();
-        while (iter.hasNext()) {
-            RoleData role = (RoleData) iter.next();
-            if (role.getRole().equals(roleName)) {
-                removeme = role;
-            }
-        }
-        
-        /* 
-         * NOTE: we do this outside the loop above because we are not allowed
-         * to modify the contents of the Set while we are iterating over it.
-         * doing so causes a ConcurrentModificationException
-         */
-        if(removeme != null) {
-            roles.remove(removeme);
-        }
-    }
-    
-    
-    /**
      * Grant to user role specified by role name.
      */
     public void grantRole(String roleName) throws RollerException {
         if (!hasRole(roleName)) {
             RoleData role = new RoleData(null, this, roleName);
             roles.add(role);
+            role.setUser(this);
         }
     }
     
Index: src/org/apache/roller/pojos/WeblogEntryData.java
===================================================================
--- src/org/apache/roller/pojos/WeblogEntryData.java    (revision 488141)
+++ src/org/apache/roller/pojos/WeblogEntryData.java    (working copy)
@@ -353,7 +353,7 @@
      *
      * @roller.wrapPojoMethod type="pojo-collection" 
class="org.apache.roller.pojos.EntryAttributeData"
      * @ejb:persistent-field
-     * @hibernate.set lazy="true" order-by="name" inverse="true" 
cascade="all-delete-orphan"
+     * @hibernate.set lazy="true" order-by="name" inverse="true" cascade="all"
      * @hibernate.collection-key column="entryid" type="String"
      * @hibernate.collection-one-to-many 
class="org.apache.roller.pojos.EntryAttributeData"
      */
@@ -404,12 +404,8 @@
             att.setValue(value);
         }
     }
-    public void removeEntryAttribute(String name) throws RollerException {
-        EntryAttributeData att = (EntryAttributeData)attMap.get(name);
-        if (att != null) {
-            attMap.remove(att);
-            attSet.remove(att);
-        }
+    public void onRemoveEntryAttribute(EntryAttributeData att) throws 
RollerException {
+        attMap.remove(att);
     }
     //-------------------------------------------------------------------------
     
@@ -609,7 +605,7 @@
      *
      * @ejb:persistent-field
      * 
-     * @hibernate.set lazy="true" order-by="name" inverse="true" 
cascade="all-delete-orphan" 
+     * @hibernate.set lazy="true" order-by="name" inverse="true" cascade="all" 
      * @hibernate.collection-key column="entryid" 
      * @hibernate.collection-one-to-many 
class="org.apache.roller.pojos.WeblogEntryTagData"
      */
@@ -654,16 +650,10 @@
         addedTags.add(name);
     }
 
-    public void removeTag(String name) throws RollerException {
-        for (Iterator it = tagSet.iterator(); it.hasNext();) {
-            WeblogEntryTagData tag = (WeblogEntryTagData) it.next();
-            if (tag.getName().equals(name)) {
-                removedTags.add(name);
-                it.remove();
-            }
-        }
+    public void onRemoveTag(String name) throws RollerException {
+        removedTags.add(name);
     }
-    
+
     public Set getAddedTags() {
         return addedTags;
     }
@@ -696,9 +686,10 @@
                 newTags.remove(tag.getName());
             }
         }
-        
+
+        WeblogManager weblogManager = 
RollerFactory.getRoller().getWeblogManager();
         for (Iterator it = removeTags.iterator(); it.hasNext();) {
-            removeTag((String) it.next());
+            weblogManager.removeWeblogEntryTag((String) it.next(), this);
         }
         
         for (Iterator it = newTags.iterator(); it.hasNext();) {
Index: src/org/apache/roller/business/UserManager.java
===================================================================
--- src/org/apache/roller/business/UserManager.java     (revision 488141)
+++ src/org/apache/roller/business/UserManager.java     (working copy)
@@ -27,7 +27,6 @@
 import org.apache.roller.pojos.UserData;
 import org.apache.roller.pojos.WebsiteData;
 
-
 /**
  * Manages users, weblogs, permissions, and weblog pages.
  */
@@ -325,8 +324,15 @@
     public void retireUser(WebsiteData website, UserData user)
         throws RollerException;
     
-    
     /**
+     * Retire user from a website
+     * @param roleName Name of the role to be revoked
+     * @param user    User for whom the role is to be revoked
+     */
+    public void revokeRole(String roleName, UserData user)
+        throws RollerException;
+
+    /**
      * Store page.
      */
     public void savePage(WeblogTemplate data) throws RollerException;
Index: src/org/apache/roller/business/WeblogManager.java
===================================================================
--- src/org/apache/roller/business/WeblogManager.java   (revision 488141)
+++ src/org/apache/roller/business/WeblogManager.java   (working copy)
@@ -194,9 +194,22 @@
      * @return Collection of WeblogEntryData objects.
      */
     public List getWeblogEntriesPinnedToMain(Integer max) throws 
RollerException;
-    
-    
+
     /**
+     * Remove attribute with given name from given WeblogEntryData
+     * @param name Name of attribute to be removed
+     */
+    public void removeWeblogEntryAttribute(String name, WeblogEntryData entry)
+            throws RollerException;
+
+    /**
+     * Remove tag with given name from given WeblogEntryData
+     * @param name Name of tag to be removed
+     */
+    public void removeWeblogEntryTag(String name, WeblogEntryData entry)
+            throws RollerException;
+
+    /**
      * Save weblog category.
      */
     public void saveWeblogCategory(WeblogCategoryData cat) throws 
RollerException;
Index: 
src/org/apache/roller/business/hibernate/HibernateBookmarkManagerImpl.java
===================================================================
--- src/org/apache/roller/business/hibernate/HibernateBookmarkManagerImpl.java  
(revision 488141)
+++ src/org/apache/roller/business/hibernate/HibernateBookmarkManagerImpl.java  
(working copy)
@@ -81,10 +81,13 @@
     
     
     public void removeBookmark(BookmarkData bookmark) throws RollerException {
+        //Remove the bookmark from its parent folder
+        bookmark.getFolder().getBookmarks().remove(bookmark);
+        //Now remove it from database
         this.strategy.remove(bookmark);
-        
         // update weblog last modified date.  date updated by saveWebsite()
-        
RollerFactory.getRoller().getUserManager().saveWebsite(bookmark.getWebsite());
+        RollerFactory.getRoller().getUserManager()
+                .saveWebsite(bookmark.getWebsite());
     }
     
     
Index: src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java
===================================================================
--- src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java      
(revision 488141)
+++ src/org/apache/roller/business/hibernate/HibernateUserManagerImpl.java      
(working copy)
@@ -26,6 +26,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.Collection;
+
 import org.apache.roller.pojos.StatCount;
 import org.hibernate.Criteria;
 import org.hibernate.HibernateException;
@@ -58,6 +60,7 @@
 import org.apache.roller.pojos.WeblogCategoryData;
 import org.apache.roller.pojos.WeblogEntryData;
 import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.pojos.RoleData;
 import org.hibernate.Query;
 
 
@@ -406,6 +409,20 @@
         website.removePermission(target);
         this.strategy.remove(target);
     }
+
+    public void revokeRole(String roleName, UserData user) throws 
RollerException {
+        RoleData removeme = null;
+        Collection roles = user.getRoles();
+        Iterator iter = roles.iterator();
+        while (iter.hasNext()) {
+            RoleData role = (RoleData) iter.next();
+            if (role.getRole().equals(roleName)) {
+                iter.remove();
+                this.strategy.remove(role);
+            }
+        }
+    }
+
         
     public WebsiteData getWebsite(String id) throws RollerException {
         return (WebsiteData) this.strategy.load(id,WebsiteData.class);
Index: src/org/apache/roller/business/hibernate/HibernateWeblogManagerImpl.java
===================================================================
--- src/org/apache/roller/business/hibernate/HibernateWeblogManagerImpl.java    
(revision 488141)
+++ src/org/apache/roller/business/hibernate/HibernateWeblogManagerImpl.java    
(working copy)
@@ -50,6 +50,7 @@
 import org.apache.roller.pojos.WeblogEntryTagData;
 import org.apache.roller.pojos.WeblogEntryTagAggregateData;
 import org.apache.roller.pojos.WebsiteData;
+import org.apache.roller.pojos.EntryAttributeData;
 import org.apache.roller.util.DateUtil;
 import org.hibernate.Criteria;
 import org.hibernate.HibernateException;
@@ -60,13 +61,7 @@
 import org.hibernate.criterion.MatchMode;
 import org.hibernate.criterion.Order;
 import org.hibernate.criterion.Restrictions;
-import org.hibernate.dialect.DerbyDialect;
-import org.hibernate.dialect.Dialect;
-import org.hibernate.dialect.OracleDialect;
-import org.hibernate.dialect.SQLServerDialect;
-import org.hibernate.engine.SessionFactoryImplementor;
 
-
 /**
  * Hibernate implementation of the WeblogManager.
  */
@@ -547,10 +542,39 @@
             throw new RollerException(e);
         }
     }
-    
-    
-    public WeblogEntryData getWeblogEntryByAnchor(WebsiteData website, String 
anchor) 
+
+    public void removeWeblogEntryAttribute(String name, WeblogEntryData entry)
             throws RollerException {
+        for (Iterator it = entry.getEntryAttributes().iterator(); 
it.hasNext();) {
+            EntryAttributeData entryAttribute = (EntryAttributeData) it.next();
+            if (entryAttribute.getName().equals(name)) {
+                //Call back the entity to adjust its internal state
+                entry.onRemoveEntryAttribute(entryAttribute);
+                //Remove it from the collection
+                it.remove();
+                //Remove it from database
+                this.strategy.remove(entryAttribute);
+            }
+        }
+    }
+
+    public void removeWeblogEntryTag(String name, WeblogEntryData entry)
+            throws RollerException {
+        for (Iterator it = entry.getTags().iterator(); it.hasNext();) {
+            WeblogEntryTagData tag = (WeblogEntryTagData) it.next();
+            if (tag.getName().equals(name)) {
+                //Call back the entity to adjust its internal state
+                entry.onRemoveTag(name);
+                //Remove it from the collection
+                it.remove();
+                //Remove it from database
+                this.strategy.remove(tag);
+            }
+        }
+    }
+
+    public WeblogEntryData getWeblogEntryByAnchor(WebsiteData website, String 
anchor)
+            throws RollerException {
         
         if (website == null)
             throw new RollerException("Website is null");
Index: 
src/org/apache/roller/ui/authoring/struts/actions/WeblogEntryFormAction.java
===================================================================
--- 
src/org/apache/roller/ui/authoring/struts/actions/WeblogEntryFormAction.java    
    (revision 488141)
+++ 
src/org/apache/roller/ui/authoring/struts/actions/WeblogEntryFormAction.java    
    (working copy)
@@ -513,10 +513,11 @@
         }
         if (!valid || empty) {
             mLogger.debug("Removing MediaCast attributes");
+            WeblogManager weblogManager = 
RollerFactory.getRoller().getWeblogManager();
             try {
-                entry.removeEntryAttribute("att_mediacast_url");
-                entry.removeEntryAttribute("att_mediacast_type");
-                entry.removeEntryAttribute("att_mediacast_length");
+                weblogManager.removeWeblogEntryAttribute("att_mediacast_url", 
entry);
+                weblogManager.removeWeblogEntryAttribute("att_mediacast_type", 
entry);
+                
weblogManager.removeWeblogEntryAttribute("att_mediacast_length", entry);
             } catch (RollerException e) {
                 mLogger.error("ERROR removing invalid MediaCast attributes");
             }
Index: src/org/apache/roller/ui/admin/struts/formbeans/UserAdminForm.java
===================================================================
--- src/org/apache/roller/ui/admin/struts/formbeans/UserAdminForm.java  
(revision 488141)
+++ src/org/apache/roller/ui/admin/struts/formbeans/UserAdminForm.java  
(working copy)
@@ -23,6 +23,9 @@
 
 import org.apache.struts.action.ActionMapping;
 import org.apache.roller.RollerException;
+import org.apache.roller.business.Roller;
+import org.apache.roller.business.RollerImpl;
+import org.apache.roller.business.RollerFactory;
 import org.apache.roller.pojos.UserData;
 import org.apache.roller.ui.authoring.struts.forms.UserForm;
 
@@ -118,7 +121,7 @@
         }
         else
         {
-            user.revokeRole("admin");
+            RollerFactory.getRoller().getUserManager().revokeRole("admin", 
user);
         }
     }
 
Index: tests/org/apache/roller/business/WeblogEntryTest.java
===================================================================
--- tests/org/apache/roller/business/WeblogEntryTest.java       (revision 
488141)
+++ tests/org/apache/roller/business/WeblogEntryTest.java       (working copy)
@@ -415,8 +415,8 @@
         TestUtils.endSession(true);
 
         entry = mgr.getWeblogEntry(id);
-        entry.removeTag("testtag");
-        entry.removeTag("testtag2");
+        mgr.removeWeblogEntryTag("testtag", entry);
+        mgr.removeWeblogEntryTag("testtag2", entry);
         mgr.saveWeblogEntry(entry);
         TestUtils.endSession(true);
 
Index: tests/org/apache/roller/business/BookmarkTest.java
===================================================================
--- tests/org/apache/roller/business/BookmarkTest.java  (revision 488141)
+++ tests/org/apache/roller/business/BookmarkTest.java  (working copy)
@@ -328,7 +328,7 @@
         bookmarkb = (BookmarkData)testFolder.getBookmarks().iterator().next();
         
         // Remove one bookmark
-        testFolder.removeBookmark(bookmarka);
+        bmgr.removeBookmark(bookmarka);
         bmgr.saveFolder(testFolder);
         TestUtils.endSession(true);
         
Index: tests/org/apache/roller/business/UserTest.java
===================================================================
--- tests/org/apache/roller/business/UserTest.java      (revision 488141)
+++ tests/org/apache/roller/business/UserTest.java      (working copy)
@@ -186,7 +186,7 @@
         assertTrue(user.hasRole("admin"));
         
         // remove role
-        user.revokeRole("admin");
+        mgr.revokeRole("admin",user);
         mgr.saveUser(user);
         TestUtils.endSession(true);
         

Reply via email to