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

@@ -54,6 +54,7 @@
<ref bean="patch.db-V2.0-ExplicitIndexes" /> <ref bean="patch.db-V2.0-ExplicitIndexes" />
<ref bean="patch.db-V2.0-AVMFKIndexes" /> <ref bean="patch.db-V2.0-AVMFKIndexes" />
<ref bean="patch.db-V2.1-JBPMData" /> <ref bean="patch.db-V2.1-JBPMData" />
<ref bean="patch.db-V2.1-VersionColumns" />
</list> </list>
</property> </property>
</bean> </bean>

View File

@@ -0,0 +1,32 @@
--
-- Title: Fill 'version' columns with data
-- Database: Generic
-- Since: V2.1 Schema 54
-- Author: Derek Hulley
--
-- Please contact support@alfresco.com if you need assistance with the upgrade.
--
UPDATE alf_store SET version = 1 WHERE version IS NULL;
UPDATE alf_node SET version = 1 WHERE version IS NULL;
UPDATE alf_child_assoc SET version = 1 WHERE version IS NULL;
UPDATE alf_node_assoc SET version = 1 WHERE version IS NULL;
UPDATE alf_node_status SET version = 1 WHERE version IS NULL;
UPDATE alf_transaction SET version = 1 WHERE version IS NULL;
UPDATE alf_server SET version = 1 WHERE version IS NULL;
UPDATE alf_access_control_list SET version = 1 WHERE version IS NULL;
UPDATE alf_access_control_entry SET version = 1 WHERE version IS NULL;
UPDATE alf_permission SET version = 1 WHERE version IS NULL;
UPDATE alf_authority SET version = 1 WHERE version IS NULL;
--
-- Record script finish
--
DELETE FROM alf_applied_patch WHERE id = 'patch.db-V2.1-VersionColumns';
INSERT INTO alf_applied_patch
(id, description, fixes_from_schema, fixes_to_schema, applied_to_schema, target_schema, applied_on_date, applied_to_server, was_executed, succeeded, report)
VALUES
(
'patch.db-V2.1-VersionColumns', 'Manually executed script upgrade V2.1: Created initial version number for ADM entities',
0, 53, -1, 54, null, 'UNKOWN', 1, 1, 'Script completed'
);

View File

@@ -177,7 +177,7 @@
<ref bean="sessionFactory" /> <ref bean="sessionFactory" />
</property> </property>
<property name="threshold"> <property name="threshold">
<value>2000</value> <value>5000</value>
</property> </property>
</bean> </bean>

View File

@@ -753,4 +753,16 @@
<ref bean="avmNodeDAO"/> <ref bean="avmNodeDAO"/>
</property> </property>
</bean> </bean>
<bean id="patch.db-V2.1-VersionColumns" class="org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch" parent="basePatch">
<property name="id"><value>patch.db-V2.1-VersionColumns</value></property>
<property name="description"><value>patch.schemaUpgradeScript.description</value></property>
<property name="fixesFromSchema"><value>0</value></property>
<property name="fixesToSchema"><value>53</value></property>
<property name="targetSchema"><value>54</value></property>
<property name="scriptUrl">
<value>classpath:alfresco/dbscripts/upgrade/2.1/${db.script.dialect}/AlfrescoSchemaUpdate-2.1-VersionColumns.sql</value>
</property>
</bean>
</beans> </beans>

View File

@@ -19,4 +19,4 @@ version.build=@build-number@
# Schema number # Schema number
version.schema=53 version.schema=54

View File

