Added 'version' column to ADM entities

- A patch will assign initial version values to the entities
 - Deprecated TransactionUtil in favour of the RetryingTransactionHelper
 - Renamed RetryingTransactionHelper.Callback to RetryingTransactionHelper.RetryingTransactionCallback
   The name Callback clashes with many other classes in the classpath
 - Moved loads of components to be included in the retry behaviour
Duplicate name checks
 - This is done using a query, but the entity update is not written to the database early
 - Concurrent adds of the same-named child node will only fail at the end of the transaction
 - TODO: Detect the duplicate violation during transaction retrying
Workaround for ADMLuceneTest
 - Disable session size resource management during tests


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5823 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2007-06-01 12:40:17 +00:00
parent bbbd18923f
commit 819c7084a2
45 changed files with 818 additions and 230 deletions

View File

@@ -5247,7 +5247,7 @@ public class AVMServiceTest extends AVMServiceTestBase
try
{
setupBasicTree();
class TxnCallback implements RetryingTransactionHelper.Callback
class TxnCallback implements RetryingTransactionHelper.RetryingTransactionCallback
{
public Object execute()
{

View File

@@ -34,7 +34,7 @@ import org.alfresco.repo.attributes.ListAttributeValue;
import org.alfresco.repo.attributes.MapAttributeValue;
import org.alfresco.repo.attributes.StringAttributeValue;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.Callback;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.attributes.AttrQueryEquals;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.avm.AVMExistsException;
@@ -88,7 +88,7 @@ public class AVMLockingServiceImpl implements AVMLockingService
public void init()
{
Callback callback = new Callback()
RetryingTransactionCallback callback = new RetryingTransactionCallback()
{
public Object execute()
{

View File

@@ -38,6 +38,7 @@ import java.util.Locale;
import org.alfresco.error.StackTraceUtil;
import org.alfresco.i18n.I18NUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ContentAccessor;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
@@ -251,8 +252,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor
// nothing to do
return;
}
RetryingTransactionHelper.Callback cb =
new RetryingTransactionHelper.Callback()
RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>()
{
public Object execute()
{
@@ -274,7 +274,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor
{
cb.execute();
}
catch (Exception e)
catch (Throwable e)
{
throw new ContentIOException("Failed to executed channel close callbacks", e);
}
@@ -347,8 +347,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor
}
// We're now doing this in a retrying transaction, which means
// that the body of execute() must be idempotent.
RetryingTransactionHelper.Callback cb =
new RetryingTransactionHelper.Callback()
RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>()
{
public Object execute()
{
@@ -372,7 +371,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor
{
cb.execute();
}
catch (Exception e)
catch (Throwable e)
{
throw new ContentIOException("Failed to executed channel close callbacks", e);
}

View File

@@ -59,6 +59,11 @@ public interface ChildAssoc extends Comparable<ChildAssoc>
public Long getId();
/**
* @return Return the current version number
*/
public Long getVersion();
public Node getParent();
public Node getChild();

View File

@@ -37,7 +37,12 @@ public interface DbAccessControlEntry
/**
* @return Returns the identifier for this object
*/
public long getId();
public Long getId();
/**
* @return Returns the version number for optimistic locking
*/
public Long getVersion();
/**
* @return Returns the containing access control list

View File

@@ -36,8 +36,13 @@ import org.alfresco.repo.domain.hibernate.DbAccessControlEntryImpl;
*/
public interface DbAccessControlList
{
public long getId();
public Long getId();
/**
* @return Returns the version number for optimistic locking
*/
public Long getVersion();
/**
*
* @return Returns the access control entries for this access control list

View File

@@ -33,6 +33,11 @@ import java.util.Set;
*/
public interface DbAuthority extends Serializable
{
/**
* @return Returns the version number for optimistic locking
*/
public Long getVersion();
/**
* @return Returns the recipient
*/

View File

@@ -38,7 +38,12 @@ public interface DbPermission extends Serializable
/**
* @return Returns the automatically assigned ID
*/
public long getId();
public Long getId();
/**
* @return Returns the version number for optimistic locking
*/
public Long getVersion();
/**
* @return Returns the qualified name of this permission

View File

@@ -52,6 +52,11 @@ public interface Node
*/
public Long getId();
/**
* @return Returns the current version number
*/
public Long getVersion();
public Store getStore();
public void setStore(Store store);

View File

@@ -35,8 +35,6 @@ import org.alfresco.service.namespace.QName;
*/
public interface NodeAssoc
{
public long getId();
/**
* Wires up the necessary bits on the source and target nodes so that the association
* is immediately bidirectional.
@@ -52,6 +50,13 @@ public interface NodeAssoc
public AssociationRef getNodeAssocRef();
public Long getId();
/**
* @return Returns the current version number
*/
public Long getVersion();
public Node getSource();
public Node getTarget();

View File

@@ -45,6 +45,11 @@ public interface NodeStatus
*/
public void setKey(NodeKey key);
/**
* @return Returns the current version number
*/
public Long getVersion();
public Node getNode();
public void setNode(Node node);

View File

@@ -35,6 +35,8 @@ public interface Server
{
public Long getId();
public Long getVersion();
public String getIpAddress();
public void setIpAddress(String ipAddress);

View File

@@ -38,6 +38,11 @@ public interface Store
* @return Returns the key for the class
*/
public StoreKey getKey();
/**
* @return Returns the current version number used for optimistic locking
*/
public Long getVersion();
/**
* @param key the key uniquely identifying this store

View File

@@ -33,6 +33,8 @@ public interface Transaction
{
public Long getId();
public Long getVersion();
public String getChangeTxnId();
public void setChangeTxnId(String changeTxnId);

View File

@@ -33,6 +33,7 @@ import org.alfresco.repo.domain.ChildAssoc;
import org.alfresco.repo.domain.Node;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
/**
* @author Derek Hulley
@@ -42,6 +43,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
private static final long serialVersionUID = -8993272236626580410L;
private Long id;
private Long version;
private Node parent;
private Node child;
private QName typeQName;
@@ -127,6 +129,32 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
}
}
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
else if (obj == this)
{
return true;
}
else if (!(obj instanceof ChildAssoc))
{
return false;
}
ChildAssoc that = (ChildAssoc) obj;
return (EqualsHelper.nullSafeEquals(this.getTypeQName(), that.getTypeQName())
&& EqualsHelper.nullSafeEquals(this.getQname(), that.getQname())
&& EqualsHelper.nullSafeEquals(this.getChild(), that.getChild())
&& EqualsHelper.nullSafeEquals(this.getParent(), that.getParent()));
}
public int hashCode()
{
return (child == null ? 0 : child.hashCode());
}
public String toString()
{
StringBuffer sb = new StringBuffer(32);
@@ -192,6 +220,20 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Node getParent()
{
return parent;

View File

@@ -42,18 +42,14 @@ public class DbAccessControlEntryImpl extends LifecycleAdapter
{
private static final long serialVersionUID = -418837862334064582L;
/** The object id */
private long id;
private Long id;
private Long version;
/** The container of these entries */
private DbAccessControlList accessControlList;
/** The permission to which this applies (non null - all is a special string) */
private DbPermission permission;
/** The recipient to which this applies (non null - all is a special string) */
private DbAuthority authority;
/** Is this permission allowed? */
private boolean allowed;
@@ -113,7 +109,7 @@ public class DbAccessControlEntryImpl extends LifecycleAdapter
return hashCode;
}
public long getId()
public Long getId()
{
return id;
}
@@ -121,11 +117,26 @@ public class DbAccessControlEntryImpl extends LifecycleAdapter
/**
* For Hibernate use
*/
/* package */ void setId(long id)
@SuppressWarnings("unused")
private void setId(Long id)
{
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public DbAccessControlList getAccessControlList()
{
return accessControlList;

View File

@@ -51,7 +51,8 @@ public class DbAccessControlListImpl extends LifecycleAdapter
private static Log logger = LogFactory.getLog(DbAccessControlListImpl.class);
private long id;
private Long id;
private Long version;
private Set<DbAccessControlEntry> entries;
private boolean inherits;
@@ -94,7 +95,7 @@ public class DbAccessControlListImpl extends LifecycleAdapter
return (inherits == false ? 0 : 17);
}
public long getId()
public Long getId()
{
return id;
}
@@ -103,11 +104,25 @@ public class DbAccessControlListImpl extends LifecycleAdapter
* Hibernate use
*/
@SuppressWarnings("unused")
private void setId(long id)
private void setId(Long id)
{
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Set<DbAccessControlEntry> getEntries()
{
return entries;

View File

@@ -47,6 +47,7 @@ public class DbAuthorityImpl extends LifecycleAdapter
private static Log logger = LogFactory.getLog(DbAuthorityImpl.class);
private Long version;
private String recipient;
private Set<String> externalKeys;
@@ -105,6 +106,20 @@ public class DbAuthorityImpl extends LifecycleAdapter
return super.onDelete(session);
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public String getRecipient()
{
return recipient;

View File

@@ -48,7 +48,8 @@ public class DbPermissionImpl extends LifecycleAdapter
private static Log logger = LogFactory.getLog(DbPermissionImpl.class);
private long id;
private Long id;
private Long version;
private QName typeQname;
private String name;
@@ -120,7 +121,7 @@ public class DbPermissionImpl extends LifecycleAdapter
return super.onDelete(session);
}
public long getId()
public Long getId()
{
return id;
}
@@ -129,11 +130,25 @@ public class DbPermissionImpl extends LifecycleAdapter
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setId(long id)
private void setId(Long id)
{
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public QName getTypeQname()
{
return typeQname;

View File

@@ -49,6 +49,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseSpringTest;
@@ -178,6 +179,8 @@ public class HibernateNodeTest extends BaseSpringTest
// Sybase
// expected
}
// Just clear out any pending changes
getSession().clear();
}
/**
@@ -584,4 +587,58 @@ public class HibernateNodeTest extends BaseSpringTest
getSession().flush();
getSession().clear();
}
public void testDeletesAndFlush() throws Exception
{
// Create parent node
Node parentNode = new NodeImpl();
parentNode.setStore(store);
parentNode.setUuid(GUID.generate());
parentNode.setTypeQName(ContentModel.TYPE_CONTAINER);
Long nodeIdOne = (Long) getSession().save(parentNode);
// Create child node
Node childNode = new NodeImpl();
childNode.setStore(store);
childNode.setUuid(GUID.generate());
childNode.setTypeQName(ContentModel.TYPE_CONTENT);
Long nodeIdTwo = (Long) getSession().save(childNode);
// Get them into the database
getSession().flush();
// Now create a loads of associations
int assocCount = 1000;
List<Long> assocIds = new ArrayList<Long>(assocCount);
for (int i = 0; i < assocCount; i++)
{
ChildAssoc assoc = new ChildAssocImpl();
assoc.buildAssociation(parentNode, childNode);
assoc.setIsPrimary(false);
assoc.setTypeQName(QName.createQName(null, "TYPE"));
assoc.setQname(QName.createQName(null, "" + System.nanoTime()));
assoc.setChildNodeName(GUID.generate()); // It must be unique
assoc.setChildNodeNameCrc(-1L);
Long assocId = (Long) getSession().save(assoc);
assocIds.add(assocId);
}
// Flush and clear the lot
getSession().flush();
getSession().clear();
// Now we delete the entities, flushing and clearing every 100 deletes
int count = 0;
for (Long assocId : assocIds)
{
// Load the entity
ChildAssoc assoc = (ChildAssoc) getSession().get(ChildAssocImpl.class, assocId);
assertNotNull("Entity should exist", assoc);
getSession().delete(assoc);
// Do we flush and clear
if (count % 100 == 0)
{
getSession().flush();
getSession().clear();
}
count++;
}
}
}

View File

@@ -38,6 +38,9 @@
<!-- the store-unique identifier -->
<property name="uuid" column="uuid" type="string" length="36" />
</natural-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<property name="typeQName" column="type_qname" type="QName" length="255" not-null="true" />
<!-- forward assoc to access control list (optional) -->
<many-to-one
@@ -115,6 +118,8 @@
<key-property name="identifier" length="100" />
<key-property name="guid" length="36" />
</composite-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to transaction -->
<many-to-one
name="transaction"
@@ -148,13 +153,15 @@
<id name="id" column="id" type="long" >
<generator class="native" />
</id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to parent node -->
<many-to-one
name="parent"
class="org.alfresco.repo.domain.hibernate.NodeImpl"
lazy="proxy"
fetch="select"
optimistic-lock="true"
optimistic-lock="false"
not-null="true"
unique-key="UIDX_CHILD_NAME" >
<column name="parent_node_id" not-null="true" />
@@ -165,7 +172,7 @@
lazy="proxy"
fetch="select"
class="org.alfresco.repo.domain.hibernate.NodeImpl"
optimistic-lock="true"
optimistic-lock="false"
not-null="true" >
<column name="child_node_id" not-null="true"/>
</many-to-one>
@@ -190,6 +197,7 @@
<many-to-one
name="source"
class="org.alfresco.repo.domain.hibernate.NodeImpl"
optimistic-lock="false"
lazy="false"
fetch="join"
not-null="true" >
@@ -199,6 +207,7 @@
<many-to-one
name="target"
class="org.alfresco.repo.domain.hibernate.NodeImpl"
optimistic-lock="false"
lazy="false"
fetch="join"
not-null="true" >
@@ -206,6 +215,8 @@
</many-to-one>
<property name="typeQName" column="type_qname" type="QName" length="255" not-null="true" />
</natural-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
</class>
<query name="store.GetAllStores">
@@ -272,6 +283,17 @@
assoc.id
</query>
<query name="node.GetChildAssocByShortName">
select
assoc
from
org.alfresco.repo.domain.hibernate.ChildAssocImpl as assoc
where
assoc.parent.id = :parentId and
assoc.childNodeName = :childNodeName and
assoc.childNodeNameCrc = :childNodeNameCrc
</query>
<query name="node.GetChildAssocByTypeAndName">
select
assoc

View File

@@ -44,7 +44,8 @@ public class NodeAssocImpl implements NodeAssoc, Serializable
{
private static final long serialVersionUID = 864534636913524867L;
private long id;
private Long id;
private Long version;
private Node source;
private Node target;
private QName typeQName;
@@ -149,7 +150,7 @@ public class NodeAssocImpl implements NodeAssoc, Serializable
return (typeQName == null ? 0 : typeQName.hashCode());
}
public long getId()
public Long getId()
{
return id;
}
@@ -163,6 +164,20 @@ public class NodeAssocImpl implements NodeAssoc, Serializable
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Node getSource()
{
return source;

View File

@@ -56,6 +56,7 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable
private static final long serialVersionUID = -2101330674810283053L;
private Long id;
private Long version;
private Store store;
private String uuid;
private QName typeQName;
@@ -179,6 +180,20 @@ public class NodeImpl extends LifecycleAdapter implements Node, Serializable
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Store getStore()
{
return store;

View File

@@ -42,6 +42,7 @@ public class NodeStatusImpl implements NodeStatus, Serializable
private static final long serialVersionUID = -802747893314715639L;
private NodeKey key;
private Long version;
private Node node;
private Transaction transaction;
@@ -85,6 +86,20 @@ public class NodeStatusImpl implements NodeStatus, Serializable
this.key = key;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Node getNode()
{
return node;

View File

@@ -19,6 +19,8 @@
<generator class="native" />
</id>
<version column="version" name="version" type="long" />
<set name="entries"
inverse="true"
lazy="false"
@@ -74,6 +76,8 @@
not-null="true" />
</natural-id>
<version column="version" name="version" type="long" />
<property name="allowed" column="allowed" type="boolean" not-null="true" />
</class>
@@ -97,6 +101,8 @@
<property name="name" type="string" length="100" column="name" />
</natural-id>
<version column="version" name="version" type="long" />
</class>
<class
@@ -111,6 +117,8 @@
<id name="recipient" column="recipient" type="string" length="100" />
<version column="version" name="version" type="long" />
<set
name="externalKeys"
table="alf_auth_ext_keys"

View File

@@ -41,6 +41,7 @@ public class ServerImpl extends LifecycleAdapter implements Server, Serializable
private static final long serialVersionUID = 8063452519040344479L;
private Long id;
private Long version;
private String ipAddress;
public ServerImpl()
@@ -77,6 +78,20 @@ public class ServerImpl extends LifecycleAdapter implements Server, Serializable
return ipAddress;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public void setIpAddress(String ipAddress)
{
this.ipAddress = ipAddress;

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.domain.hibernate;
import java.lang.reflect.Method;
import java.util.Map;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.util.resource.MethodResourceManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -50,11 +51,50 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
*/
public class SessionSizeResourceManager extends HibernateDaoSupport implements MethodResourceManager
{
/** key to store the local flag to disable resource control during the current transaction */
private static final String KEY_DISABLE_IN_TRANSACTION = "SessionSizeResourceManager.DisableInTransaction";
private static Log logger = LogFactory.getLog(SessionSizeResourceManager.class);
/** Default 1000 */
private int threshold = 1000;
private int threshold;
/**
* Disable resource management for the duration of the current transaction. This is temporary
* and relies on an active transaction.
*/
public static void setDisableInTransaction()
{
AlfrescoTransactionSupport.bindResource(KEY_DISABLE_IN_TRANSACTION, Boolean.TRUE);
}
/**
* @return Returns true if the resource management must be ignored in the current transaction.
* If <code>false</code>, the global setting will take effect.
*
* @see #setDisableInTransaction()
*/
public static boolean isDisableInTransaction()
{
Boolean disableInTransaction = (Boolean) AlfrescoTransactionSupport.getResource(KEY_DISABLE_IN_TRANSACTION);
if (disableInTransaction == null || disableInTransaction == Boolean.FALSE)
{
return false;
}
else
{
return true;
}
}
/**
* Default public constructor required for bean instantiation.
*/
public SessionSizeResourceManager()
{
this.threshold = 1000;
}
/**
* Set the {@link Session#clear()} threshold. If the number of entities and collections in the
* current session exceeds this number, then the session will be cleared. Have you read the
@@ -74,6 +114,12 @@ public class SessionSizeResourceManager extends HibernateDaoSupport implements M
long transactionElapsedTimeNs,
Method currentMethod)
{
if (isDisableInTransaction())
{
// Don't do anything
return;
}
// We are go for interfering
Session session = getSession(false);
SessionStatistics stats = session.getStatistics();
int entityCount = stats.getEntityCount();

View File

@@ -19,6 +19,8 @@
<key-property name="protocol" length="50" />
<key-property name="identifier" length="100" />
</composite-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to root node -->
<many-to-one
name="rootNode"

View File

@@ -44,6 +44,7 @@ public class StoreImpl implements Store, Serializable
private static final long serialVersionUID = -6135740209100885890L;
private StoreKey key;
private Long version;
private Node rootNode;
private transient ReadLock refReadLock;
@@ -148,6 +149,20 @@ public class StoreImpl implements Store, Serializable
}
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Node getRootNode()
{
return rootNode;

View File

@@ -19,6 +19,8 @@
<id name="id" column="id" type="long" >
<generator class="native" />
</id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to server IP -->
<many-to-one
name="server"
@@ -48,6 +50,8 @@
<natural-id>
<property name="ipAddress" column="ip_address" type="string" length="15" not-null="true" />
</natural-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
</class>
<query name="server.getServerByIpAddress">

View File

@@ -42,6 +42,7 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se
private static final long serialVersionUID = -8264339795578077552L;
private Long id;
private Long version;
private String changeTxnId;
private Server server;
@@ -74,6 +75,20 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se
this.id = id;
}
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public String getChangeTxnId()
{
return changeTxnId;

View File

@@ -35,8 +35,8 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.transform.AbstractContentTransformerTest;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.TransactionUtil;
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
@@ -47,7 +47,6 @@ import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
@@ -71,7 +70,7 @@ public class FileFolderPerformanceTester extends TestCase
private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
private TransactionService transactionService;
private RetryingTransactionHelper retryingTransactionHelper;
private AuthenticationComponent authenticationComponent;
private NodeService nodeService;
private FileFolderService fileFolderService;
@@ -82,7 +81,7 @@ public class FileFolderPerformanceTester extends TestCase
public void setUp() throws Exception
{
ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
transactionService = serviceRegistry.getTransactionService();
retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper");
authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
nodeService = serviceRegistry.getNodeService();
fileFolderService = serviceRegistry.getFileFolderService();
@@ -138,9 +137,9 @@ public class FileFolderPerformanceTester extends TestCase
final int fileCount,
final double[] dumpPoints)
{
TransactionWork<NodeRef[]> createFoldersWork = new TransactionWork<NodeRef[]>()
RetryingTransactionCallback<NodeRef[]> createFoldersCallback = new RetryingTransactionCallback<NodeRef[]>()
{
public NodeRef[] doWork() throws Exception
public NodeRef[] execute() throws Exception
{
NodeRef[] folders = new NodeRef[folderCount];
for (int i = 0; i < folderCount; i++)
@@ -155,9 +154,7 @@ public class FileFolderPerformanceTester extends TestCase
return folders;
}
};
final NodeRef[] folders = TransactionUtil.executeInUserTransaction(
transactionService,
createFoldersWork);
final NodeRef[] folders = retryingTransactionHelper.doInTransaction(createFoldersCallback);
// the worker that will load the files into the folders
Runnable runnable = new Runnable()
{
@@ -192,9 +189,9 @@ public class FileFolderPerformanceTester extends TestCase
for (int j = 0; j < folders.length; j++)
{
final NodeRef folderRef = folders[j];
TransactionWork<FileInfo> createFileWork = new TransactionWork<FileInfo>()
RetryingTransactionCallback<FileInfo> createFileCallback = new RetryingTransactionCallback<FileInfo>()
{
public FileInfo doWork() throws Exception
public FileInfo execute() throws Exception
{
FileInfo fileInfo = fileFolderService.create(
folderRef,
@@ -208,7 +205,7 @@ public class FileFolderPerformanceTester extends TestCase
return fileInfo;
}
};
TransactionUtil.executeInUserTransaction(transactionService, createFileWork);
retryingTransactionHelper.doInTransaction(createFileCallback);
}
}
dumpResults(fileCount);
@@ -257,6 +254,7 @@ public class FileFolderPerformanceTester extends TestCase
}
}
@SuppressWarnings("unused")
private void readStructure(
final NodeRef parentNodeRef,
final int threadCount,
@@ -277,9 +275,9 @@ public class FileFolderPerformanceTester extends TestCase
for (ChildAssociationRef childAssociationRef : children)
{
final NodeRef folderRef = childAssociationRef.getChildRef();
TransactionWork<Object> readWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> readCallback = new RetryingTransactionCallback<Object>()
{
public Object doWork() throws Exception
public Object execute() throws Exception
{
// read the child associations of the folder
nodeService.getChildAssocs(folderRef);
@@ -289,7 +287,7 @@ public class FileFolderPerformanceTester extends TestCase
return null;
};
};
TransactionUtil.executeInUserTransaction(transactionService, readWork, true);
retryingTransactionHelper.doInTransaction(readCallback, true);
}
}
}
@@ -337,16 +335,16 @@ public class FileFolderPerformanceTester extends TestCase
// {
// buildStructure(rootFolderRef, 4, true, 10, 100, new double[] {0.25, 0.50, 0.75});
// }
public void test_1_ordered_100_100() throws Exception
{
buildStructure(
rootFolderRef,
1,
false,
100,
100,
new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90});
}
// public void test_1_ordered_100_100() throws Exception
// {
// buildStructure(
// rootFolderRef,
// 1,
// false,
// 100,
// 100,
// new double[] {0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90});
// }
// public void test_1_shuffled_10_400() throws Exception
// {
// buildStructure(
@@ -357,16 +355,16 @@ public class FileFolderPerformanceTester extends TestCase
// 400,
// new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90});
// }
// public void test_4_shuffled_10_100() throws Exception
// {
// buildStructure(
// rootFolderRef,
// 4,
// true,
// 10,
// 100,
// new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90});
// }
public void test_4_shuffled_10_100() throws Exception
{
buildStructure(
rootFolderRef,
4,
true,
10,
100,
new double[] {0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90});
}
// public void test_1_ordered_1_50000() throws Exception
// {
// buildStructure(

View File

@@ -51,6 +51,7 @@ import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.dictionary.ClassDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryException;
import org.alfresco.service.cmr.dictionary.DictionaryService;
@@ -140,6 +141,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
protected PolicyComponent policyComponent;
protected DictionaryService dictionaryService;
protected TransactionService transactionService;
protected RetryingTransactionHelper retryingTransactionHelper;
protected AuthenticationComponent authenticationComponent;
protected NodeDaoService nodeDaoService;
protected NodeService nodeService;
@@ -151,6 +153,7 @@ public abstract class BaseNodeServiceTest extends BaseSpringTest
{
super.onSetUpInTransaction();
transactionService = (TransactionService) applicationContext.getBean("transactionComponent");
retryingTransactionHelper = (RetryingTransactionHelper) applicationContext.getBean("retryingTransactionHelper");
policyComponent = (PolicyComponent) applicationContext.getBean("policyComponent");
authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent");

View File

@@ -27,15 +27,14 @@ package org.alfresco.repo.node;
import java.io.InputStream;
import java.util.Map;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.transaction.TransactionUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -78,6 +77,7 @@ public class ConcurrentNodeServiceTest extends TestCase
private NodeService nodeService;
private TransactionService transactionService;
private RetryingTransactionHelper retryingTransactionHelper;
private NodeRef rootNodeRef;
@@ -107,23 +107,24 @@ public class ConcurrentNodeServiceTest extends TestCase
nodeService = (NodeService) ctx.getBean("dbNodeService");
transactionService = (TransactionService) ctx.getBean("transactionComponent");
retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper");
luceneFTS = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer");
this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
this.authenticationComponent.setSystemUserAsCurrentUser();
// create a first store directly
TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork<Object>()
RetryingTransactionCallback<Object> createRootNodeCallback = new RetryingTransactionCallback<Object>()
{
public Object doWork() throws Exception
public Object execute() throws Exception
{
StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_"
+ System.currentTimeMillis());
rootNodeRef = nodeService.getRootNode(storeRef);
return null;
}
});
};
retryingTransactionHelper.doInTransaction(createRootNodeCallback);
}
@Override
@@ -140,17 +141,17 @@ public class ConcurrentNodeServiceTest extends TestCase
protected Map<QName, ChildAssociationRef> commitNodeGraph() throws Exception
{
return TransactionUtil.executeInUserTransaction(transactionService,
new TransactionUtil.TransactionWork<Map<QName, ChildAssociationRef>>()
{
RetryingTransactionCallback<Map<QName, ChildAssociationRef>> buildGraphCallback =
new RetryingTransactionCallback<Map<QName, ChildAssociationRef>>()
{
public Map<QName, ChildAssociationRef> execute() throws Exception
{
public Map<QName, ChildAssociationRef> doWork() throws Exception
{
Map<QName, ChildAssociationRef> answer = buildNodeGraph();
return answer;
}
});
Map<QName, ChildAssociationRef> answer = buildNodeGraph();
return answer;
}
};
return retryingTransactionHelper.doInTransaction(buildGraphCallback);
}
public void xtest1() throws Exception
@@ -231,10 +232,10 @@ public class ConcurrentNodeServiceTest extends TestCase
}
}
TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork<Object>()
// Test it
RetryingTransactionCallback<Object> testCallback = new RetryingTransactionCallback<Object>()
{
public Object doWork() throws Exception
public Object execute() throws Exception
{
// There are two nodes at the base level in each test
assertEquals(2 * ((COUNT * REPEATS) + 1), nodeService.getChildAssocs(rootNodeRef).size());
@@ -276,9 +277,8 @@ public class ConcurrentNodeServiceTest extends TestCase
return null;
}
});
};
retryingTransactionHelper.doInTransaction(testCallback);
}
/**

View File

@@ -40,8 +40,7 @@ import org.alfresco.repo.domain.Node;
import org.alfresco.repo.domain.NodeStatus;
import org.alfresco.repo.node.BaseNodeServiceTest;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionUtil;
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -143,9 +142,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
endTransaction();
// change property - check status
TransactionWork<Object> changePropertiesWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> changePropertiesWork = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
nodeService.setProperty(n6Ref, ContentModel.PROP_CREATED, new Date());
return null;
@@ -154,9 +153,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, changePropertiesWork);
// add an aspect
TransactionWork<Object> addAspectWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> addAspectWork = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
nodeService.addAspect(n6Ref, ASPECT_QNAME_TEST_MARKER, null);
return null;
@@ -165,9 +164,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, addAspectWork);
// remove an aspect
TransactionWork<Object> removeAspectWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> removeAspectWork = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
nodeService.removeAspect(n6Ref, ASPECT_QNAME_TEST_MARKER);
return null;
@@ -176,9 +175,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, removeAspectWork);
// move the node
TransactionWork<Object> moveNodeWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> moveNodeWork = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
nodeService.moveNode(
n6Ref,
@@ -191,9 +190,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, moveNodeWork);
// delete the node
TransactionWork<Object> deleteNodeWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> deleteNodeWork = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
nodeService.deleteNode(n6Ref);
return null;
@@ -202,9 +201,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, deleteNodeWork);
// check cascade-deleted nodes
TransactionWork<Object> checkCascadeWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> checkCascadeCallback = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
// check n6
NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false);
@@ -221,12 +220,12 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
return null;
}
};
TransactionUtil.executeInUserTransaction(txnService, checkCascadeWork);
retryingTransactionHelper.doInTransaction(checkCascadeCallback);
// check node recreation
TransactionWork<Object> checkRecreateWork = new TransactionWork<Object>()
RetryingTransactionCallback<Object> checkRecreateCallback = new RetryingTransactionCallback<Object>()
{
public Object doWork()
public Object execute()
{
properties.put(ContentModel.PROP_STORE_PROTOCOL, n6Ref.getStoreRef().getProtocol());
properties.put(ContentModel.PROP_STORE_IDENTIFIER, n6Ref.getStoreRef().getIdentifier());
@@ -242,10 +241,10 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
return null;
}
};
TransactionUtil.executeInUserTransaction(txnService, checkRecreateWork);
retryingTransactionHelper.doInTransaction(checkRecreateCallback);
}
private void executeAndCheck(NodeRef nodeRef, TransactionWork<Object> work) throws Throwable
private void executeAndCheck(NodeRef nodeRef, RetryingTransactionCallback<Object> callback) throws Throwable
{
UserTransaction txn = txnService.getUserTransaction();
txn.begin();
@@ -257,7 +256,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
assertNotSame(currentTxnId, currentStatus.getChangeTxnId());
try
{
work.doWork();
callback.execute();
// get the status
NodeRef.Status newStatus = nodeService.getNodeStatus(nodeRef);
assertNotNull(newStatus);
@@ -362,4 +361,20 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
// Get it again
nodeService.getPrimaryParent(n8Ref);
}
/**
* It would appear that an issue has arisen with creating and deleting nodes
* in the same transaction.
*/
public void testInTransactionCreateAndDelete() throws Exception
{
// Create a node
NodeRef nodeRef = nodeService.createNode(
rootNodeRef,
ASSOC_TYPE_QNAME_TEST_CHILDREN,
QName.createQName(NAMESPACE, this.getName()),
TYPE_QNAME_TEST_CONTENT).getChildRef();
// Delete the node
nodeService.deleteNode(nodeRef);
}
}

View File

@@ -32,6 +32,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.zip.CRC32;
@@ -72,13 +73,17 @@ import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.LockMode;
import org.hibernate.ObjectDeletedException;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.exception.LockAcquisitionException;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
@@ -94,6 +99,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
private static final String QUERY_GET_PRIMARY_CHILD_NODE_STATUSES = "node.GetPrimaryChildNodeStatuses";
private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs";
private static final String QUERY_GET_CHILD_ASSOCS_BY_ALL = "node.GetChildAssocsByAll";
private static final String QUERY_GET_CHILD_ASSOC_BY_NAME = "node.GetChildAssocByShortName";
private static final String QUERY_GET_CHILD_ASSOC_BY_TYPE_AND_NAME = "node.GetChildAssocByTypeAndName";
private static final String QUERY_GET_CHILD_ASSOC_REFS = "node.GetChildAssocRefs";
private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME = "node.GetChildAssocRefsByQName";
@@ -105,12 +111,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
private static final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress";
private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class);
private static Log loggerChildAssoc = LogFactory.getLog(HibernateNodeDaoServiceImpl.class.getName() + ".ChildAssoc");
/** a uuid identifying this unique instance */
private final String uuid;
/** the number of lock retries against the parent node to ensure child uniqueness */
private int maxLockRetries;
private static TransactionAwareSingleton<Long> serverIdSingleton = new TransactionAwareSingleton<Long>();
private final String ipAddress;
private Random randomWaitTime;
/** used for debugging */
private Set<String> changeTxnIdSet;
@@ -121,6 +131,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
public HibernateNodeDaoServiceImpl()
{
this.uuid = GUID.generate();
this.maxLockRetries = 20;
try
{
ipAddress = InetAddress.getLocalHost().getHostAddress();
@@ -129,6 +140,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{
throw new AlfrescoRuntimeException("Failed to get server IP address", e);
}
randomWaitTime = new Random(System.currentTimeMillis());
changeTxnIdSet = new HashSet<String>(0);
}
@@ -158,6 +170,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
return uuid.hashCode();
}
/**
* Set the maximum number of retries when attempting to get a lock on a parent node
*
* @param maxLockRetries the retry count
*/
public void setMaxLockRetries(int maxLockRetries)
{
this.maxLockRetries = maxLockRetries;
}
/**
* Gets/creates the <b>server</b> instance to use for the life of this instance
*/
@@ -515,8 +537,8 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
nodeStatus.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
// finally delete the node
getHibernateTemplate().delete(node);
// flush to ensure constraints can't be violated
getSession().flush();
// // flush to ensure constraints can't be violated
// getSession().flush();
// done
}
@@ -584,13 +606,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
public void setChildNameUnique(final ChildAssoc childAssoc, String childName)
{
/*
* As the Hibernate session is rendered useless when an exception is
* bubbled up, we go direct to the database to update the child association.
* This preserves the session and client code can catch the resulting
* exception and react to it whilst in the same transaction.
*
* We ensure that case-insensitivity is maintained by persisting
* the lowercase version of the child node name.
* Work out if there has been any change in the name
*/
String childNameNew = null;
@@ -621,40 +637,75 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
}
}
/*
* The parent node is explicitly locked. A query is then issued to ensure that there
* are no duplicates in the index on the child assoc table. The child association is
* then modified, although not directly in the database. The lock guards against other
* transactions modifying the unique index without this transaction's knowledge.
*/
final Node parentNode = childAssoc.getParent();
// if (loggerChildAssoc.isDebugEnabled())
// {
// loggerChildAssoc.debug(
// "Locking parent node for modifying child assoc: \n" +
// " Parent: " + parentNode + "\n" +
// " Child Assoc: " + childAssoc + "\n" +
// " New Name: " + childNameNew);
// }
// for (int i = 0; i < maxLockRetries; i++)
// {
// try
// {
// getSession().lock(parentNode, LockMode.UPGRADE);
// // The lock was good, proceed
// break;
// }
// catch (LockAcquisitionException e) {}
// catch (ConcurrencyFailureException e) {}
// // We can retry after a short pause that gets potentially longer each time
// try { Thread.sleep(randomWaitTime.nextInt(500 * i + 500)); } catch (InterruptedException ee) {}
// }
// We have the lock, so issue the query to check
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
session.flush();
Query query = session
.getNamedQuery(HibernateNodeDaoServiceImpl.UPDATE_SET_CHILD_ASSOC_NAME)
.setString("newName", childNameNewShort)
.setLong("newNameCrc", childNameNewCrc)
.setLong("childAssocId", childAssoc.getId());
return (Integer) query.executeUpdate();
.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_NAME)
.setLong("parentId", parentNode.getId())
.setParameter("childNodeName", childNameNewShort)
.setLong("childNodeNameCrc", childNameNewCrc);
return query.uniqueResult();
}
};
try
ChildAssoc childAssocExisting = (ChildAssoc) getHibernateTemplate().execute(callback);
if (childAssocExisting != null)
{
Integer count = (Integer) getHibernateTemplate().execute(callback);
// refresh the entity directly
if (count.intValue() == 0)
// There is already an entity
if (loggerChildAssoc.isDebugEnabled())
{
if (logger.isDebugEnabled())
{
logger.debug("ChildAssoc not updated: " + childAssoc.getId());
}
}
else
{
getHibernateTemplate().refresh(childAssoc);
loggerChildAssoc.debug(
"Duplicate child association detected: \n" +
" Child Assoc: " + childAssoc + "\n" +
" Existing Child Assoc: " + childName);
}
throw new DuplicateChildNodeNameException(
parentNode.getNodeRef(),
childAssoc.getTypeQName(),
childName);
}
catch (DataIntegrityViolationException e)
// We got past that, so we can just update the entity and know that no other transaction
// can lock the parent.
childAssoc.setChildNodeName(childNameNewShort);
childAssoc.setChildNodeNameCrc(childNameNewCrc);
// Done
if (loggerChildAssoc.isDebugEnabled())
{
NodeRef parentNodeRef = childAssoc.getParent().getNodeRef();
QName assocTypeQName = childAssoc.getTypeQName();
throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName);
loggerChildAssoc.debug(
"Updated child association: \n" +
" Parent: " + parentNode + "\n" +
" Child Assoc: " + childAssoc);
}
}
@@ -868,9 +919,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
return;
}
if (logger.isDebugEnabled())
if (loggerChildAssoc.isDebugEnabled())
{
logger.debug(
loggerChildAssoc.debug(
"Deleting parent-child association " + assoc.getId() +
(cascade ? " with" : " without") + " cascade:" +
assoc.getParent().getId() + " -> " + assoc.getChild().getId());
@@ -894,10 +945,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
* duplicate call will be received to do this
*/
}
// To ensure the validity of the constraint enforcement by the database,
// we have to flush here
getSession().flush();
//
// // To ensure the validity of the constraint enforcement by the database,
// // we have to flush here. It is possible to delete and recreate the instance
// getSession().flush();
}
/**
@@ -1117,9 +1168,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{
// Remove instance
getHibernateTemplate().delete(assoc);
// Flush to ensure that the database constraints aren't violated if the assoc
// is recreated in the transaction
getSession().flush();
// // Flush to ensure that the database constraints aren't violated if the assoc
// // is recreated in the transaction
// getSession().flush();
}
public List<Serializable> getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition)

View File

@@ -136,9 +136,6 @@ public class SessionSizeManagementTest extends BaseNodeServiceTest
}
createNodes(nodeService, LOAD_COUNT, true);
// Check the session size
int entityCount = getSession().getStatistics().getEntityCount();
assertTrue("Manual flush: Entity count should be less than " + LOAD_COUNT, entityCount < LOAD_COUNT);
// Now flush integrity to be sure things are not broken
AlfrescoTransactionSupport.flush();

View File

@@ -50,6 +50,7 @@ import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.DictionaryNamespaceComponent;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.NamespaceDAOImpl;
import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager;
import org.alfresco.repo.node.BaseNodeServiceTest;
import org.alfresco.repo.search.MLAnalysisMode;
import org.alfresco.repo.search.QueryParameterDefImpl;
@@ -59,6 +60,8 @@ import org.alfresco.repo.search.results.ChildAssocRefResultSet;
import org.alfresco.repo.search.results.DetachedResultSet;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
@@ -67,7 +70,6 @@ import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MLText;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path;
@@ -120,6 +122,7 @@ public class ADMLuceneTest extends TestCase
QName aspectWithChildren = QName.createQName(TEST_NAMESPACE, "aspectWithChildren");
TransactionService transactionService;
RetryingTransactionHelper retryingTransactionHelper;
NodeService nodeService;
@@ -205,6 +208,8 @@ public class ADMLuceneTest extends TestCase
indexerAndSearcher = (LuceneIndexerAndSearcher) ctx.getBean("admLuceneIndexerAndSearcherFactory");
((AbstractLuceneIndexerAndSearcherFactory)indexerAndSearcher).setMaxAtomicTransformationTime(1000000);
transactionService = (TransactionService) ctx.getBean("transactionComponent");
retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper");
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
namespaceDao = (NamespaceDAOImpl) ctx.getBean("namespaceDAO");
@@ -1030,25 +1035,31 @@ public class ADMLuceneTest extends TestCase
assertEquals(1, results.length());
results.close();
UserTransaction tx1 = transactionService.getUserTransaction();
tx1.begin();
for (int i = 0; i < 100; i++)
RetryingTransactionCallback<Object> createAndDeleteCallback = new RetryingTransactionCallback<Object>()
{
HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>();
for (int j = 0; j < i; j++)
public Object execute() throws Throwable
{
ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName
.createQName("{namespace}test"), testSuperType);
refs.add(test);
}
// Disable resource management
SessionSizeResourceManager.setDisableInTransaction();
for (int i = 0; i < 100; i++)
{
HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>();
for (int j = 0; j < i; j++)
{
ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, QName
.createQName("{namespace}test"), testSuperType);
refs.add(test);
}
for (ChildAssociationRef car : refs)
{
nodeService.deleteNode(car.getChildRef());
for (ChildAssociationRef car : refs)
{
nodeService.deleteNode(car.getChildRef());
}
}
return null;
}
}
tx1.commit();
};
retryingTransactionHelper.doInTransaction(createAndDeleteCallback);
UserTransaction tx3 = transactionService.getUserTransaction();
tx3.begin();
@@ -1134,29 +1145,34 @@ public class ADMLuceneTest extends TestCase
try
{
System.out.println("Start " + this.getName());
UserTransaction tx1 = transactionService.getUserTransaction();
tx1.begin();
for (int i = 0; i < 20; i++)
RetryingTransactionCallback<Object> createAndDeleteCallback = new RetryingTransactionCallback<Object>()
{
HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>();
for (int j = 0; j < i; j++)
public Object execute() throws Throwable
{
ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN,
QName.createQName("{namespace}test_" + getName() + "_" + i + "_" +j), testSuperType);
refs.add(test);
}
for (int i = 0; i < 20; i++)
{
HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>();
for (int j = 0; j < i; j++)
{
ChildAssociationRef test = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN,
QName.createQName("{namespace}test_" + getName() + "_" + i + "_" +j), testSuperType);
refs.add(test);
}
for (ChildAssociationRef car : refs)
{
nodeService.deleteNode(car.getChildRef());
for (ChildAssociationRef car : refs)
{
nodeService.deleteNode(car.getChildRef());
}
}
return null;
}
}
tx1.commit();
};
retryingTransactionHelper.doInTransaction(createAndDeleteCallback);
System.out.println("End " + this.getName());
}
catch (Exception e)
{
System.out.println("End " + this.getName() + " with error " + e.getMessage());
e.printStackTrace();
}
finally

View File

@@ -1,5 +1,26 @@
/**
*
/*
* Copyright (C) 2005-2007 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.repo.transaction;
@@ -10,6 +31,7 @@ import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.service.transaction.TransactionService;
import org.apache.log4j.Logger;
import org.hibernate.StaleObjectStateException;
@@ -20,13 +42,28 @@ import org.springframework.dao.DeadlockLoserDataAccessException;
/**
* A helper that runs a unit of work inside a UserTransaction,
* transparently retrying the unit of work if the cause of
* failure is an optimistic locking or deadlock condition.
* failure is an optimistic locking or deadlock condition.
*
* @author britt
*/
public class RetryingTransactionHelper
{
private static Logger fgLogger = Logger.getLogger(RetryingTransactionHelper.class);
/**
* Exceptions that trigger retries.
*/
private static final Class[] RETRY_EXCEPTIONS;
static
{
RETRY_EXCEPTIONS = new Class[] {
ConcurrencyFailureException.class,
DeadlockLoserDataAccessException.class,
StaleObjectStateException.class,
LockAcquisitionException.class
};
}
/**
* Reference to the TransactionService instance.
*/
@@ -46,9 +83,15 @@ public class RetryingTransactionHelper
* Callback interface
* @author britt
*/
public interface Callback
public interface RetryingTransactionCallback<Result>
{
public Object execute();
/**
* Perform a unit of transactional work.
*
* @return Return the result of the unit of work
* @throws Throwable This can be anything and will guarantee either a retry or a rollback
*/
public Result execute() throws Throwable;
};
/**
@@ -81,11 +124,53 @@ public class RetryingTransactionHelper
* because of an error not the result of an optimistic locking failure,
* or a deadlock loser failure, or until a maximum number of retries have
* been attempted.
* @param cb The callback containing the unit of work.
* @param readOnly Whether this is a read only transaction.
* @return The result of the unit of work.
* <p>
* If there is already an active transaction, then the callback is merely
* executed and any retry logic is left to the caller. The transaction
* will attempt to be read-write.
*
* @param cb The callback containing the unit of work.
* @return Returns the result of the unit of work.
*/
public Object doInTransaction(Callback cb, boolean readOnly)
public <R> R doInTransaction(RetryingTransactionCallback<R> cb)
{
return doInTransaction(cb, false, false);
}
/**
* Execute a callback in a transaction until it succeeds, fails
* because of an error not the result of an optimistic locking failure,
* or a deadlock loser failure, or until a maximum number of retries have
* been attempted.
* <p>
* If there is already an active transaction, then the callback is merely
* executed and any retry logic is left to the caller.
*
* @param cb The callback containing the unit of work.
* @param readOnly Whether this is a read only transaction.
* @return Returns the result of the unit of work.
*/
public <R> R doInTransaction(RetryingTransactionCallback<R> cb, boolean readOnly)
{
return doInTransaction(cb, readOnly, false);
}
/**
* Execute a callback in a transaction until it succeeds, fails
* because of an error not the result of an optimistic locking failure,
* or a deadlock loser failure, or until a maximum number of retries have
* been attempted.
* <p>
* It is possible to force a new transaction to be created or to partake in
* any existing transaction.
*
* @param cb The callback containing the unit of work.
* @param readOnly Whether this is a read only transaction.
* @param newTransaction <tt>true</tt> to force a new transaction or
* <tt>false</tt> to partake in any existing transaction.
* @return Returns the result of the unit of work.
*/
public <R> R doInTransaction(RetryingTransactionCallback<R> cb, boolean readOnly, boolean newTransaction)
{
// Track the last exception caught, so that we
// can throw it if we run out of retries.
@@ -96,7 +181,14 @@ public class RetryingTransactionHelper
boolean isNew = false;
try
{
txn = fTxnService.getUserTransaction(readOnly);
if (newTransaction)
{
txn = fTxnService.getNonPropagatingUserTransaction();
}
else
{
txn = fTxnService.getUserTransaction(readOnly);
}
// Do we need to handle transaction demarcation. If
// no, we cannot do retries, that will be up to the containing
// transaction.
@@ -106,7 +198,7 @@ public class RetryingTransactionHelper
txn.begin();
}
// Do the work.
Object result = cb.execute();
R result = cb.execute();
// Only commit if we 'own' the transaction.
if (isNew)
{
@@ -160,45 +252,28 @@ public class RetryingTransactionHelper
}
lastException = (e instanceof RuntimeException) ?
(RuntimeException)e : new AlfrescoRuntimeException("Unknown Exception in Transaction.", e);
Throwable t = e;
boolean shouldRetry = false;
while (t != null)
// Check if there is a cause for retrying
Throwable retryCause = ExceptionStackUtil.getCause(e, RETRY_EXCEPTIONS);
if (retryCause != null)
{
// These are the 'OK' exceptions. These mean we can retry.
if (t instanceof ConcurrencyFailureException ||
t instanceof DeadlockLoserDataAccessException ||
t instanceof StaleObjectStateException ||
t instanceof LockAcquisitionException)
// Sleep a random amount of time before retrying.
// The sleep interval increases with the number of retries.
try
{
shouldRetry = true;
// Sleep a random amount of time before retrying.
// The sleep interval increases with the number of retries.
try
{
Thread.sleep(fRandom.nextInt(500 * count + 500));
}
catch (InterruptedException ie)
{
// Do nothing.
}
break;
Thread.sleep(fRandom.nextInt(500 * count + 500));
}
if (t == t.getCause())
catch (InterruptedException ie)
{
break;
// Do nothing.
}
t = t.getCause();
}
if (shouldRetry)
{
// Try again
continue;
}
// It was a 'bad' exception.
if (e instanceof RuntimeException)
else
{
throw (RuntimeException)e;
// It was a 'bad' exception.
throw lastException;
}
throw new AlfrescoRuntimeException("Exception in Transaction.", e);
}
}
// We've worn out our welcome and retried the maximum number of times.

View File

@@ -37,7 +37,9 @@ import org.apache.commons.logging.LogFactory;
/**
* Class containing transactions helper methods and interfaces.
*
* @author Roy Wetherall
* @deprecated Use a {@link RetryingTransactionHelper} instance
*
* @author Derek Hulley
*/
public class TransactionUtil
{
@@ -48,6 +50,9 @@ public class TransactionUtil
* <p>
* This interface encapsulates a unit of work that should be done within a
* transaction.
*
* @deprecated
* @see RetryingTransactionHelper.RetryingTransactionCallback
*/
public interface TransactionWork<Result>
{
@@ -74,6 +79,8 @@ public class TransactionUtil
* @param transactionWork the transaction work
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/
public static <R> R executeInUserTransaction(
TransactionService transactionService,
@@ -91,6 +98,8 @@ public class TransactionUtil
* @param readOnly true if the transaction should be read-only
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/
public static <R> R executeInUserTransaction(
TransactionService transactionService,
@@ -108,6 +117,8 @@ public class TransactionUtil
* @param transactionWork the transaction work
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/
public static <R> R executeInNonPropagatingUserTransaction(
TransactionService transactionService,
@@ -125,6 +136,8 @@ public class TransactionUtil
* @param readOnly true if the transaction should be read-only
*
* @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/
public static <R> R executeInNonPropagatingUserTransaction(
TransactionService transactionService,