Photo from Chile

CF9 ORM Quick Tip - Removing all items from a collection defined as a structure

I wrote a ColdFusion ORM Quick Tip awhile back on removing all items from a collection, which a number of folks were kind enough to comment on and provide even better solutions. I was recently faced with the same problem, but this time I wanted to remove all items from a collection that was defined as a structure (last time it was an array). My initial code generated an error, so I had to get creative. I'm putting this out there to see if anyone has any better ideas for doing this.

For this example, let's assume we have a User object and a Department object. The User object is defined like this:

view plain print about
1component persistent="true" output="false" entityname="User" {
2    property name="UserId" fieldtype="id" generator="native";
3    property name="UserName";
4}

The Department has a collection of Users, like so:

view plain print about
1component persistent="true" output="false" entityname="Department" {
2    property name="DeptId" fieldtype="id" generator="native";
3    property name="Name";
4    property name="Users" fieldtype="one-to-many" type="struct"
5        cfc="User" fkcolumn="DeptId" singularname="User"
6        structkeycolumn="UserName" structkeytype="string";
7}

Let's also say that Department 1 currently has 3 Users assigned to it, call them User1, User2 and User3. Now, we want to write some code that will empty the Department's collection of Users. A first attempt might look something like:

view plain print about
1Users = Department.getUsers();
2for (user in Users) {
3    Department.removeUser(user);
4}

Unfortunately, this code throws a java.util.ConcurrentModificationException error. A bit of Googling led me to believe that this is an issue with the way Hibernate is manipulating the collection, which is a Java HashMap. It sounds like when you're iterating over a HashMap you must ask the iterator to remove an item, rather than removing it yourself. This sounds like a bug in Hibernate, but I'd be very surprised if there were such an obvious bug in Hibernate, so maybe it has something to do with the way that Hibernate has been integrated into CF. Either way, it simply doesn't work, so I needed to come up with another way of removing all of the items from the collection.

As with my last post on the topic, the simplest way to achieve this is to call the setter for the collection and pass in an empty struct:

view plain print about
1Department.setUsers({});

The way that I ended up doing it was to use a while loop, checking whether the collection has any remaining items, and to remove the first item in the collection inside the loop. It looks like this:

view plain print about
1Users = Department.getUsers();
2while (Department.hasUser()) {
3    user = listFirst(structKeyList(Users));
4    Department.removeUser(user);
5}

As discussed in the last post, I prefer the latter approach. The first seems a bit hacky to me, although both of them require that you program to the implementation of the collection (you are writing code expecting the collection to be a structure). Also as discussed previously you could make that a bit less painful by moving the code into the actual object itself. For example, in the Department object, add the following method:

view plain print about
1function void removeAllUsers() {
2    var Users = this.getUsers();
3    while (this.hasUser()) {
4        this.removeUser(listFirst(structKeyList(Users)));
5    }
6}

Now you can call removeAllUsers() from outside of the object (e.g., from a Service) and nothing other than the object will have to know that your collection has been implemented as a structure.

What other methods can people think of for achieving this?

TweetBacks
Comments
Or... maybe you can get the structKeyList or structKeyArray, and loop through that list/array instead?
# Posted By Henry Ho | 1/6/10 2:12 PM
In Java (and .NET), it is illegal to modify a collection (any collection) while iterating over that collection. Using the for-in notation constitutes iterating over the collection. While inside the for-in block, therefore, it is illegal to add items to the collection, remove items from it, or update it in any other way. If you do modify the collection while iterating it, then the next iteration will immediately throw an exception.

This is not a bug at all, and certainly not with Hibernate. It is well-known and well-documented behavior applying to all Java (and .NET) collections.
# Posted By Justice | 1/6/10 5:14 PM
@Henry: That's a great idea!

@Justice:I guess I either misread something or what I was reading was incorrect. I don't have any experience writing Java code, but the posts I found when Googling the issue seemed to suggest that it was fine to remove an item from a collection when iterating over it, as long as you asked the Iterator itself to remove it. Here's an example from Stack Overflow: http://stackoverflow.com/questions/602636/concurre.... Note the answer from Robin about halfway down.
# Posted By Bob Silverberg | 1/6/10 10:41 PM
Way late to the party, and only sightly related, but I ran across this java.util.ConcurrentModificationException issue while migrating a site from CF7 to CF9. It's a site that uses client variables, and it has a cleaning script that loops through the client collection, and removes a subset of the keys in the client structure.

CF7 allows this to happen without a problem. CF9 blows up with the java.util.ConcurrentModificationException error.

I don't know Java hardly at all, but in older versions of CF, this was an allowed action.
# Posted By Rob | 5/4/10 10:06 AM
Thanks Bob!
# Posted By Jamie | 2/29/12 9:25 PM