mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
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:
@@ -5247,7 +5247,7 @@ public class AVMServiceTest extends AVMServiceTestBase
|
||||
try
|
||||
{
|
||||
setupBasicTree();
|
||||
class TxnCallback implements RetryingTransactionHelper.Callback
|
||||
class TxnCallback implements RetryingTransactionHelper.RetryingTransactionCallback
|
||||
{
|
||||
public Object execute()
|
||||
{
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
|
@@ -35,6 +35,8 @@ public interface Server
|
||||
{
|
||||
public Long getId();
|
||||
|
||||
public Long getVersion();
|
||||
|
||||
public String getIpAddress();
|
||||
|
||||
public void setIpAddress(String ipAddress);
|
||||
|
@@ -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
|
||||
|
@@ -33,6 +33,8 @@ public interface Transaction
|
||||
{
|
||||
public Long getId();
|
||||
|
||||
public Long getVersion();
|
||||
|
||||
public String getChangeTxnId();
|
||||
|
||||
public void setChangeTxnId(String changeTxnId);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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">
|
||||
|
@@ -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;
|
||||
|
@@ -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(
|
||||
|
@@ -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");
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -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.
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user