killbill-uncached

Merge branch 'integration' of github.com:ning/killbill

6/11/2012 4:37:05 PM

Details

diff --git a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
index 15f405e..e2cc293 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AccountEmailSqlDao.java
@@ -16,13 +16,9 @@
 
 package com.ning.billing.account.dao;
 
-import com.ning.billing.account.api.AccountEmail;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.EntityHistory;
-import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.dao.ObjectTypeBinder;
-import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+import java.util.Collection;
+import java.util.List;
+
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
@@ -30,7 +26,13 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
-import java.util.List;
+import com.ning.billing.account.api.AccountEmail;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(AccountEmailMapper.class)
@@ -39,21 +41,21 @@ public interface AccountEmailSqlDao extends UpdatableEntityCollectionSqlDao<Acco
     @SqlBatch(transactional=false)
     public void insertFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @AccountEmailBinder final List<AccountEmail> entities,
+                                      @AccountEmailBinder final Collection<AccountEmail> entities,
                                       @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
     public void updateFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @AccountEmailBinder final List<AccountEmail> entities,
+                                      @AccountEmailBinder final Collection<AccountEmail> entities,
                                       @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
     public void deleteFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @AccountEmailBinder final List<AccountEmail> entities,
+                                      @AccountEmailBinder final Collection<AccountEmail> entities,
                                       @CallContextBinder final CallContext context);
 
     @Override
diff --git a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
index 6ea0786..664d9cc 100644
--- a/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
+++ b/account/src/main/java/com/ning/billing/account/dao/AuditedAccountEmailDao.java
@@ -16,6 +16,13 @@
 
 package com.ning.billing.account.dao;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
 import com.google.inject.Inject;
 import com.ning.billing.account.api.AccountEmail;
 import com.ning.billing.util.callcontext.CallContext;
@@ -23,14 +30,8 @@ import com.ning.billing.util.dao.AuditedCollectionDaoBase;
 import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-
-public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmail> implements AccountEmailDao {
+public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmail, AccountEmail> implements AccountEmailDao {
     private final AccountEmailSqlDao accountEmailSqlDao;
 
     @Inject
@@ -39,6 +40,11 @@ public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmai
     }
 
     @Override
+    protected AccountEmail getEquivalenceObjectFor(AccountEmail obj) {
+        return obj;
+    }
+
+    @Override
     public List<AccountEmail> getEmails(final UUID accountId) {
         return new ArrayList<AccountEmail>(super.loadEntities(accountId, ObjectType.ACCOUNT_EMAIL).values());
     }
@@ -53,6 +59,7 @@ public class AuditedAccountEmailDao extends AuditedCollectionDaoBase<AccountEmai
         return entity.getEmail();
     }
 
+    @Override
     public void test() {
         accountEmailSqlDao.test();
     }
diff --git a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
index fed7517..422221a 100644
--- a/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
+++ b/api/src/main/java/com/ning/billing/util/api/TagUserApi.java
@@ -20,9 +20,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.invoice.api.InvoiceApiException;
 import com.ning.billing.util.callcontext.CallContext;
-
 import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