@@ -5247,7 +5247,7 @@ public class AVMServiceTest extends AVMServiceTestBase
try try
{ {
setupBasicTree(); setupBasicTree();
class TxnCallback implements RetryingTransactionHelper.Callback class TxnCallback implements RetryingTransactionHelper.RetryingTransactionCallback
{ {
public Object execute() 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.MapAttributeValue;
import org.alfresco.repo.attributes.StringAttributeValue; import org.alfresco.repo.attributes.StringAttributeValue;
import org.alfresco.repo.transaction.RetryingTransactionHelper; 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.AttrQueryEquals;
import org.alfresco.service.cmr.attributes.AttributeService; import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMExistsException;
@@ -88,7 +88,7 @@ public class AVMLockingServiceImpl implements AVMLockingService
public void init() public void init()
{ {
Callback callback = new Callback() RetryingTransactionCallback callback = new RetryingTransactionCallback()
{ {
public Object execute() public Object execute()
{ {

View File

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

View File

@@ -37,7 +37,12 @@ public interface DbAccessControlEntry
/** /**
* @return Returns the identifier for this object * @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 * @return Returns the containing access control list

View File

@@ -36,7 +36,12 @@ import org.alfresco.repo.domain.hibernate.DbAccessControlEntryImpl;
*/ */
public interface DbAccessControlList public interface DbAccessControlList
{ {
public long getId(); public Long getId();
/**
* @return Returns the version number for optimistic locking
*/
public Long getVersion();
/** /**
* *

View File

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

View File

@@ -38,7 +38,12 @@ public interface DbPermission extends Serializable
/** /**
* @return Returns the automatically assigned ID * @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 * @return Returns the qualified name of this permission

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,6 +33,8 @@ public interface Transaction
{ {
public Long getId(); public Long getId();
public Long getVersion();
public String getChangeTxnId(); public String getChangeTxnId();
public void setChangeTxnId(String changeTxnId); 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.repo.domain.Node;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
/** /**
* @author Derek Hulley * @author Derek Hulley
@@ -42,6 +43,7 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
private static final long serialVersionUID = -8993272236626580410L; private static final long serialVersionUID = -8993272236626580410L;
private Long id; private Long id;
private Long version;
private Node parent; private Node parent;
private Node child; private Node child;
private QName typeQName; 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() public String toString()
{ {
StringBuffer sb = new StringBuffer(32); StringBuffer sb = new StringBuffer(32);
@@ -192,6 +220,20 @@ public class ChildAssocImpl implements ChildAssoc, Serializable
this.id = id; this.id = id;
} }
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public Node getParent() public Node getParent()
{ {
return parent; return parent;

View File

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

View File

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

View File

@@ -47,6 +47,7 @@ public class DbAuthorityImpl extends LifecycleAdapter
private static Log logger = LogFactory.getLog(DbAuthorityImpl.class); private static Log logger = LogFactory.getLog(DbAuthorityImpl.class);
private Long version;
private String recipient; private String recipient;
private Set<String> externalKeys; private Set<String> externalKeys;
@@ -105,6 +106,20 @@ public class DbAuthorityImpl extends LifecycleAdapter
return super.onDelete(session); 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() public String getRecipient()
{ {
return recipient; return recipient;

View File

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

View File

@@ -49,6 +49,7 @@ import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseSpringTest; import org.alfresco.util.BaseSpringTest;
@@ -178,6 +179,8 @@ public class HibernateNodeTest extends BaseSpringTest
// Sybase // Sybase
// expected // expected
} }
// Just clear out any pending changes
getSession().clear();
} }
/** /**
@@ -584,4 +587,58 @@ public class HibernateNodeTest extends BaseSpringTest
getSession().flush(); getSession().flush();
getSession().clear(); 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 --> <!-- the store-unique identifier -->
<property name="uuid" column="uuid" type="string" length="36" /> <property name="uuid" column="uuid" type="string" length="36" />
</natural-id> </natural-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<property name="typeQName" column="type_qname" type="QName" length="255" not-null="true" /> <property name="typeQName" column="type_qname" type="QName" length="255" not-null="true" />
<!-- forward assoc to access control list (optional) --> <!-- forward assoc to access control list (optional) -->
<many-to-one <many-to-one
@@ -115,6 +118,8 @@
<key-property name="identifier" length="100" /> <key-property name="identifier" length="100" />
<key-property name="guid" length="36" /> <key-property name="guid" length="36" />
</composite-id> </composite-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to transaction --> <!-- forward assoc to transaction -->
<many-to-one <many-to-one
name="transaction" name="transaction"
@@ -148,13 +153,15 @@
<id name="id" column="id" type="long" > <id name="id" column="id" type="long" >
<generator class="native" /> <generator class="native" />
</id> </id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to parent node --> <!-- forward assoc to parent node -->
<many-to-one <many-to-one
name="parent" name="parent"
class="org.alfresco.repo.domain.hibernate.NodeImpl" class="org.alfresco.repo.domain.hibernate.NodeImpl"
lazy="proxy" lazy="proxy"
fetch="select" fetch="select"
optimistic-lock="true" optimistic-lock="false"
not-null="true" not-null="true"
unique-key="UIDX_CHILD_NAME" > unique-key="UIDX_CHILD_NAME" >
<column name="parent_node_id" not-null="true" /> <column name="parent_node_id" not-null="true" />
@@ -165,7 +172,7 @@
lazy="proxy" lazy="proxy"
fetch="select" fetch="select"
class="org.alfresco.repo.domain.hibernate.NodeImpl" class="org.alfresco.repo.domain.hibernate.NodeImpl"
optimistic-lock="true" optimistic-lock="false"
not-null="true" > not-null="true" >
<column name="child_node_id" not-null="true"/> <column name="child_node_id" not-null="true"/>
</many-to-one> </many-to-one>
@@ -190,6 +197,7 @@
<many-to-one <many-to-one
name="source" name="source"
class="org.alfresco.repo.domain.hibernate.NodeImpl" class="org.alfresco.repo.domain.hibernate.NodeImpl"
optimistic-lock="false"
lazy="false" lazy="false"
fetch="join" fetch="join"
not-null="true" > not-null="true" >
@@ -199,6 +207,7 @@
<many-to-one <many-to-one
name="target" name="target"
class="org.alfresco.repo.domain.hibernate.NodeImpl" class="org.alfresco.repo.domain.hibernate.NodeImpl"
optimistic-lock="false"
lazy="false" lazy="false"
fetch="join" fetch="join"
not-null="true" > not-null="true" >
@@ -206,6 +215,8 @@
</many-to-one> </many-to-one>
<property name="typeQName" column="type_qname" type="QName" length="255" not-null="true" /> <property name="typeQName" column="type_qname" type="QName" length="255" not-null="true" />
</natural-id> </natural-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
</class> </class>
<query name="store.GetAllStores"> <query name="store.GetAllStores">
@@ -272,6 +283,17 @@
assoc.id assoc.id
</query> </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"> <query name="node.GetChildAssocByTypeAndName">
select select
assoc assoc

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,8 @@
<generator class="native" /> <generator class="native" />
</id> </id>
<version column="version" name="version" type="long" />
<set name="entries" <set name="entries"
inverse="true" inverse="true"
lazy="false" lazy="false"
@@ -74,6 +76,8 @@
not-null="true" /> not-null="true" />
</natural-id> </natural-id>
<version column="version" name="version" type="long" />
<property name="allowed" column="allowed" type="boolean" not-null="true" /> <property name="allowed" column="allowed" type="boolean" not-null="true" />
</class> </class>
@@ -97,6 +101,8 @@
<property name="name" type="string" length="100" column="name" /> <property name="name" type="string" length="100" column="name" />
</natural-id> </natural-id>
<version column="version" name="version" type="long" />
</class> </class>
<class <class
@@ -111,6 +117,8 @@
<id name="recipient" column="recipient" type="string" length="100" /> <id name="recipient" column="recipient" type="string" length="100" />
<version column="version" name="version" type="long" />
<set <set
name="externalKeys" name="externalKeys"
table="alf_auth_ext_keys" 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 static final long serialVersionUID = 8063452519040344479L;
private Long id; private Long id;
private Long version;
private String ipAddress; private String ipAddress;
public ServerImpl() public ServerImpl()
@@ -77,6 +78,20 @@ public class ServerImpl extends LifecycleAdapter implements Server, Serializable
return ipAddress; 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) public void setIpAddress(String ipAddress)
{ {
this.ipAddress = ipAddress; this.ipAddress = ipAddress;

View File

@@ -27,6 +27,7 @@ package org.alfresco.repo.domain.hibernate;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.util.resource.MethodResourceManager; import org.alfresco.util.resource.MethodResourceManager;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -50,10 +51,49 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
*/ */
public class SessionSizeResourceManager extends HibernateDaoSupport implements MethodResourceManager 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); private static Log logger = LogFactory.getLog(SessionSizeResourceManager.class);
/** Default 1000 */ /** 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 * Set the {@link Session#clear()} threshold. If the number of entities and collections in the
@@ -74,6 +114,12 @@ public class SessionSizeResourceManager extends HibernateDaoSupport implements M
long transactionElapsedTimeNs, long transactionElapsedTimeNs,
Method currentMethod) Method currentMethod)
{ {
if (isDisableInTransaction())
{
// Don't do anything
return;
}
// We are go for interfering
Session session = getSession(false); Session session = getSession(false);
SessionStatistics stats = session.getStatistics(); SessionStatistics stats = session.getStatistics();
int entityCount = stats.getEntityCount(); int entityCount = stats.getEntityCount();

View File

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

View File

@@ -44,6 +44,7 @@ public class StoreImpl implements Store, Serializable
private static final long serialVersionUID = -6135740209100885890L; private static final long serialVersionUID = -6135740209100885890L;
private StoreKey key; private StoreKey key;
private Long version;
private Node rootNode; private Node rootNode;
private transient ReadLock refReadLock; 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() public Node getRootNode()
{ {
return rootNode; return rootNode;

View File

@@ -19,6 +19,8 @@
<id name="id" column="id" type="long" > <id name="id" column="id" type="long" >
<generator class="native" /> <generator class="native" />
</id> </id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
<!-- forward assoc to server IP --> <!-- forward assoc to server IP -->
<many-to-one <many-to-one
name="server" name="server"
@@ -48,6 +50,8 @@
<natural-id> <natural-id>
<property name="ipAddress" column="ip_address" type="string" length="15" not-null="true" /> <property name="ipAddress" column="ip_address" type="string" length="15" not-null="true" />
</natural-id> </natural-id>
<!-- Optimistic locking -->
<version column="version" name="version" type="long" />
</class> </class>
<query name="server.getServerByIpAddress"> <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 static final long serialVersionUID = -8264339795578077552L;
private Long id; private Long id;
private Long version;
private String changeTxnId; private String changeTxnId;
private Server server; private Server server;
@@ -74,6 +75,20 @@ public class TransactionImpl extends LifecycleAdapter implements Transaction, Se
this.id = id; this.id = id;
} }
public Long getVersion()
{
return version;
}
/**
* For Hibernate use
*/
@SuppressWarnings("unused")
private void setVersion(Long version)
{
this.version = version;
}
public String getChangeTxnId() public String getChangeTxnId()
{ {
return changeTxnId; return changeTxnId;

View File

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

View File

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

View File

@@ -27,15 +27,14 @@ package org.alfresco.repo.node;
import java.io.InputStream; import java.io.InputStream;
import java.util.Map; import java.util.Map;
import javax.transaction.UserTransaction;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.alfresco.repo.dictionary.DictionaryDAO; import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer; import org.alfresco.repo.search.impl.lucene.fts.FullTextSearchIndexer;
import org.alfresco.repo.security.authentication.AuthenticationComponent; 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.ServiceRegistry;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
@@ -78,6 +77,7 @@ public class ConcurrentNodeServiceTest extends TestCase
private NodeService nodeService; private NodeService nodeService;
private TransactionService transactionService; private TransactionService transactionService;
private RetryingTransactionHelper retryingTransactionHelper;
private NodeRef rootNodeRef; private NodeRef rootNodeRef;
@@ -107,23 +107,24 @@ public class ConcurrentNodeServiceTest extends TestCase
nodeService = (NodeService) ctx.getBean("dbNodeService"); nodeService = (NodeService) ctx.getBean("dbNodeService");
transactionService = (TransactionService) ctx.getBean("transactionComponent"); transactionService = (TransactionService) ctx.getBean("transactionComponent");
retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper");
luceneFTS = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer"); luceneFTS = (FullTextSearchIndexer) ctx.getBean("LuceneFullTextSearchIndexer");
this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent"); this.authenticationComponent = (AuthenticationComponent) ctx.getBean("authenticationComponent");
this.authenticationComponent.setSystemUserAsCurrentUser(); this.authenticationComponent.setSystemUserAsCurrentUser();
// create a first store directly // create a first store directly
TransactionUtil.executeInUserTransaction(transactionService, new TransactionUtil.TransactionWork<Object>() RetryingTransactionCallback<Object> createRootNodeCallback = new RetryingTransactionCallback<Object>()
{ {
public Object execute() throws Exception
public Object doWork() throws Exception
{ {
StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" StoreRef storeRef = nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_"
+ System.currentTimeMillis()); + System.currentTimeMillis());
rootNodeRef = nodeService.getRootNode(storeRef); rootNodeRef = nodeService.getRootNode(storeRef);
return null; return null;
} }
}); };
retryingTransactionHelper.doInTransaction(createRootNodeCallback);
} }
@Override @Override
@@ -140,17 +141,17 @@ public class ConcurrentNodeServiceTest extends TestCase
protected Map<QName, ChildAssociationRef> commitNodeGraph() throws Exception protected Map<QName, ChildAssociationRef> commitNodeGraph() throws Exception
{ {
return TransactionUtil.executeInUserTransaction(transactionService, RetryingTransactionCallback<Map<QName, ChildAssociationRef>> buildGraphCallback =
new TransactionUtil.TransactionWork<Map<QName, ChildAssociationRef>>() new RetryingTransactionCallback<Map<QName, ChildAssociationRef>>()
{ {
public Map<QName, ChildAssociationRef> execute() throws Exception
public Map<QName, ChildAssociationRef> doWork() throws Exception
{ {
Map<QName, ChildAssociationRef> answer = buildNodeGraph(); Map<QName, ChildAssociationRef> answer = buildNodeGraph();
return answer; return answer;
} }
}); };
return retryingTransactionHelper.doInTransaction(buildGraphCallback);
} }
public void xtest1() throws Exception 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 execute() throws Exception
public Object doWork() throws Exception
{ {
// There are two nodes at the base level in each test // There are two nodes at the base level in each test
assertEquals(2 * ((COUNT * REPEATS) + 1), nodeService.getChildAssocs(rootNodeRef).size()); assertEquals(2 * ((COUNT * REPEATS) + 1), nodeService.getChildAssocs(rootNodeRef).size());
@@ -276,9 +277,8 @@ public class ConcurrentNodeServiceTest extends TestCase
return null; 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.domain.NodeStatus;
import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.node.BaseNodeServiceTest;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.repo.transaction.TransactionUtil.TransactionWork;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -143,9 +142,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
endTransaction(); endTransaction();
// change property - check status // 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()); nodeService.setProperty(n6Ref, ContentModel.PROP_CREATED, new Date());
return null; return null;
@@ -154,9 +153,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, changePropertiesWork); executeAndCheck(n6Ref, changePropertiesWork);
// add an aspect // 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); nodeService.addAspect(n6Ref, ASPECT_QNAME_TEST_MARKER, null);
return null; return null;
@@ -165,9 +164,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, addAspectWork); executeAndCheck(n6Ref, addAspectWork);
// remove an aspect // 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); nodeService.removeAspect(n6Ref, ASPECT_QNAME_TEST_MARKER);
return null; return null;
@@ -176,9 +175,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, removeAspectWork); executeAndCheck(n6Ref, removeAspectWork);
// move the node // move the node
TransactionWork<Object> moveNodeWork = new TransactionWork<Object>() RetryingTransactionCallback<Object> moveNodeWork = new RetryingTransactionCallback<Object>()
{ {
public Object doWork() public Object execute()
{ {
nodeService.moveNode( nodeService.moveNode(
n6Ref, n6Ref,
@@ -191,9 +190,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, moveNodeWork); executeAndCheck(n6Ref, moveNodeWork);
// delete the node // delete the node
TransactionWork<Object> deleteNodeWork = new TransactionWork<Object>() RetryingTransactionCallback<Object> deleteNodeWork = new RetryingTransactionCallback<Object>()
{ {
public Object doWork() public Object execute()
{ {
nodeService.deleteNode(n6Ref); nodeService.deleteNode(n6Ref);
return null; return null;
@@ -202,9 +201,9 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
executeAndCheck(n6Ref, deleteNodeWork); executeAndCheck(n6Ref, deleteNodeWork);
// check cascade-deleted nodes // check cascade-deleted nodes
TransactionWork<Object> checkCascadeWork = new TransactionWork<Object>() RetryingTransactionCallback<Object> checkCascadeCallback = new RetryingTransactionCallback<Object>()
{ {
public Object doWork() public Object execute()
{ {
// check n6 // check n6
NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false); NodeStatus n6Status = nodeDaoService.getNodeStatus(n6Ref, false);
@@ -221,12 +220,12 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
return null; return null;
} }
}; };
TransactionUtil.executeInUserTransaction(txnService, checkCascadeWork); retryingTransactionHelper.doInTransaction(checkCascadeCallback);
// check node recreation // 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_PROTOCOL, n6Ref.getStoreRef().getProtocol());
properties.put(ContentModel.PROP_STORE_IDENTIFIER, n6Ref.getStoreRef().getIdentifier()); properties.put(ContentModel.PROP_STORE_IDENTIFIER, n6Ref.getStoreRef().getIdentifier());
@@ -242,10 +241,10 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
return null; 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(); UserTransaction txn = txnService.getUserTransaction();
txn.begin(); txn.begin();
@@ -257,7 +256,7 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
assertNotSame(currentTxnId, currentStatus.getChangeTxnId()); assertNotSame(currentTxnId, currentStatus.getChangeTxnId());
try try
{ {
work.doWork(); callback.execute();
// get the status // get the status
NodeRef.Status newStatus = nodeService.getNodeStatus(nodeRef); NodeRef.Status newStatus = nodeService.getNodeStatus(nodeRef);
assertNotNull(newStatus); assertNotNull(newStatus);
@@ -362,4 +361,20 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest
// Get it again // Get it again
nodeService.getPrimaryParent(n8Ref); 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.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.zip.CRC32; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.LockMode;
import org.hibernate.ObjectDeletedException; import org.hibernate.ObjectDeletedException;
import org.hibernate.Query; import org.hibernate.Query;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.exception.LockAcquisitionException;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DeadlockLoserDataAccessException;
import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport; 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_PRIMARY_CHILD_NODE_STATUSES = "node.GetPrimaryChildNodeStatuses";
private static final String QUERY_GET_CHILD_ASSOCS = "node.GetChildAssocs"; 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_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_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 = "node.GetChildAssocRefs";
private static final String QUERY_GET_CHILD_ASSOC_REFS_BY_QNAME = "node.GetChildAssocRefsByQName"; 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 final String QUERY_GET_SERVER_BY_IPADDRESS = "server.getServerByIpAddress";
private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class); private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class);
private static Log loggerChildAssoc = LogFactory.getLog(HibernateNodeDaoServiceImpl.class.getName() + ".ChildAssoc");
/** a uuid identifying this unique instance */ /** a uuid identifying this unique instance */
private final String uuid; 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 static TransactionAwareSingleton<Long> serverIdSingleton = new TransactionAwareSingleton<Long>();
private final String ipAddress; private final String ipAddress;
private Random randomWaitTime;
/** used for debugging */ /** used for debugging */
private Set<String> changeTxnIdSet; private Set<String> changeTxnIdSet;
@@ -121,6 +131,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
public HibernateNodeDaoServiceImpl() public HibernateNodeDaoServiceImpl()
{ {
this.uuid = GUID.generate(); this.uuid = GUID.generate();
this.maxLockRetries = 20;
try try
{ {
ipAddress = InetAddress.getLocalHost().getHostAddress(); ipAddress = InetAddress.getLocalHost().getHostAddress();
@@ -129,6 +140,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{ {
throw new AlfrescoRuntimeException("Failed to get server IP address", e); throw new AlfrescoRuntimeException("Failed to get server IP address", e);
} }
randomWaitTime = new Random(System.currentTimeMillis());
changeTxnIdSet = new HashSet<String>(0); changeTxnIdSet = new HashSet<String>(0);
} }
@@ -158,6 +170,16 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
return uuid.hashCode(); 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 * 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()); nodeStatus.getTransaction().setChangeTxnId(AlfrescoTransactionSupport.getTransactionId());
// finally delete the node // finally delete the node
getHibernateTemplate().delete(node); getHibernateTemplate().delete(node);
// flush to ensure constraints can't be violated // // flush to ensure constraints can't be violated
getSession().flush(); // getSession().flush();
// done // done
} }
@@ -584,13 +606,7 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
public void setChildNameUnique(final ChildAssoc childAssoc, String childName) public void setChildNameUnique(final ChildAssoc childAssoc, String childName)
{ {
/* /*
* As the Hibernate session is rendered useless when an exception is * Work out if there has been any change in the name
* 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.
*/ */
String childNameNew = null; 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() HibernateCallback callback = new HibernateCallback()
{ {
public Object doInHibernate(Session session) public Object doInHibernate(Session session)
{ {
session.flush();
Query query = session Query query = session
.getNamedQuery(HibernateNodeDaoServiceImpl.UPDATE_SET_CHILD_ASSOC_NAME) .getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CHILD_ASSOC_BY_NAME)
.setString("newName", childNameNewShort) .setLong("parentId", parentNode.getId())
.setLong("newNameCrc", childNameNewCrc) .setParameter("childNodeName", childNameNewShort)
.setLong("childAssocId", childAssoc.getId()); .setLong("childNodeNameCrc", childNameNewCrc);
return (Integer) query.executeUpdate(); return query.uniqueResult();
} }
}; };
try ChildAssoc childAssocExisting = (ChildAssoc) getHibernateTemplate().execute(callback);
if (childAssocExisting != null)
{ {
Integer count = (Integer) getHibernateTemplate().execute(callback); // There is already an entity
// refresh the entity directly if (loggerChildAssoc.isDebugEnabled())
if (count.intValue() == 0)
{ {
if (logger.isDebugEnabled()) loggerChildAssoc.debug(
{ "Duplicate child association detected: \n" +
logger.debug("ChildAssoc not updated: " + childAssoc.getId()); " Child Assoc: " + childAssoc + "\n" +
" Existing Child Assoc: " + childName);
} }
throw new DuplicateChildNodeNameException(
parentNode.getNodeRef(),
childAssoc.getTypeQName(),
childName);
} }
else // 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())
{ {
getHibernateTemplate().refresh(childAssoc); loggerChildAssoc.debug(
} "Updated child association: \n" +
} " Parent: " + parentNode + "\n" +
catch (DataIntegrityViolationException e) " Child Assoc: " + childAssoc);
{
NodeRef parentNodeRef = childAssoc.getParent().getNodeRef();
QName assocTypeQName = childAssoc.getTypeQName();
throw new DuplicateChildNodeNameException(parentNodeRef, assocTypeQName, childName);
} }
} }
@@ -868,9 +919,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
return; return;
} }
if (logger.isDebugEnabled()) if (loggerChildAssoc.isDebugEnabled())
{ {
logger.debug( loggerChildAssoc.debug(
"Deleting parent-child association " + assoc.getId() + "Deleting parent-child association " + assoc.getId() +
(cascade ? " with" : " without") + " cascade:" + (cascade ? " with" : " without") + " cascade:" +
assoc.getParent().getId() + " -> " + assoc.getChild().getId()); assoc.getParent().getId() + " -> " + assoc.getChild().getId());
@@ -894,10 +945,10 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
* duplicate call will be received to do this * duplicate call will be received to do this
*/ */
} }
//
// To ensure the validity of the constraint enforcement by the database, // // To ensure the validity of the constraint enforcement by the database,
// we have to flush here // // we have to flush here. It is possible to delete and recreate the instance
getSession().flush(); // getSession().flush();
} }
/** /**
@@ -1117,9 +1168,9 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{ {
// Remove instance // Remove instance
getHibernateTemplate().delete(assoc); getHibernateTemplate().delete(assoc);
// Flush to ensure that the database constraints aren't violated if the assoc // // Flush to ensure that the database constraints aren't violated if the assoc
// is recreated in the transaction // // is recreated in the transaction
getSession().flush(); // getSession().flush();
} }
public List<Serializable> getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition) public List<Serializable> getPropertyValuesByActualType(DataTypeDefinition actualDataTypeDefinition)

View File

@@ -136,9 +136,6 @@ public class SessionSizeManagementTest extends BaseNodeServiceTest
} }
createNodes(nodeService, LOAD_COUNT, true); 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 // Now flush integrity to be sure things are not broken
AlfrescoTransactionSupport.flush(); 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.DictionaryNamespaceComponent;
import org.alfresco.repo.dictionary.M2Model; import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.NamespaceDAOImpl; import org.alfresco.repo.dictionary.NamespaceDAOImpl;
import org.alfresco.repo.domain.hibernate.SessionSizeResourceManager;
import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.node.BaseNodeServiceTest;
import org.alfresco.repo.search.MLAnalysisMode; import org.alfresco.repo.search.MLAnalysisMode;
import org.alfresco.repo.search.QueryParameterDefImpl; 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.search.results.DetachedResultSet;
import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil; 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.ServiceRegistry;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService; 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.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MLText; 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.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.Path;
@@ -120,6 +122,7 @@ public class ADMLuceneTest extends TestCase
QName aspectWithChildren = QName.createQName(TEST_NAMESPACE, "aspectWithChildren"); QName aspectWithChildren = QName.createQName(TEST_NAMESPACE, "aspectWithChildren");
TransactionService transactionService; TransactionService transactionService;
RetryingTransactionHelper retryingTransactionHelper;
NodeService nodeService; NodeService nodeService;
@@ -205,6 +208,8 @@ public class ADMLuceneTest extends TestCase
indexerAndSearcher = (LuceneIndexerAndSearcher) ctx.getBean("admLuceneIndexerAndSearcherFactory"); indexerAndSearcher = (LuceneIndexerAndSearcher) ctx.getBean("admLuceneIndexerAndSearcherFactory");
((AbstractLuceneIndexerAndSearcherFactory)indexerAndSearcher).setMaxAtomicTransformationTime(1000000); ((AbstractLuceneIndexerAndSearcherFactory)indexerAndSearcher).setMaxAtomicTransformationTime(1000000);
transactionService = (TransactionService) ctx.getBean("transactionComponent"); transactionService = (TransactionService) ctx.getBean("transactionComponent");
retryingTransactionHelper = (RetryingTransactionHelper) ctx.getBean("retryingTransactionHelper");
serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
namespaceDao = (NamespaceDAOImpl) ctx.getBean("namespaceDAO"); namespaceDao = (NamespaceDAOImpl) ctx.getBean("namespaceDAO");
@@ -1030,8 +1035,12 @@ public class ADMLuceneTest extends TestCase
assertEquals(1, results.length()); assertEquals(1, results.length());
results.close(); results.close();
UserTransaction tx1 = transactionService.getUserTransaction(); RetryingTransactionCallback<Object> createAndDeleteCallback = new RetryingTransactionCallback<Object>()
tx1.begin(); {
public Object execute() throws Throwable
{
// Disable resource management
SessionSizeResourceManager.setDisableInTransaction();
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>(); HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>();
@@ -1046,9 +1055,11 @@ public class ADMLuceneTest extends TestCase
{ {
nodeService.deleteNode(car.getChildRef()); nodeService.deleteNode(car.getChildRef());
} }
} }
tx1.commit(); return null;
}
};
retryingTransactionHelper.doInTransaction(createAndDeleteCallback);
UserTransaction tx3 = transactionService.getUserTransaction(); UserTransaction tx3 = transactionService.getUserTransaction();
tx3.begin(); tx3.begin();
@@ -1134,8 +1145,10 @@ public class ADMLuceneTest extends TestCase
try try
{ {
System.out.println("Start " + this.getName()); System.out.println("Start " + this.getName());
UserTransaction tx1 = transactionService.getUserTransaction(); RetryingTransactionCallback<Object> createAndDeleteCallback = new RetryingTransactionCallback<Object>()
tx1.begin(); {
public Object execute() throws Throwable
{
for (int i = 0; i < 20; i++) for (int i = 0; i < 20; i++)
{ {
HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>(); HashSet<ChildAssociationRef> refs = new HashSet<ChildAssociationRef>();
@@ -1150,13 +1163,16 @@ public class ADMLuceneTest extends TestCase
{ {
nodeService.deleteNode(car.getChildRef()); nodeService.deleteNode(car.getChildRef());
} }
} }
tx1.commit(); return null;
}
};
retryingTransactionHelper.doInTransaction(createAndDeleteCallback);
System.out.println("End " + this.getName()); System.out.println("End " + this.getName());
} }
catch (Exception e) catch (Exception e)
{ {
System.out.println("End " + this.getName() + " with error " + e.getMessage());
e.printStackTrace(); e.printStackTrace();
} }
finally 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; package org.alfresco.repo.transaction;
@@ -10,6 +31,7 @@ import javax.transaction.SystemException;
import javax.transaction.UserTransaction; import javax.transaction.UserTransaction;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.hibernate.StaleObjectStateException; import org.hibernate.StaleObjectStateException;
@@ -21,12 +43,27 @@ import org.springframework.dao.DeadlockLoserDataAccessException;
* A helper that runs a unit of work inside a UserTransaction, * A helper that runs a unit of work inside a UserTransaction,
* transparently retrying the unit of work if the cause of * 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 * @author britt
*/ */
public class RetryingTransactionHelper public class RetryingTransactionHelper
{ {
private static Logger fgLogger = Logger.getLogger(RetryingTransactionHelper.class); 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. * Reference to the TransactionService instance.
*/ */
@@ -46,9 +83,15 @@ public class RetryingTransactionHelper
* Callback interface * Callback interface
* @author britt * @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, * 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 * or a deadlock loser failure, or until a maximum number of retries have
* been attempted. * 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. 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 <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 cb The callback containing the unit of work.
* @param readOnly Whether this is a read only transaction. * @param readOnly Whether this is a read only transaction.
* @return The result of 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, 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 // Track the last exception caught, so that we
// can throw it if we run out of retries. // can throw it if we run out of retries.
@@ -95,8 +180,15 @@ public class RetryingTransactionHelper
UserTransaction txn = null; UserTransaction txn = null;
boolean isNew = false; boolean isNew = false;
try try
{
if (newTransaction)
{
txn = fTxnService.getNonPropagatingUserTransaction();
}
else
{ {
txn = fTxnService.getUserTransaction(readOnly); txn = fTxnService.getUserTransaction(readOnly);
}
// Do we need to handle transaction demarcation. If // Do we need to handle transaction demarcation. If
// no, we cannot do retries, that will be up to the containing // no, we cannot do retries, that will be up to the containing
// transaction. // transaction.
@@ -106,7 +198,7 @@ public class RetryingTransactionHelper
txn.begin(); txn.begin();
} }
// Do the work. // Do the work.
Object result = cb.execute(); R result = cb.execute();
// Only commit if we 'own' the transaction. // Only commit if we 'own' the transaction.
if (isNew) if (isNew)
{ {
@@ -160,17 +252,10 @@ public class RetryingTransactionHelper
} }
lastException = (e instanceof RuntimeException) ? lastException = (e instanceof RuntimeException) ?
(RuntimeException)e : new AlfrescoRuntimeException("Unknown Exception in Transaction.", e); (RuntimeException)e : new AlfrescoRuntimeException("Unknown Exception in Transaction.", e);
Throwable t = e; // Check if there is a cause for retrying
boolean shouldRetry = false; Throwable retryCause = ExceptionStackUtil.getCause(e, RETRY_EXCEPTIONS);
while (t != null) 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)
{
shouldRetry = true;
// Sleep a random amount of time before retrying. // Sleep a random amount of time before retrying.
// The sleep interval increases with the number of retries. // The sleep interval increases with the number of retries.
try try
@@ -181,24 +266,14 @@ public class RetryingTransactionHelper
{ {
// Do nothing. // Do nothing.
} }
break; // Try again
}
if (t == t.getCause())
{
break;
}
t = t.getCause();
}
if (shouldRetry)
{
continue; continue;
} }
// It was a 'bad' exception. else
if (e instanceof RuntimeException)
{ {
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. // 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. * Class containing transactions helper methods and interfaces.
* *
* @author Roy Wetherall * @deprecated Use a {@link RetryingTransactionHelper} instance
*
* @author Derek Hulley
*/ */
public class TransactionUtil public class TransactionUtil
{ {
@@ -48,6 +50,9 @@ public class TransactionUtil
* <p> * <p>
* This interface encapsulates a unit of work that should be done within a * This interface encapsulates a unit of work that should be done within a
* transaction. * transaction.
*
* @deprecated
* @see RetryingTransactionHelper.RetryingTransactionCallback
*/ */
public interface TransactionWork<Result> public interface TransactionWork<Result>
{ {
@@ -74,6 +79,8 @@ public class TransactionUtil
* @param transactionWork the transaction work * @param transactionWork the transaction work
* *
* @throws java.lang.RuntimeException if the transaction was rolled back * @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/ */
public static <R> R executeInUserTransaction( public static <R> R executeInUserTransaction(
TransactionService transactionService, TransactionService transactionService,
@@ -91,6 +98,8 @@ public class TransactionUtil
* @param readOnly true if the transaction should be read-only * @param readOnly true if the transaction should be read-only
* *
* @throws java.lang.RuntimeException if the transaction was rolled back * @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/ */
public static <R> R executeInUserTransaction( public static <R> R executeInUserTransaction(
TransactionService transactionService, TransactionService transactionService,
@@ -108,6 +117,8 @@ public class TransactionUtil
* @param transactionWork the transaction work * @param transactionWork the transaction work
* *
* @throws java.lang.RuntimeException if the transaction was rolled back * @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/ */
public static <R> R executeInNonPropagatingUserTransaction( public static <R> R executeInNonPropagatingUserTransaction(
TransactionService transactionService, TransactionService transactionService,
@@ -125,6 +136,8 @@ public class TransactionUtil
* @param readOnly true if the transaction should be read-only * @param readOnly true if the transaction should be read-only
* *
* @throws java.lang.RuntimeException if the transaction was rolled back * @throws java.lang.RuntimeException if the transaction was rolled back
*
* @deprecated Use a {@link RetryingTransactionHelper} instance
*/ */
public static <R> R executeInNonPropagatingUserTransaction( public static <R> R executeInNonPropagatingUserTransaction(
TransactionService transactionService, TransactionService transactionService,