diff --git a/modules/core/src/META-INF/cuba-persistence.xml b/modules/core/src/META-INF/cuba-persistence.xml index 400a2da961..72ac0ed515 100644 --- a/modules/core/src/META-INF/cuba-persistence.xml +++ b/modules/core/src/META-INF/cuba-persistence.xml @@ -17,7 +17,7 @@ - + diff --git a/modules/core/src/com/haulmont/cuba/core/EntityManager.java b/modules/core/src/com/haulmont/cuba/core/EntityManager.java index 7188436ee6..956389ce13 100644 --- a/modules/core/src/com/haulmont/cuba/core/EntityManager.java +++ b/modules/core/src/com/haulmont/cuba/core/EntityManager.java @@ -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); } diff --git a/modules/core/src/com/haulmont/cuba/core/SecurityProvider.java b/modules/core/src/com/haulmont/cuba/core/SecurityProvider.java index 9767f9c747..012880d8b2 100644 --- a/modules/core/src/com/haulmont/cuba/core/SecurityProvider.java +++ b/modules/core/src/com/haulmont/cuba/core/SecurityProvider.java @@ -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"; diff --git a/modules/core/src/com/haulmont/cuba/core/Utils.java b/modules/core/src/com/haulmont/cuba/core/Utils.java new file mode 100644 index 0000000000..10c857e65d --- /dev/null +++ b/modules/core/src/com/haulmont/cuba/core/Utils.java @@ -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")); + } +} diff --git a/modules/core/src/com/haulmont/cuba/core/global/MetadataProvider.java b/modules/core/src/com/haulmont/cuba/core/global/MetadataProvider.java index 3aed906ca8..8d2da92fa7 100644 --- a/modules/core/src/com/haulmont/cuba/core/global/MetadataProvider.java +++ b/modules/core/src/com/haulmont/cuba/core/global/MetadataProvider.java @@ -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"; diff --git a/modules/core/src/com/haulmont/cuba/core/global/TimeProvider.java b/modules/core/src/com/haulmont/cuba/core/global/TimeProvider.java index db8e8b2dda..43eba6cd1d 100644 --- a/modules/core/src/com/haulmont/cuba/core/global/TimeProvider.java +++ b/modules/core/src/com/haulmont/cuba/core/global/TimeProvider.java @@ -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"; diff --git a/modules/core/src/com/haulmont/cuba/core/global/UuidProvider.java b/modules/core/src/com/haulmont/cuba/core/global/UuidProvider.java index 1811e672c4..873b9b881f 100644 --- a/modules/core/src/com/haulmont/cuba/core/global/UuidProvider.java +++ b/modules/core/src/com/haulmont/cuba/core/global/UuidProvider.java @@ -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"; diff --git a/modules/core/src/com/haulmont/cuba/core/sys/EntityManagerImpl.java b/modules/core/src/com/haulmont/cuba/core/sys/EntityManagerImpl.java index a53cb9d426..9e2705a12a 100644 --- a/modules/core/src/com/haulmont/cuba/core/sys/EntityManagerImpl.java +++ b/modules/core/src/com/haulmont/cuba/core/sys/EntityManagerImpl.java @@ -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 closeListeners = new HashSet(); + + 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); } } diff --git a/modules/core/src/com/haulmont/cuba/core/sys/ManagedPersistenceProvider.java b/modules/core/src/com/haulmont/cuba/core/sys/ManagedPersistenceProvider.java index 2eee121994..3c0348d1ee 100644 --- a/modules/core/src/com/haulmont/cuba/core/sys/ManagedPersistenceProvider.java +++ b/modules/core/src/com/haulmont/cuba/core/sys/ManagedPersistenceProvider.java @@ -37,6 +37,8 @@ public class ManagedPersistenceProvider extends PersistenceProvider private Map emMap = new Hashtable(); + private ThreadLocal emThreadLocal = new ThreadLocal(); + 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) { diff --git a/modules/core/src/com/haulmont/cuba/core/sys/persistence/CubaHSQLDictionary.java b/modules/core/src/com/haulmont/cuba/core/sys/persistence/CubaHSQLDictionary.java new file mode 100644 index 0000000000..7934f3d959 --- /dev/null +++ b/modules/core/src/com/haulmont/cuba/core/sys/persistence/CubaHSQLDictionary.java @@ -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 tables = new HashMap(); + 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) 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; + } +} diff --git a/modules/core/src/com/haulmont/cuba/security/app/LoginWorkerBean.java b/modules/core/src/com/haulmont/cuba/security/app/LoginWorkerBean.java index c7773019b7..1e208dca45 100644 --- a/modules/core/src/com/haulmont/cuba/security/app/LoginWorkerBean.java +++ b/modules/core/src/com/haulmont/cuba/security/app/LoginWorkerBean.java @@ -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; } diff --git a/modules/core/src/com/haulmont/cuba/security/listener/GroupEntityListener.java b/modules/core/src/com/haulmont/cuba/security/listener/GroupEntityListener.java index caf26ceabf..75a82d1524 100644 --- a/modules/core/src/com/haulmont/cuba/security/listener/GroupEntityListener.java +++ b/modules/core/src/com/haulmont/cuba/security/listener/GroupEntityListener.java @@ -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, @@ -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); diff --git a/modules/core/test/com/haulmont/cuba/core/CubaTestCase.java b/modules/core/test/com/haulmont/cuba/core/CubaTestCase.java index ce01410a66..6ead2ab708 100644 --- a/modules/core/test/com/haulmont/cuba/core/CubaTestCase.java +++ b/modules/core/test/com/haulmont/cuba/core/CubaTestCase.java @@ -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()); diff --git a/modules/core/test/com/haulmont/cuba/core/DeletedCollectionItemTest.java b/modules/core/test/com/haulmont/cuba/core/DeletedCollectionItemTest.java new file mode 100644 index 0000000000..ac06f5fa74 --- /dev/null +++ b/modules/core/test/com/haulmont/cuba/core/DeletedCollectionItemTest.java @@ -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 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 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 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 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(); + } + } +}