ALF-5028 - More tag scope updates and unit tests. Shortly after the system is started, check for un-applied tag scope updates, and apply them.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22997 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Nick Burch
2010-10-08 15:47:36 +00:00
parent c2fbd5d8d6
commit 4ff69f599a
4 changed files with 516 additions and 52 deletions

View File

@@ -26,44 +26,55 @@ import java.util.Map;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.jscript.ClasspathScriptLocation;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.audit.AuditService;
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.ScriptLocation;
import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.tagging.TagDetails;
import org.alfresco.service.cmr.tagging.TagScope;
import org.alfresco.service.cmr.tagging.TaggingService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.GUID;
import org.springframework.context.ConfigurableApplicationContext;
/**
* Tagging service implementation unit test
*
* @author Roy Wetherall
* @author Nick Burch
*/
public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
public class TaggingServiceImplTest extends TestCase
{
private static ConfigurableApplicationContext ctx =
(ConfigurableApplicationContext)ApplicationContextHelper.getApplicationContext();
/** Services */
private TaggingService taggingService;
private NodeService nodeService;
private CopyService copyService;
private CheckOutCheckInService checkOutCheckInService;
private ScriptService scriptService;
private PolicyComponent policyComponent;
private AuditService auditService;
private ActionService actionService;
private ActionTrackingService actionTrackingService;
private TransactionService transactionService;
private AuthenticationComponent authenticationComponent;
private static StoreRef storeRef;
private static NodeRef rootNode;
@@ -84,32 +95,28 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
private static boolean init = false;
@Override
protected void onSetUpBeforeTransaction() throws Exception
protected void setUp() throws Exception
{
super.onSetUpBeforeTransaction();
// Get services
this.taggingService = (TaggingService)this.applicationContext.getBean("TaggingService");
this.nodeService = (NodeService) this.applicationContext.getBean("NodeService");
this.copyService = (CopyService) this.applicationContext.getBean("CopyService");
this.contentService = (ContentService) this.applicationContext.getBean("ContentService");
this.checkOutCheckInService = (CheckOutCheckInService) this.applicationContext.getBean("CheckoutCheckinService");
this.authenticationService = (MutableAuthenticationService) this.applicationContext.getBean("authenticationService");
this.actionService = (ActionService)this.applicationContext.getBean("ActionService");
this.transactionService = (TransactionService)this.applicationContext.getBean("transactionComponent");
this.scriptService = (ScriptService)this.applicationContext.getBean("scriptService");
this.policyComponent = (PolicyComponent)this.applicationContext.getBean("policyComponent");
this.actionTrackingService = (ActionTrackingService)this.applicationContext.getBean("actionTrackingService");
this.taggingService = (TaggingService)ctx.getBean("TaggingService");
this.nodeService = (NodeService) ctx.getBean("NodeService");
this.copyService = (CopyService) ctx.getBean("CopyService");
this.checkOutCheckInService = (CheckOutCheckInService) ctx.getBean("CheckoutCheckinService");
this.actionService = (ActionService)ctx.getBean("ActionService");
this.transactionService = (TransactionService)ctx.getBean("transactionComponent");
this.auditService = (AuditService)ctx.getBean("auditService");
this.scriptService = (ScriptService)ctx.getBean("scriptService");
this.actionTrackingService = (ActionTrackingService)ctx.getBean("actionTrackingService");
this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent");
if (init == false)
{
UserTransaction tx = this.transactionService.getUserTransaction();
UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction();
tx.begin();
// Authenticate as the system user
AuthenticationComponent authenticationComponent = (AuthenticationComponent) this.applicationContext
.getBean("authenticationComponent");
authenticationComponent.setSystemUserAsCurrentUser();
// Create the store and get the root node
@@ -133,14 +140,22 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
tx.commit();
}
// Create the folders and documents to be tagged
createTestDocumentsAndFolders();
}
@Override
protected void onSetUpInTransaction() throws Exception
protected void tearDown() throws Exception {
removeTestDocumentsAndFolders();
}
private void createTestDocumentsAndFolders() throws Exception
{
UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction();
tx.begin();
// Authenticate as the system user
AuthenticationComponent authenticationComponent = (AuthenticationComponent) this.applicationContext
.getBean("authenticationComponent");
authenticationComponent.setSystemUserAsCurrentUser();
String guid = GUID.generate();
@@ -183,9 +198,40 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
ContentModel.TYPE_CONTENT,
props).getChildRef();
//tx.commit();
setComplete();
endTransaction();
tx.commit();
}
private void removeTestDocumentsAndFolders() throws Exception
{
UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction();
tx.begin();
// Authenticate as the system user
authenticationComponent.setSystemUserAsCurrentUser();
// If anything is a tag scope, stop it being
NodeRef[] nodes = new NodeRef[] { subDocument, subFolder, document, folder };
for(NodeRef nodeRef : nodes)
{
if(taggingService.isTagScope(nodeRef))
{
taggingService.removeTagScope(nodeRef);
}
}
// Remove the sample nodes
for(NodeRef nodeRef : nodes)
{
nodeService.deleteNode(nodeRef);
}
// Tidy up the audit component, now all the nodes have gone
auditService.clearAudit(
TaggingServiceImpl.TAGGING_AUDIT_APPLICATION_NAME,
0l, System.currentTimeMillis()+1
);
// All done
tx.commit();
}
public void testTagCRUD()
@@ -203,10 +249,7 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
this.taggingService.createTag(TaggingServiceImplTest.storeRef, TAG_1);
this.taggingService.createTag(TaggingServiceImplTest.storeRef, UPPER_TAG);
//setComplete();
//endTransaction();
tx.commit();
tx = this.transactionService.getUserTransaction();
tx.begin();
@@ -905,7 +948,7 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
UserTransaction tx = this.transactionService.getUserTransaction();
tx.begin();
Map model = new HashMap<String, Object>(0);
Map<String, Object> model = new HashMap<String, Object>(0);
model.put("folder", this.folder);
model.put("subFolder", this.subFolder);
model.put("document", this.document);
@@ -915,6 +958,8 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/tagging/script/test_taggingService.js");
this.scriptService.executeScript(location, model);
// Let the script run
tx = waitForActionExecution(tx);
tx.commit();
}
@@ -950,9 +995,8 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
tx = waitForActionExecution(tx);
this.taggingService.addTag(this.folder, TAG_1);
tx = waitForActionExecution(tx);
tx.commit();
Map model = new HashMap<String, Object>(0);
Map<String, Object> model = new HashMap<String, Object>(0);
model.put("folder", this.folder);
model.put("subFolder", this.subFolder);
model.put("document", this.document);
@@ -962,9 +1006,197 @@ public class TaggingServiceImplTest extends BaseAlfrescoSpringTest
ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/tagging/script/test_taggingService.js");
this.scriptService.executeScript(location, model);
// Let the script run
tx = waitForActionExecution(tx);
tx.commit();
}
private static Object mutex = new Object();
/**
* Test that the scheduled task will do the right thing
* when it runs.
*/
public void testOnStartupJob() throws Exception
{
UserTransaction tx = this.transactionService.getUserTransaction();
tx.begin();
// Nothing is pending to start with
UpdateTagScopesActionExecuter updateTagsAction = (UpdateTagScopesActionExecuter)
ctx.getBean("update-tagscope");
assertEquals(0, updateTagsAction.searchForTagScopesPendingUpdates().size());
// Take the tag scope lock, so that no real updates will happen
String lockF = updateTagsAction.lockTagScope(this.folder);
String lockSF = updateTagsAction.lockTagScope(this.subFolder);
// Do some tagging
this.taggingService.addTagScope(this.folder);
this.taggingService.addTagScope(this.subFolder);
this.taggingService.addTag(this.subDocument, TAG_1);
this.taggingService.addTag(this.subDocument, TAG_2);
this.taggingService.addTag(this.subFolder, TAG_1);
this.taggingService.addTag(this.document, TAG_1);
this.taggingService.addTag(this.folder, TAG_1);
this.taggingService.addTag(this.folder, TAG_3);
tx = waitForActionExecution(tx);
// Tag scope updates shouldn't have happened yet,
// as the scopes are locked
TagScope ts1 = this.taggingService.findTagScope(this.folder);
TagScope ts2 = this.taggingService.findTagScope(this.subFolder);
assertEquals(
"Wrong tags on folder tagscope: " + ts1.getTags(),
0, ts1.getTags().size()
);
assertEquals(
"Wrong tags on folder tagscope: " + ts1.getTags(),
0, ts2.getTags().size()
);
// Check the pending list now
assertEquals(2, updateTagsAction.searchForTagScopesPendingUpdates().size());
List<NodeRef> pendingScopes = updateTagsAction.searchForTagScopesPendingUpdates();
assertTrue("Not found in " + pendingScopes, pendingScopes.contains(this.folder));
assertTrue("Not found in " + pendingScopes, pendingScopes.contains(this.subFolder));
// Give back our locks, so we can proceed
updateTagsAction.unlockTagScope(this.folder, lockF);
updateTagsAction.unlockTagScope(this.subFolder, lockSF);
// Fire off the quartz bean
UpdateTagScopesQuartzJob job = new UpdateTagScopesQuartzJob();
job.execute(actionService, updateTagsAction);
// Now check again
assertEquals(0, updateTagsAction.searchForTagScopesPendingUpdates().size());
ts1 = this.taggingService.findTagScope(this.folder);
ts2 = this.taggingService.findTagScope(this.subFolder);
assertEquals(
"Wrong tags on folder tagscope: " + ts1.getTags(),
3, ts1.getTags().size()
);
assertEquals(
"Wrong tags on folder tagscope: " + ts1.getTags(),
2, ts2.getTags().size()
);
assertEquals(4, ts1.getTag(TAG_1).getCount());
assertEquals(1, ts1.getTag(TAG_2).getCount());
assertEquals(1, ts1.getTag(TAG_3.toLowerCase()).getCount());
assertEquals(2, ts2.getTag(TAG_1).getCount());
assertEquals(1, ts2.getTag(TAG_2).getCount());
}
/**
* Test that when multiple threads do tag updates, the right
* thing still happens
*/
public void DISABLEDtestMultiThreaded() throws Exception
{
UserTransaction tx = this.transactionService.getNonPropagatingUserTransaction();
tx.begin();
this.taggingService.addTagScope(this.folder);
this.taggingService.addTagScope(this.subFolder);
tx.commit();
// Prepare a bunch of threads to do tagging
final List<Thread> threads = new ArrayList<Thread>();
String[] tags = new String[] { TAG_1, TAG_2, TAG_3, TAG_4, TAG_5 };
for(String tmpTag : tags)
{
final String tag = tmpTag;
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// Let everything catch up
try {
Thread.sleep(250);
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread() + " - About to start tagging");
// Do the updates
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable {
taggingService.addTag(folder, tag);
taggingService.addTag(subFolder, tag);
taggingService.addTag(subDocument, tag);
System.out.println(Thread.currentThread() + " - Tagging");
return null;
}
}, false, true
);
System.out.println(Thread.currentThread() + " - Done tagging");
}
});
threads.add(t);
t.start();
}
// Release the threads
Thread.sleep(50);
for(Thread t : threads) {
t.interrupt();
}
Thread.sleep(100);
System.out.println("Done waiting, proceeding with multi-threaded test");
// Wait for all the threads to finish working
try {
// Wait for a maximum of 10 seconds
for(int i=0; i<1000; i++)
{
if(actionTrackingService.getAllExecutingActions().size() > 0)
{
Thread.sleep(10);
}
else {
break;
}
}
} catch(InterruptedException e) {}
// Now check that things ended up as planned
tx = this.transactionService.getUserTransaction();
tx.begin();
TagScope ts1 = this.taggingService.findTagScope(this.folder);
TagScope ts2 = this.taggingService.findTagScope(this.subFolder);
assertEquals(
"Wrong tags on folder tagscope: " + ts1.getTags(),
5, ts1.getTags().size()
);
assertEquals(
"Wrong tags on folder tagscope: " + ts1.getTags(),
5, ts2.getTags().size()
);
// Each tag should crop up 3 times on the folder
// and twice for the subfolder
for(String tag : tags)
{
assertEquals(3, ts1.getTag(tag.toLowerCase()).getCount());
assertEquals(2, ts2.getTag(tag.toLowerCase()).getCount());
}
// All done
tx.commit();
}
private UserTransaction waitForActionExecution(UserTransaction txn)
throws Exception