@@ -62,7 +60,7 @@ public interface TagUserApi {
     public void deleteTagDefinition(String definitionName, CallContext context) throws TagDefinitionApiException;
 
 	/**
-	 * 
+	 *
 	 * @param name
 	 * @return the tag with this definition
      * @throws TagDefinitionApiException
diff --git a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
index b1ab41f..c957c86 100644
--- a/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
+++ b/jaxrs/src/main/java/com/ning/billing/jaxrs/resources/InvoiceResource.java
@@ -124,8 +124,13 @@ public class InvoiceResource extends JaxRsResourceBase {
     @Produces(APPLICATION_JSON)
     public Response getInvoice(@PathParam("invoiceId") String invoiceId, @QueryParam("withItems") @DefaultValue("false") boolean withItems) {
         Invoice invoice = invoiceApi.getInvoice(UUID.fromString(invoiceId));
-        InvoiceJsonSimple json = withItems ? new InvoiceJsonWithItems(invoice) : new InvoiceJsonSimple(invoice);  
-        return Response.status(Status.OK).entity(json).build();
+        if (invoice == null) {
+            return Response.status(Status.NO_CONTENT).build();
+        }
+        else {
+            InvoiceJsonSimple json = withItems ? new InvoiceJsonWithItems(invoice) : new InvoiceJsonSimple(invoice);
+            return Response.status(Status.OK).entity(json).build();
+        }
     }
 
     @POST
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
index 6cf6f0c..e5d5ad0 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/AuditedCustomFieldDao.java
@@ -16,15 +16,16 @@
 
 package com.ning.billing.util.customfield.dao;
 
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
 import com.google.inject.Inject;
 import com.ning.billing.util.customfield.CustomField;
 import com.ning.billing.util.dao.AuditedCollectionDaoBase;
 import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 
-public class AuditedCustomFieldDao extends AuditedCollectionDaoBase<CustomField> implements CustomFieldDao {
+public class AuditedCustomFieldDao extends AuditedCollectionDaoBase<CustomField, String> implements CustomFieldDao {
     private final CustomFieldSqlDao dao;
 
     @Inject
@@ -38,6 +39,11 @@ public class AuditedCustomFieldDao extends AuditedCollectionDaoBase<CustomField>
     }
 
     @Override
+    protected String getEquivalenceObjectFor(CustomField obj) {
+        return obj.getName();
+    }
+
+    @Override
     protected UpdatableEntityCollectionSqlDao<CustomField> transmogrifyDao(Transmogrifier transactionalDao) {
         return transactionalDao.become(CustomFieldSqlDao.class);
     }
diff --git a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
index e73dbf4..4da5a59 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/dao/CustomFieldSqlDao.java
@@ -16,21 +16,23 @@
 
 package com.ning.billing.util.customfield.dao;
 
+import java.util.Collection;
 import java.util.List;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.EntityHistory;
-import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.dao.ObjectTypeBinder;
-import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 
 @ExternalizedSqlViaStringTemplate3
 @RegisterMapper(CustomFieldMapper.class)
@@ -40,21 +42,21 @@ public interface CustomFieldSqlDao extends UpdatableEntityCollectionSqlDao<Custo
     @SqlBatch(transactional=false)
     public void insertFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @CustomFieldBinder final List<CustomField> entities,
+                                      @CustomFieldBinder final Collection<CustomField> entities,
                                       @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
     public void updateFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @CustomFieldBinder final List<CustomField> entities,
+                                      @CustomFieldBinder final Collection<CustomField> entities,
                                       @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
     public void deleteFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @CustomFieldBinder final List<CustomField> entities,
+                                      @CustomFieldBinder final Collection<CustomField> entities,
                                       @CallContextBinder final CallContext context);
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
index 074f322..7b7268a 100644
--- a/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
+++ b/util/src/main/java/com/ning/billing/util/customfield/StringCustomField.java
@@ -17,10 +17,11 @@
 package com.ning.billing.util.customfield;
 
 import java.util.UUID;
+
 import com.ning.billing.util.entity.UpdatableEntityBase;
 
 public class StringCustomField extends UpdatableEntityBase implements CustomField {
-    private String name;
+    private final String name;
     private String value;
 
     public StringCustomField(String name, String value) {
@@ -51,22 +52,41 @@ public class StringCustomField extends UpdatableEntityBase implements CustomFiel
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        StringCustomField that = (StringCustomField) o;
-
-        if (name != null ? !name.equals(that.name) : that.name != null) return false;
-        //if (value != null ? !value.equals(that.value) : that.value != null) return false;
-
-        return true;
+    public String toString() {
+        return "StringCustomField [name=" + name + ", value=" + value + ", id=" + id + "]";
     }
 
     @Override
     public int hashCode() {
-        int result = name != null ? name.hashCode() : 0;
-        result = 31 * result + (value != null ? value.hashCode() : 0);
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((value == null) ? 0 : value.hashCode());
         return result;
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        StringCustomField other = (StringCustomField) obj;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        }
+        else if (!name.equals(other.name))
+            return false;
+        if (value == null) {
+            if (other.value != null)
+                return false;
+        }
+        else if (!value.equals(other.value))
+            return false;
+        return true;
+    }
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
index 6c09c85..3cf6620 100644
--- a/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditedCollectionDaoBase.java
@@ -16,71 +16,96 @@
 
 package com.ning.billing.util.dao;
 
-import com.ning.billing.util.ChangeType;
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.entity.Entity;
-import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
-import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
-public abstract class AuditedCollectionDaoBase<T extends Entity> implements AuditedCollectionDao<T> {
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
+import com.google.common.collect.Sets;
+import com.ning.billing.util.ChangeType;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.entity.Entity;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
+
+public abstract class AuditedCollectionDaoBase<T extends Entity, V> implements AuditedCollectionDao<T> {
+    /**
+     * Returns equivalence object for the entities, so that dao
+     * can figure out if entities have changed (UPDATE statement) or
+     * are new (INSERT statement).
+     * If two entities return the equivalence objects that are equal themselves
+     * (and have the same hashCode), then the entities are equivalent.
+     * For example, two custom field instances are equivalent (describe the same
+     * custom field) if their name is the same (the equivalence object is the
+     * name string). The instances can still have different custom field values
+     * (represent two different 'assignments' to that
+     * field), which should result in UPDATE statements in the dao.
+     */
+    protected abstract V getEquivalenceObjectFor(T obj);
+
     @Override
-    public void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType, List<T> entities, CallContext context) {
+    public void saveEntitiesFromTransaction(Transmogrifier transactionalDao, UUID objectId, ObjectType objectType, List<T> newEntities, CallContext context) {
         UpdatableEntityCollectionSqlDao<T> dao = transmogrifyDao(transactionalDao);
 
         // get list of existing entities
-        List<T> existingEntities = dao.load(objectId.toString(), objectType);
-        List<T> entitiesToUpdate = new ArrayList<T>();
-
-        // sort into entities to update (entitiesToUpdate), entities to add (entities), and entities to delete (existingEntities)
-        Iterator<T> entityIterator = entities.iterator();
-        while (entityIterator.hasNext()) {
-            T entity = entityIterator.next();
-
-            Iterator<T> existingEntityIterator = existingEntities.iterator();
-            while (existingEntityIterator.hasNext()) {
-                T existingEntity = existingEntityIterator.next();
-                if (entity.equals(existingEntity)) {
-                    // if the entities match, remove from both lists
-                    // this requires that the id is *not* part of the equals statement
-                    entityIterator.remove();
-                    existingEntityIterator.remove();
-
-                    // if the entities have the same hash code (e.g. same data), don't bother updating
-                    if (entity.hashCode() != existingEntity.hashCode()) {
-                        entitiesToUpdate.add(entity);
-                    }
-                }
+        List<T> currentEntities = dao.load(objectId.toString(), objectType);
+
+        Map<V, T> currentObjs = new HashMap<V, T>(currentEntities.size());
+        Map<V, T> updatedObjs = new HashMap<V, T>(newEntities.size());
+
+        for (T currentObj : currentEntities) {
+            currentObjs.put(getEquivalenceObjectFor(currentObj), currentObj);
+        }
+        for (T updatedObj : newEntities) {
+            updatedObjs.put(getEquivalenceObjectFor(updatedObj), updatedObj);
+        }
+
+        Set<V> equivToRemove = Sets.difference(currentObjs.keySet(), updatedObjs.keySet());
+        Set<V> equivToAdd = Sets.difference(updatedObjs.keySet(), currentObjs.keySet());
+        Set<V> equivToCheckForUpdate = Sets.intersection(updatedObjs.keySet(), currentObjs.keySet());
+
+        List<T> objsToAdd = new ArrayList<T>(equivToAdd.size());
+        List<T> objsToRemove = new ArrayList<T>(equivToRemove.size());
+        List<T> objsToUpdate = new ArrayList<T>(equivToCheckForUpdate.size());
+
+        for (V equiv : equivToAdd) {
+            objsToAdd.add(updatedObjs.get(equiv));
+        }
+        for (V equiv : equivToRemove) {
+            objsToRemove.add(currentObjs.get(equiv));
+        }
+        for (V equiv : equivToCheckForUpdate) {
+            T currentObj = currentObjs.get(equiv);
+            T updatedObj = updatedObjs.get(equiv);
+            if (!currentObj.equals(updatedObj)) {
+                objsToUpdate.add(updatedObj);
             }
         }
 
-        if (entities.size() != 0) {
-            dao.insertFromTransaction(objectId.toString(), objectType, entities, context);
+        if (objsToAdd.size() != 0) {
+            dao.insertFromTransaction(objectId.toString(), objectType, objsToAdd, context);
         }
 
-        if (entitiesToUpdate.size() != 0) {
-            dao.updateFromTransaction(objectId.toString(), objectType, entitiesToUpdate, context);
+        if (objsToUpdate.size() != 0) {
+            dao.updateFromTransaction(objectId.toString(), objectType, objsToUpdate, context);
         }
 
         // get all custom entities (including those that are about to be deleted) from the database in order to get the record ids
         List<Mapper<UUID, Long>> recordIds = dao.getRecordIds(objectId.toString(), objectType);
         Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
 
-        if (existingEntities.size() != 0) {
-            dao.deleteFromTransaction(objectId.toString(), objectType, existingEntities, context);
+        if (objsToRemove.size() != 0) {
+            dao.deleteFromTransaction(objectId.toString(), objectType, objsToRemove, context);
         }
 
         List<EntityHistory<T>> entityHistories = new ArrayList<EntityHistory<T>>();
-        entityHistories.addAll(convertToHistory(entities, recordIdMap, ChangeType.INSERT));
-        entityHistories.addAll(convertToHistory(entitiesToUpdate, recordIdMap, ChangeType.UPDATE));
-        entityHistories.addAll(convertToHistory(existingEntities, recordIdMap, ChangeType.DELETE));
+        entityHistories.addAll(convertToHistory(objsToAdd, recordIdMap, ChangeType.INSERT));
+        entityHistories.addAll(convertToHistory(objsToUpdate, recordIdMap, ChangeType.UPDATE));
+        entityHistories.addAll(convertToHistory(objsToRemove, recordIdMap, ChangeType.DELETE));
 
         Long maxHistoryRecordId = dao.getMaxHistoryRecordId();
         dao.addHistoryFromTransaction(objectId.toString(), objectType, entityHistories, context);
@@ -119,7 +144,7 @@ public abstract class AuditedCollectionDaoBase<T extends Entity> implements Audi
         return results;
     }
 
-    protected List<EntityHistory<T>> convertToHistory(List<T> entities, Map<UUID, Long> recordIds, ChangeType changeType) {
+    protected List<EntityHistory<T>> convertToHistory(Collection<T> entities, Map<UUID, Long> recordIds, ChangeType changeType) {
         List<EntityHistory<T>> histories = new ArrayList<EntityHistory<T>>();
 
         for (T entity : entities) {
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java b/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java
index d488c6b..4f9c256 100644
--- a/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java
@@ -16,15 +16,16 @@
 
 package com.ning.billing.util.dao;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
+import java.util.List;
+
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
 
-import java.util.List;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
 
 @ExternalizedSqlViaStringTemplate3
 public interface AuditSqlDao {
@@ -41,4 +42,5 @@ public interface AuditSqlDao {
 
     @SqlQuery
     public Long getHistoryRecordId(@Bind("recordId") final Long recordId);
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java b/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java
index 475ae8d..e74a64a 100644
--- a/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/collection/dao/EntityCollectionSqlDao.java
@@ -16,13 +16,10 @@
 
 package com.ning.billing.util.entity.collection.dao;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.Mapper;
-import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.dao.ObjectTypeBinder;
-import com.ning.billing.util.dao.RecordIdMapper;
-import com.ning.billing.util.entity.Entity;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
+
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
@@ -30,8 +27,13 @@ import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 
-import java.util.List;
-import java.util.UUID;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.Mapper;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.dao.RecordIdMapper;
+import com.ning.billing.util.entity.Entity;
 
 /**
  * provides consistent semantics for entity collections
@@ -42,13 +44,13 @@ public interface EntityCollectionSqlDao<T extends Entity> {
     @SqlBatch(transactional=false)
     public void insertFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @BindBean final List<T> entities,
+                                      @BindBean final Collection<T> entities,
                                       @CallContextBinder final CallContext context);
 
     @SqlBatch(transactional=false)
     public void deleteFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @BindBean final List<T> entities,
+                                      @BindBean final Collection<T> entities,
                                       @CallContextBinder final CallContext context);
 
     @SqlQuery
diff --git a/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
index bdaf1c9..0079008 100644
--- a/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/collection/dao/UpdatableEntityCollectionSqlDao.java
@@ -16,6 +16,17 @@
 
 package com.ning.billing.util.entity.collection.dao;
 
+import java.util.Collection;
+import java.util.List;
+
+import org.skife.jdbi.v2.sqlobject.Bind;
+import org.skife.jdbi.v2.sqlobject.BindBean;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
+import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
+import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallContextBinder;
 import com.ning.billing.util.dao.AuditSqlDao;
@@ -25,15 +36,6 @@ import com.ning.billing.util.dao.Mapper;
 import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.dao.ObjectTypeBinder;
 import com.ning.billing.util.entity.Entity;
-import org.skife.jdbi.v2.sqlobject.Bind;
-import org.skife.jdbi.v2.sqlobject.BindBean;
-import org.skife.jdbi.v2.sqlobject.SqlBatch;
-import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
-import org.skife.jdbi.v2.sqlobject.mixins.CloseMe;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-
-import java.util.List;
 
 public interface UpdatableEntityCollectionSqlDao<T extends Entity> extends EntityCollectionSqlDao<T>,
         CollectionHistorySqlDao<T>,
@@ -41,7 +43,7 @@ public interface UpdatableEntityCollectionSqlDao<T extends Entity> extends Entit
     @SqlBatch(transactional=false)
     public void updateFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @BindBean final List<T> entities,
+                                      @BindBean final Collection<T> entities,
                                       @CallContextBinder final CallContext context);
 
     @SqlQuery
diff --git a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java
index b988bde..4a065de 100644
--- a/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java
+++ b/util/src/main/java/com/ning/billing/util/tag/api/DefaultTagUserApi.java
@@ -20,16 +20,14 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import com.ning.billing.invoice.api.InvoiceApiException;
-import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.tag.dao.TagDao;
-
 import com.google.inject.Inject;
-import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.api.TagDefinitionApiException;
 import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.dao.ObjectType;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
+import com.ning.billing.util.tag.dao.TagDao;
 import com.ning.billing.util.tag.dao.TagDefinitionDao;
 
 public class DefaultTagUserApi implements TagUserApi {
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
index 2ffe7fc..a803660 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/AuditedTagDao.java
@@ -16,6 +16,17 @@
 
 package com.ning.billing.util.tag.dao;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.skife.jdbi.v2.IDBI;
+import org.skife.jdbi.v2.Transaction;
+import org.skife.jdbi.v2.TransactionStatus;
+import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
+
 import com.google.inject.Inject;
 import com.ning.billing.ErrorCode;
 import com.ning.billing.invoice.api.InvoiceApiException;
@@ -33,17 +44,8 @@ import com.ning.billing.util.tag.DefaultControlTag;
 import com.ning.billing.util.tag.DescriptiveTag;
 import com.ning.billing.util.tag.Tag;
 import com.ning.billing.util.tag.TagDefinition;
-import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.Transaction;
-import org.skife.jdbi.v2.TransactionStatus;
-import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
 
-public class AuditedTagDao extends AuditedCollectionDaoBase<Tag> implements TagDao {
+public class AuditedTagDao extends AuditedCollectionDaoBase<Tag, Tag> implements TagDao {
     private final TagSqlDao tagSqlDao;
 
     @Inject
@@ -52,6 +54,11 @@ public class AuditedTagDao extends AuditedCollectionDaoBase<Tag> implements TagD
     }
 
     @Override
+    protected Tag getEquivalenceObjectFor(Tag obj) {
+        return obj;
+    }
+
+    @Override
     public void insertTag(final UUID objectId, final ObjectType objectType,
                           final TagDefinition tagDefinition, final CallContext context) {
         tagSqlDao.inTransaction(new Transaction<Void, TagSqlDao>() {
@@ -113,8 +120,7 @@ public class AuditedTagDao extends AuditedCollectionDaoBase<Tag> implements TagD
                     throw new InvoiceApiException(ErrorCode.TAG_DOES_NOT_EXIST, tagName);
                 }
 
-                List<Tag> tagList = new ArrayList<Tag>();
-                tagList.add(tag);
+                List<Tag> tagList = Arrays.asList(tag);
 
                 List<Mapper<UUID, Long>> recordIds = tagSqlDao.getRecordIds(objectId.toString(), objectType);
                 Map<UUID, Long> recordIdMap = convertToHistoryMap(recordIds);
diff --git a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
index ee62b60..bde62e7 100644
--- a/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/tag/dao/TagSqlDao.java
@@ -16,18 +16,9 @@
 
 package com.ning.billing.util.tag.dao;
 
+import java.util.Collection;
 import java.util.List;
 
-import com.ning.billing.util.callcontext.CallContext;
-import com.ning.billing.util.callcontext.CallContextBinder;
-import com.ning.billing.util.dao.AuditBinder;
-import com.ning.billing.util.dao.EntityAudit;
-import com.ning.billing.util.dao.EntityHistory;
-import com.ning.billing.util.dao.ObjectType;
-import com.ning.billing.util.dao.ObjectTypeBinder;
-import com.ning.billing.util.dao.TableName;
-import com.ning.billing.util.dao.TableNameBinder;
-import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
@@ -36,6 +27,13 @@ import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 import org.skife.jdbi.v2.sqlobject.mixins.Transactional;
 import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import org.skife.jdbi.v2.sqlobject.stringtemplate.ExternalizedSqlViaStringTemplate3;
+
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextBinder;
+import com.ning.billing.util.dao.EntityHistory;
+import com.ning.billing.util.dao.ObjectType;
+import com.ning.billing.util.dao.ObjectTypeBinder;
+import com.ning.billing.util.entity.collection.dao.UpdatableEntityCollectionSqlDao;
 import com.ning.billing.util.tag.Tag;
 
 @ExternalizedSqlViaStringTemplate3
@@ -45,21 +43,21 @@ public interface TagSqlDao extends UpdatableEntityCollectionSqlDao<Tag>, Transac
     @SqlBatch(transactional=false)
     public void insertFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @TagBinder final List<Tag> tags,
+                                      @TagBinder final Collection<Tag> tags,
                                       @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
     public void updateFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @TagBinder final List<Tag> tags,
+                                      @TagBinder final Collection<Tag> tags,
                                       @CallContextBinder final CallContext context);
 
     @Override
     @SqlBatch(transactional=false)
     public void deleteFromTransaction(@Bind("objectId") final String objectId,
                                       @ObjectTypeBinder final ObjectType objectType,
-                                      @TagBinder final List<Tag> tags,
+                                      @TagBinder final Collection<Tag> tags,
                                       @CallContextBinder final CallContext context);
 
     @Override
diff --git a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
index b6d502f..330b204 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DefaultControlTag.java
@@ -40,23 +40,30 @@ public class DefaultControlTag extends DescriptiveTag implements ControlTag {
 
     @Override
     public String toString() {
-        return controlTagType.toString();
+        return "DefaultControlTag [controlTagType=" + controlTagType + ", id=" + id + "]";
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        DefaultControlTag that = (DefaultControlTag) o;
-
-        if (controlTagType != that.controlTagType) return false;
-
-        return true;
+    public int hashCode() {
+        final int prime = 31;
+        int result = super.hashCode();
+        result = prime * result + ((controlTagType == null) ? 0
+                                                           : controlTagType.hashCode());
+        return result;
     }
 
     @Override
-    public int hashCode() {
-        return controlTagType != null ? controlTagType.hashCode() : 0;
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (!super.equals(obj))
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DefaultControlTag other = (DefaultControlTag) obj;
+        if (controlTagType != other.controlTagType)
+            return false;
+        return true;
     }
+
 }
diff --git a/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java b/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
index c3d8f93..3f875be 100644
--- a/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
+++ b/util/src/main/java/com/ning/billing/util/tag/DescriptiveTag.java
@@ -48,24 +48,34 @@ public class DescriptiveTag extends EntityBase implements Tag {
 
     @Override
     public String toString() {
-        return tagDefinitionName;
+        return "DescriptiveTag [tagDefinitionName=" + tagDefinitionName + ", id=" + id + "]";
     }
 
     @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        DescriptiveTag that = (DescriptiveTag) o;
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((tagDefinitionName == null) ? 0
+                                                              : tagDefinitionName.hashCode());
+        return result;
+    }
 
-        if (tagDefinitionName != null ? !tagDefinitionName.equals(that.tagDefinitionName) : that.tagDefinitionName != null)
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        DescriptiveTag other = (DescriptiveTag) obj;
+        if (tagDefinitionName == null) {
+            if (other.tagDefinitionName != null)
+                return false;
+        }
+        else if (!tagDefinitionName.equals(other.tagDefinitionName))
             return false;
-
         return true;
     }
 
-    @Override
-    public int hashCode() {
-        return tagDefinitionName != null ? tagDefinitionName.hashCode() : 0;
-    }
 }