package ilarkesto.persistence; import ilarkesto.base.Utl; import ilarkesto.core.logging.Log; import ilarkesto.fp.Predicate; import ilarkesto.id.IdentifiableResolver; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; class Transaction implements IdentifiableResolver<AEntity> { private static final Log LOG = Log.get(Transaction.class); private static int count = 0; private List<AEntity> entitiesToSave = new ArrayList<AEntity>(); private List<AEntity> entitiesToDelete = new ArrayList<AEntity>(); private List<AEntity> entitiesRegistered = new ArrayList<AEntity>(); /** * avoids infinite loops within <code>ensureIntegrity()</code> */ private AEntity currentlySaving; public synchronized void saveEntity(AEntity entity) { if (currentlySaving == entity) return; currentlySaving = entity; if (entitiesToSave.contains(entity) || entitiesToDelete.contains(entity)) return; LOG.debug("SAVE", Utl.toStringWithType(entity), "@", this); entitiesToSave.add(entity); currentlySaving = null; } public synchronized void deleteEntity(AEntity entity) { if (entitiesToDelete.contains(entity)) return; LOG.debug("DELETE", Utl.toStringWithType(entity), "@", this); entitiesToDelete.add(entity); entitiesToSave.remove(entity); } public synchronized void registerEntity(AEntity entity) { entitiesRegistered.add(entity); } boolean committed; synchronized void commit() { if (committed) throw new RuntimeException("Transaction already committed: " + this); committed = true; if (isEmpty()) { LOG.debug("Committing empty transaction:", this); } else { LOG.info("Committing transaction:", this); } Collection<AEntity> savedEntities = new HashSet<AEntity>(entitiesToSave.size()); while (!isEmpty()) { for (AEntity entity : new ArrayList<AEntity>(entitiesToSave)) { entityStore.save(entity); entitiesToSave.remove(entity); savedEntities.add(entity); } for (AEntity entity : new ArrayList<AEntity>(entitiesToDelete)) { entityStore.delete(entity); entitiesToDelete.remove(entity); } for (AEntity entity : savedEntities) { entity.ensureIntegrity(); } } entitiesRegistered.clear(); LOG.debug("Transaction committed:", this); } public synchronized boolean isEmpty() { return entitiesToDelete.isEmpty() && entitiesToSave.isEmpty(); } @Override public synchronized List<AEntity> getByIds(Collection<String> ids) { List<AEntity> result = entityStore.getByIds(ids); for (AEntity entity : entitiesToSave) { if (ids.contains(entity.getId())) { result.remove(entity); result.add(entity); } } for (AEntity entity : entitiesRegistered) { if (ids.contains(entity.getId())) { result.remove(entity); result.add(entity); } } result.removeAll(entitiesToDelete); return result; } public synchronized Set<AEntity> getEntities(Predicate<Class> typeFilter, Predicate<AEntity> entityFilter) { Set<AEntity> result = entityStore.getEntities(typeFilter, entityFilter); for (AEntity entity : entitiesToSave) { if (Persist.test(entity, typeFilter, entityFilter)) result.add(entity); } for (AEntity entity : entitiesRegistered) { if (Persist.test(entity, typeFilter, entityFilter)) result.add(entity); } result.removeAll(entitiesToDelete); return result; } public int getEntitiesCount(Predicate<Class> typeFilter, Predicate<AEntity> entityFilter) { return entityStore.getEntitiesCount(typeFilter, entityFilter); } @Override public synchronized AEntity getById(String id) { AEntity result = entityStore.getById(id); if (result == null) { for (AEntity entity : entitiesToSave) { if (id.equals(entity.getId())) return entity; } for (AEntity entity : entitiesRegistered) { if (id.equals(entity.getId())) return entity; } } else { if (entitiesToDelete.contains(result)) return null; } return result; } public synchronized AEntity getEntity(Predicate<Class> typeFilter, Predicate<AEntity> entityFilter) { AEntity result = entityStore.getEntity(typeFilter, entityFilter); if (result == null) { for (AEntity entity : entitiesToSave) { if (Persist.test(entity, typeFilter, entityFilter) && !entitiesToDelete.contains(entity)) return entity; } for (AEntity entity : entitiesRegistered) { if (Persist.test(entity, typeFilter, entityFilter) && !entitiesToDelete.contains(entity)) return entity; } } else { if (entitiesToDelete.contains(result)) return null; } return result; } @Override public synchronized String toString() { StringBuilder sb = new StringBuilder(); sb.append("#").append(no); sb.append(" (").append(threadName).append(")"); if (!entitiesToSave.isEmpty()) { sb.append("\n SAVE: ").append(toString(entitiesToSave)); } if (!entitiesRegistered.isEmpty()) { sb.append("\n REGISTERED: ").append(toString(entitiesRegistered)); } if (!entitiesToDelete.isEmpty()) { sb.append("\n DELETE: ").append(toString(entitiesToDelete)); } return sb.toString(); } private String toString(Collection<AEntity> entities) { StringBuilder sb = new StringBuilder(); for (AEntity entity : entities) { if (entity == null) { sb.append("\n null"); } else { sb.append("\n ").append(entity.getClass().getSimpleName()).append(": ") .append(entity.toString()); } } return sb.toString(); } // --- dependencies --- private EntityStore entityStore; private int no; private String threadName; public Transaction(EntityStore entityStore) { synchronized (getClass()) { no = ++count; } this.entityStore = entityStore; threadName = Thread.currentThread().getName(); } }