View File

@@ -22,8 +22,10 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.action.ParameterDefinitionImpl;
@@ -87,7 +89,16 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase
/** What's the largest number of updates we should claim for a tag scope in one transaction? */
private static final int tagUpdateBatchSize = 100;
// For searching
private static final String noderefPath =
TaggingServiceImpl.TAGGING_AUDIT_ROOT_PATH + "/" +
TaggingServiceImpl.TAGGING_AUDIT_KEY_NODEREF + "/value";
private static final String tagsPath =
TaggingServiceImpl.TAGGING_AUDIT_ROOT_PATH + "/" +
TaggingServiceImpl.TAGGING_AUDIT_KEY_TAGS + "/value";
/**
* Set the node service
*
@@ -174,10 +185,7 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase
// to worry as they'll handle the update for us!
try
{
QName lockQName = QName.createQName("TagScope_" + tagScope.toString());
String lock = jobLockService.getLock(
lockQName, 2500, 0, 0
);
String lock = lockTagScope(tagScope);
// If we got here, we're the only thread currently
// processing this tag scope
@@ -236,7 +244,7 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase
);
// We're done with this tag scope
jobLockService.releaseLock(lock, lockQName);
unlockTagScope(tagScope, lock);
} catch(LockAcquisitionException e) {}
// Now proceed to the next tag scope
@@ -257,19 +265,13 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase
*/
private List<Long> searchForUpdates(final NodeRef tagScopeNode, final Map<String,Integer> updates)
{
final String noderefPath =
TaggingServiceImpl.TAGGING_AUDIT_ROOT_PATH + "/" +
TaggingServiceImpl.TAGGING_AUDIT_KEY_NODEREF + "/value";
final String tagsPath =
TaggingServiceImpl.TAGGING_AUDIT_ROOT_PATH + "/" +
TaggingServiceImpl.TAGGING_AUDIT_KEY_TAGS + "/value";
// Build the query
AuditQueryParameters params = new AuditQueryParameters();
final AuditQueryParameters params = new AuditQueryParameters();
params.setApplicationName(TaggingServiceImpl.TAGGING_AUDIT_APPLICATION_NAME);
params.addSearchKey(noderefPath, tagScopeNode.toString());
// Execute it
// Execute the query, in a new transaction
// (Avoid contention issues with repeated runs / updates)
final List<Long> ids = new ArrayList<Long>();
auditService.auditQuery(new AuditQueryCallback() {
@Override
@@ -412,6 +414,92 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase
contentWriter.putContent(tagContent);
}
}
/**
* Checks several batches of updates in the Audit event log,
* and returns the list of Tag Scope Node References found there.
* You should generally call this action to process the list of
* tag nodes before re-calling this method.
* You may need to call this method several times if lots of work
* is backed up, when an empty list is returned then you know
* that all work is handled.
*/
public List<NodeRef> searchForTagScopesPendingUpdates()
{
final Set<String> tagNodesStrs = new HashSet<String>();
// We want all entries for tagging
final AuditQueryParameters params = new AuditQueryParameters();
params.setApplicationName(TaggingServiceImpl.TAGGING_AUDIT_APPLICATION_NAME);
// Execute the query, in a new transaction
// (Avoid contention issues with repeated runs / updates)
transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable {
auditService.auditQuery(new AuditQueryCallback() {
@Override
public boolean valuesRequired() {
return true;
}
@Override
public boolean handleAuditEntryError(Long entryId, String errorMsg,
Throwable error) {
logger.warn("Error fetching tagging update entry - " + errorMsg, error);
// Keep trying
return true;
}
@Override
public boolean handleAuditEntry(Long entryId, String applicationName,
String user, long time, Map<String, Serializable> values) {
// Save the NodeRef
if(values.containsKey(noderefPath))
{
String nodeRefStr = (String)values.get(noderefPath);
if(! tagNodesStrs.contains(nodeRefStr))
tagNodesStrs.add(nodeRefStr);
}
else
{
logger.warn("Unexpected Tag Scope update entry of " + values);
}
// Next entry please!
return true;
}
}, params, 4*tagUpdateBatchSize);
return null;
}
}, false, true
);
// Turn it into NodeRefs
List<NodeRef> tagNodes = new ArrayList<NodeRef>();
for(String nodeRefStr : tagNodesStrs)
{
tagNodes.add( new NodeRef(nodeRefStr) );
}
return tagNodes;
}
private QName tagScopeToLockQName(NodeRef tagScope)
{
QName lockQName = QName.createQName("TagScope_" + tagScope.toString());
return lockQName;
}
protected String lockTagScope(NodeRef tagScope)
{
String lock = jobLockService.getLock(
tagScopeToLockQName(tagScope), 2500, 0, 0
);
return lock;
}
protected void unlockTagScope(NodeRef tagScope, String lockToken)
{
jobLockService.releaseLock(lockToken, tagScopeToLockQName(tagScope));
}
/**
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
@@ -421,5 +509,4 @@ public class UpdateTagScopesActionExecuter extends ActionExecuterAbstractBase
{
paramList.add(new ParameterDefinitionImpl(PARAM_TAG_SCOPES, DataTypeDefinition.ANY, true, getParamDisplayLabel(PARAM_TAG_SCOPES)));
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.tagging;
import java.io.Serializable;
import java.util.ArrayList;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* Finds Tag Scope updates that haven't been applied, and triggers
* then to be run.
* Works with the {@link UpdateTagScopesActionExecuter}, and is typically
* run after server restart.
* Also refer to scheduled-jobs-context.xml for more information
*/
public class UpdateTagScopesQuartzJob implements Job {
private static Log logger = LogFactory.getLog(UpdateTagScopesQuartzJob.class);
public UpdateTagScopesQuartzJob() {}
/**
* Finds the tag scopes to be updated, and has them worked on
*/
public void execute(JobExecutionContext context) throws JobExecutionException
{
JobDataMap jobData = context.getJobDetail().getJobDataMap();
// Extract out our beans
Object actionServiceO = jobData.get("actionService");
if(actionServiceO == null || !(actionServiceO instanceof ActionService))
{
throw new AlfrescoRuntimeException(
"UpdateTagScopesQuartzJob data must contain a valid 'actionService' reference");
}
Object updateTagsActionO = jobData.get("updateTagsAction");
if(updateTagsActionO == null || !(updateTagsActionO instanceof UpdateTagScopesActionExecuter))
{
throw new AlfrescoRuntimeException(
"UpdateTagScopesQuartzJob data must contain a valid 'updateTagsAction' reference");
}
ActionService actionService = (ActionService)actionServiceO;
UpdateTagScopesActionExecuter updateTagsAction = (UpdateTagScopesActionExecuter)updateTagsActionO;
// Do the work
execute(actionService, updateTagsAction);
}
protected void execute(final ActionService actionService, final UpdateTagScopesActionExecuter updateTagsAction)
{
// Process
final ArrayList<NodeRef> tagNodes = new ArrayList<NodeRef>();
while(true)
{
// Fetch the list of changes
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Void>()
{
public Void doWork() throws Exception
{
tagNodes.clear();
tagNodes.addAll(
updateTagsAction.searchForTagScopesPendingUpdates()
);
return null;
}
}, AuthenticationUtil.getSystemUserName()
);
// Log what we found
if(logger.isDebugEnabled())
{
logger.debug("Checked for tag scopes with pending tag updates, found " + tagNodes);
}
if(tagNodes.size() == 0)
break;
// Have the action run for these tag scope nodes
// Needs to run synchronously
Action action = actionService.createAction(UpdateTagScopesActionExecuter.NAME);
action.setParameterValue(UpdateTagScopesActionExecuter.PARAM_TAG_SCOPES, (Serializable)tagNodes);
actionService.executeAction(action, null, false, false);
}
}
}