Support deferred delete

This commit is contained in:
Konstantin Krivopustov 2008-12-23 12:38:13 +00:00
parent 4b91f7eb14
commit 68652ec2e4
14 changed files with 519 additions and 17 deletions

View File

@ -17,7 +17,7 @@
<properties>
<property name="openjpa.Log" value="log4j"/>
<property name="openjpa.ConnectionFactoryProperties" value="PrettyPrint=true, PrettyPrintLineLength=72"/>
<property name="openjpa.jdbc.DBDictionary" value="hsql(SimulateLocking=true)"/>
<property name="openjpa.jdbc.DBDictionary" value="com.haulmont.cuba.core.sys.persistence.CubaHSQLDictionary(SimulateLocking=true)"/>
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
<property name="openjpa.jdbc.MappingDefaults"
value="FieldStrategies='java.util.UUID=com.haulmont.cuba.core.sys.persistence.UuidValueHandler'"/>

View File

@ -24,9 +24,17 @@ public interface EntityManager
Query createQuery(String qlStr);
Query createNativeQuery(String sql);
void setView(View view);
void flush();
void close();
boolean isClosed();
boolean isDeleteDeferred();
void setDeleteDeferred(boolean deleteDeferred);
}

View File

@ -16,7 +16,7 @@ import java.util.Arrays;
public abstract class SecurityProvider
{
public static final String IMPL_PROP = "cuba.SecurityProvider.sys";
public static final String IMPL_PROP = "cuba.SecurityProvider.impl";
private static final String DEFAULT_IMPL = "com.haulmont.cuba.core.sys.SecurityProviderImpl";

View File

@ -0,0 +1,20 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 23.12.2008 11:12:20
*
* $Id$
*/
package com.haulmont.cuba.core;
import org.apache.commons.lang.BooleanUtils;
public class Utils
{
public static boolean isUnitTestMode() {
return BooleanUtils.toBoolean(System.getProperty("cuba.UnitTestMode"));
}
}

View File

@ -14,7 +14,7 @@ import com.haulmont.chile.core.model.Session;
public abstract class MetadataProvider
{
public static final String IMPL_PROP = "cuba.MetadataProvider.sys";
public static final String IMPL_PROP = "cuba.MetadataProvider.impl";
private static final String DEFAULT_IMPL = "com.haulmont.cuba.core.sys.MetadataProviderImpl";

View File

@ -15,7 +15,7 @@ import java.util.Date;
public abstract class TimeProvider
{
public static final String IMPL_PROP = "cuba.TimeProvider.sys";
public static final String IMPL_PROP = "cuba.TimeProvider.impl";
private static final String DEFAULT_IMPL = "com.haulmont.cuba.core.sys.TimeProviderImpl";

View File

@ -14,7 +14,7 @@ import java.util.UUID;
public abstract class UuidProvider
{
public static final String IMPL_PROP = "cuba.UuidProvider.sys";
public static final String IMPL_PROP = "cuba.UuidProvider.impl";
private static final String DEFAULT_IMPL = "com.haulmont.cuba.core.sys.UuidProviderImpl";

View File

@ -10,6 +10,9 @@
package com.haulmont.cuba.core.sys;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.haulmont.cuba.core.EntityManager;
@ -20,16 +23,49 @@ import com.haulmont.cuba.core.global.View;
import com.haulmont.cuba.core.entity.BaseEntity;
import com.haulmont.cuba.core.entity.DeleteDeferred;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
public class EntityManagerImpl implements EntityManager
{
public interface CloseListener
{
void onClose();
}
private Log log = LogFactory.getLog(EntityManagerImpl.class);
private OpenJPAEntityManager jpaEm;
private boolean closed;
private Set<CloseListener> closeListeners = new HashSet<CloseListener>();
private boolean deleteDeferred = true;
EntityManagerImpl(OpenJPAEntityManager jpaEntityManager) {
this.jpaEm = jpaEntityManager;
}
public boolean isDeleteDeferred() {
return deleteDeferred;
}
public void setDeleteDeferred(boolean deleteDeferred) {
if (deleteDeferred != this.deleteDeferred) {
// clear SQL queries cache
OpenJPAConfiguration conf = ((OpenJPAEntityManagerFactorySPI) jpaEm.getEntityManagerFactory()).getConfiguration();
if (conf instanceof JDBCConfiguration) {
Map map = ((JDBCConfiguration) conf).getQuerySQLCacheInstance();
for (Object val : map.values()) {
if (val instanceof Map)
((Map) val).clear();
}
}
this.deleteDeferred = deleteDeferred;
}
}
public void persist(BaseEntity entity) {
jpaEm.persist(entity);
}
@ -53,10 +89,15 @@ public class EntityManagerImpl implements EntityManager
}
public Query createQuery(String qlStr) {
log.debug("Creating JPQL query: " + qlStr);
log.trace("Creating JPQL query: " + qlStr);
return new QueryImpl(jpaEm.createQuery(qlStr));
}
public Query createNativeQuery(String sql) {
log.trace("Creating SQL query: " + sql);
return new QueryImpl(jpaEm.createNativeQuery(sql));
}
public void setView(View view) {
ViewHelper.setView(jpaEm.getFetchPlan(), view);
}
@ -67,5 +108,21 @@ public class EntityManagerImpl implements EntityManager
public void close() {
jpaEm.close();
closed = true;
for (CloseListener listener : closeListeners) {
listener.onClose();
}
}
public boolean isClosed() {
return closed;
}
public void addCloseListener(CloseListener listener) {
closeListeners.add(listener);
}
public void removeCloseListener(CloseListener listener) {
closeListeners.remove(listener);
}
}

View File

@ -37,6 +37,8 @@ public class ManagedPersistenceProvider extends PersistenceProvider
private Map<javax.transaction.Transaction, EntityManager> emMap =
new Hashtable<javax.transaction.Transaction, EntityManager>();
private ThreadLocal<EntityManager> emThreadLocal = new ThreadLocal<EntityManager>();
public static final String EMF_JNDI_NAME = "EntityManagerFactoryAdapterImpl";
public static final String TM_JNDI_NAME = "java:/TransactionManager";
@ -100,7 +102,19 @@ public class ManagedPersistenceProvider extends PersistenceProvider
}
else {
log.trace("Creating new non-transactional EntityManager");
em = __getEntityManagerFactory().createEntityManager();
em = emThreadLocal.get();
if (em == null || em.isClosed()) {
em = __getEntityManagerFactory().createEntityManager();
((EntityManagerImpl) em).addCloseListener(
new EntityManagerImpl.CloseListener()
{
public void onClose() {
emThreadLocal.remove();
}
}
);
emThreadLocal.set(em);
}
}
return em;
} catch (NamingException e) {

View File

@ -0,0 +1,144 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 23.12.2008 10:31:32
*
* $Id$
*/
package com.haulmont.cuba.core.sys.persistence;
import org.apache.openjpa.jdbc.sql.*;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.Table;
import java.util.*;
import com.haulmont.cuba.core.PersistenceProvider;
public class CubaHSQLDictionary extends HSQLDictionary
{
private static final String DELETE_TS_COL = "DELETE_TS";
public SQLBuffer toTraditionalJoin(Join join) {
ForeignKey fk = join.getForeignKey();
if (fk == null)
return null;
boolean inverse = join.isForeignKeyInversed();
Column[] from = (inverse) ? fk.getPrimaryKeyColumns()
: fk.getColumns();
Column[] to = (inverse) ? fk.getColumns()
: fk.getPrimaryKeyColumns();
// do column joins
SQLBuffer buf = new SQLBuffer(this);
int count = 0;
for (int i = 0; i < from.length; i++, count++) {
if (count > 0)
buf.append(" AND ");
buf.append(join.getAlias1()).append(".").append(from[i]);
buf.append(" = ");
buf.append(join.getAlias2()).append(".").append(to[i]);
// KK: support deferred delete for collections
if (inverse
&& to[i].getTable().containsColumn(DELETE_TS_COL)
&& PersistenceProvider.getEntityManager().isDeleteDeferred())
{
buf.append(" AND ");
buf.append(join.getAlias2()).append(".").append(DELETE_TS_COL).append(" IS NULL");
}
}
// do constant joins
Column[] constCols = fk.getConstantColumns();
for (int i = 0; i < constCols.length; i++, count++) {
if (count > 0)
buf.append(" AND ");
if (inverse)
buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
else
buf.append(join.getAlias1()).append(".").
append(constCols[i]);
buf.append(" = ");
if (inverse)
buf.append(join.getAlias2()).append(".").
append(constCols[i]);
else
buf.appendValue(fk.getConstant(constCols[i]), constCols[i]);
}
Column[] constColsPK = fk.getConstantPrimaryKeyColumns();
for (int i = 0; i < constColsPK.length; i++, count++) {
if (count > 0)
buf.append(" AND ");
if (inverse)
buf.append(join.getAlias1()).append(".").
append(constColsPK[i]);
else
buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
constColsPK[i]);
buf.append(" = ");
if (inverse)
buf.appendValue(fk.getPrimaryKeyConstant(constColsPK[i]),
constColsPK[i]);
else
buf.append(join.getAlias2()).append(".").
append(constColsPK[i]);
}
return buf;
}
protected SQLBuffer getWhere(Select sel, boolean forUpdate) {
Joins joins = sel.getJoins();
if (sel.getJoinSyntax() == SYNTAX_SQL92
|| joins == null || joins.isEmpty())
{
SQLBuffer buf = sel.getWhere();
if (!PersistenceProvider.getEntityManager().isDeleteDeferred())
return buf;
Map<Table, String> tables = new HashMap<Table, String>();
Collection columns;
if (buf != null) {
columns = buf.getColumns();
}
else {
columns = sel.getSelects();
}
for (Object item : columns) {
if (item instanceof Column) {
Column col = (Column) item;
for (String s : (Collection<String>) sel.getTableAliases()) {
int i = s.indexOf(' ');
String tableName = s.substring(0, i);
if (col.getTable().getName().equals(tableName)) {
if (col.getTable().containsColumn(DELETE_TS_COL))
tables.put(col.getTable(), s.substring(i + 1));
break;
}
}
}
}
StringBuilder sb = new StringBuilder();
for (String alias : tables.values()) {
sb.append(alias).append(".").append(DELETE_TS_COL).append(" IS NULL");
}
sel.where(sb.toString());
return sel.getWhere();
}
SQLBuffer where = new SQLBuffer(this);
if (sel.getWhere() != null)
where.append(sel.getWhere());
if (joins != null)
sel.append(where, joins);
return where;
}
}

View File

@ -42,7 +42,7 @@ public class LoginWorkerBean implements LoginWorker
Query q = em.createQuery(
"select u " +
" from sec$User u join fetch u.profiles" +
" where u.login = ?1 and u.password = ?2 and u.deleteTs is null");
" where u.login = ?1 and u.password = ?2");
q.setParameter(1, login);
q.setParameter(2, password);
List list = q.getResultList();
@ -75,7 +75,7 @@ public class LoginWorkerBean implements LoginWorker
User user = loadUser(login, password, locale);
Profile profile = null;
for (Profile p : user.getProfiles()) {
if (profileName.equals(p.getName()) && !p.isDeleted()) {
if (profileName.equals(p.getName())) {
profile = p;
break;
}

View File

@ -10,18 +10,19 @@
*/
package com.haulmont.cuba.security.listener;
import com.haulmont.cuba.core.listener.BeforeUpdateEntityListener;
import com.haulmont.cuba.core.listener.BeforeInsertEntityListener;
import com.haulmont.cuba.core.listener.BeforeDeleteEntityListener;
import com.haulmont.cuba.core.PersistenceProvider;
import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.PersistenceProvider;
import com.haulmont.cuba.core.Query;
import com.haulmont.cuba.core.Utils;
import com.haulmont.cuba.core.listener.BeforeDeleteEntityListener;
import com.haulmont.cuba.core.listener.BeforeInsertEntityListener;
import com.haulmont.cuba.core.listener.BeforeUpdateEntityListener;
import com.haulmont.cuba.security.entity.Group;
import com.haulmont.cuba.security.entity.GroupHierarchy;
import org.apache.openjpa.enhance.PersistenceCapable;
import java.util.List;
import java.util.ArrayList;
import java.util.List;
public class GroupEntityListener implements
BeforeInsertEntityListener<Group>,
@ -90,6 +91,8 @@ public class GroupEntityListener implements
}
public void onBeforeDelete(Group entity) {
if (Utils.isUnitTestMode())
return;
EntityManager em = PersistenceProvider.getEntityManager();
Query q = em.createQuery("select count(p) from sec$Profile p where p.group = ?1");
q.setParameter(1, entity);

View File

@ -10,12 +10,10 @@
package com.haulmont.cuba.core;
import junit.framework.TestCase;
import com.haulmont.cuba.core.SecurityProvider;
import java.net.URL;
import java.io.File;
public class CubaTestCase extends TestCase
public abstract class CubaTestCase extends TestCase
{
protected void setUpDeploymentFiles() {
TestContainer.addDeploymentFile("cuba-core-global.jar");
@ -25,6 +23,7 @@ public class CubaTestCase extends TestCase
protected void setUp() throws Exception {
super.setUp();
System.setProperty("cuba.UnitTestMode", "true");
File confDir = new File(System.getProperty("user.dir") + "/build/conf");
System.setProperty("jboss.server.config.url", confDir.toURI().toString());

View File

@ -0,0 +1,257 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 22.12.2008 16:57:38
*
* $Id$
*/
package com.haulmont.cuba.core;
import com.haulmont.cuba.security.entity.User;
import com.haulmont.cuba.security.entity.Profile;
import com.haulmont.cuba.security.entity.Group;
import com.haulmont.cuba.core.global.View;
import java.util.UUID;
import java.util.List;
import java.util.Set;
public class DeletedCollectionItemTest extends CubaTestCase
{
private UUID groupId;
private UUID userId;
private UUID profile1Id;
private UUID profile2Id;
protected void setUp() throws Exception {
super.setUp();
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
Group group = new Group();
groupId = group.getId();
group.setName("testGroup");
em.persist(group);
User user = new User();
userId = user.getId();
user.setName("testUser");
user.setLogin("testLogin");
em.persist(user);
Profile profile1 = new Profile();
profile1Id = profile1.getId();
profile1.setName("testProfile1");
profile1.setUser(user);
profile1.setGroup(group);
em.persist(profile1);
Profile profile2 = new Profile();
profile2Id = profile2.getId();
profile2.setName("testProfile2");
profile2.setUser(user);
profile2.setGroup(group);
em.persist(profile2);
tx.commitRetaining();
em = PersistenceProvider.getEntityManager();
Profile profile = em.find(Profile.class, profile2Id);
em.remove(profile);
Group g = em.find(Group.class, groupId);
em.remove(g);
tx.commit();
} finally {
tx.end();
}
}
protected void tearDown() throws Exception {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
Query q = em.createNativeQuery("delete from SEC_PROFILE where ID = ? or ID = ?");
q.setParameter(1, profile1Id.toString());
q.setParameter(2, profile2Id.toString());
q.executeUpdate();
q = em.createNativeQuery("delete from SEC_USER where ID = ?");
q.setParameter(1, userId.toString());
q.executeUpdate();
q = em.createNativeQuery("delete from SEC_GROUP where ID = ?");
q.setParameter(1, groupId.toString());
q.executeUpdate();
tx.commit();
} finally {
tx.end();
}
super.tearDown();
}
public void testNormalMode() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
Group group = em.find(Group.class, groupId);
assertNull(group);
tx.commit();
} finally {
tx.end();
}
}
public void testCleanupMode() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
em.setDeleteDeferred(false);
Group group = em.find(Group.class, groupId);
assertNotNull(group);
assertTrue(group.isDeleted());
tx.commit();
} finally {
tx.end();
}
}
public void testOneToMany() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
em.setView(
new View(User.class, "testView")
.addProperty("name")
.addProperty("login")
.addProperty("profiles",
new View(Profile.class, "testView")
.addProperty("name")
)
);
User user = em.find(User.class, userId);
Set<Profile> profiles = user.getProfiles();
assertEquals(1, profiles.size());
for (Profile profile : profiles) {
System.out.println(profile.getName());
assertEquals("testProfile1", profile.getName());
}
tx.commit();
} finally {
tx.end();
}
}
public void testOneToMany_CleanupMode() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
em.setDeleteDeferred(false);
em.setView(
new View(User.class, "testView")
.addProperty("name")
.addProperty("login")
.addProperty("profiles",
new View(Profile.class, "testView")
.addProperty("name")
)
);
User user = em.find(User.class, userId);
Set<Profile> profiles = user.getProfiles();
assertEquals(2, profiles.size());
for (Profile profile : profiles) {
System.out.println(profile.getName());
}
tx.commit();
} finally {
tx.end();
}
}
public void testOneToMany_Query() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
Query q = em.createQuery("select u from sec$User u where u.id = ?1");
q.setParameter(1, userId);
User user = (User) q.getSingleResult();
Set<Profile> profiles = user.getProfiles();
assertEquals(1, profiles.size());
for (Profile profile : profiles) {
System.out.println(profile.getName());
assertEquals("testProfile1", profile.getName());
}
tx.commit();
} finally {
tx.end();
}
}
public void testOneToMany_JoinFetchQuery() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
Query q = em.createQuery("select u from sec$User u join fetch u.profiles where u.id = ?1");
q.setParameter(1, userId);
User user = (User) q.getSingleResult();
Set<Profile> profiles = user.getProfiles();
assertEquals(1, profiles.size());
for (Profile profile : profiles) {
System.out.println(profile.getName());
assertEquals("testProfile1", profile.getName());
}
tx.commit();
} finally {
tx.end();
}
}
public void testManyToOne() {
Transaction tx = Locator.createTransaction();
try {
EntityManager em = PersistenceProvider.getEntityManager();
em.setView(
new View(Profile.class, "testView")
.addProperty("name")
.addProperty("group",
new View(Group.class, "testView")
.addProperty("name")
)
);
Profile profile = em.find(Profile.class, profile1Id);
assertNotNull(profile.getGroup());
assertTrue(profile.getGroup().isDeleted());
tx.commit();
} finally {
tx.end();
}
}
}