use of org.alfresco.repo.batch.BatchProcessor in project alfresco-repository by Alfresco.
the class RenameUser method execute.
/*
* (non-Javadoc)
* @see org.alfresco.tools.Tool#execute()
*/
@Override
protected int execute() throws ToolException {
// Used for ability to be final and have a set
final AtomicInteger status = new AtomicInteger(0);
BatchProcessWorker<User> worker = new BatchProcessWorkerAdaptor<User>() {
public void process(final User user) throws Throwable {
RunAsWork<Void> runAsWork = new RunAsWork<Void>() {
@Override
public Void doWork() throws Exception {
try {
renameUser(user.getOldUsername(), user.getNewUsername());
} catch (Throwable t) {
status.set(handleError(t));
}
return null;
}
};
AuthenticationUtil.runAs(runAsWork, context.getUsername());
}
};
// Use 2 threads, 20 User objects per transaction. Log every 100 entries.
BatchProcessor<User> processor = new BatchProcessor<User>("HomeFolderProviderSynchronizer", getServiceRegistry().getTransactionService().getRetryingTransactionHelper(), new WorkProvider(context), 2, 20, null, logger, 100);
processor.process(worker, true);
return status.get();
}
use of org.alfresco.repo.batch.BatchProcessor in project alfresco-repository by Alfresco.
the class HomeFolderProviderSynchronizer method scanPeople.
/**
* Scans all {@code person} people objects and checks their home folder is located according
* to the person's home folder provider preferred default location.
* @param systemUserName String the system user name with the tenant-specific ID attached.
* @param tenantDomain String name of the tenant domain. Used to restrict the which people
* are processed.
* @param overrideProvider String the bean name of a HomeFolderProvider to be used
* in place of the all home folders existing providers. If {@code null}
* the existing provider is used.
*/
private void scanPeople(final String systemUserName, final String tenantDomain, final String overrideProvider) {
/*
* To avoid problems with existing home folders that are located in locations
* that will be needed by 'parent' folders, we need a 4 phase process.
* Consider the following user names and required structure. There would be a
* problem with the username 'ab'.
*
* abc --> ab/abc
* def /abd
* abd /ab
* ab de/def
*
* 1. Record which parent folders are needed
* 2. Move any home folders which overlap with parent folders to a temporary folder
* 3. Create parent folder structure. Done in a single thread before the move of
* home folders to avoid race conditions
* 4. Move home folders if required
*
* Alternative approaches are possible, but the above has the advantage that
* nodes are not moved if they are already in their preferred location.
*
* Also needed to change the case of parent folders.
*/
// Using authorities rather than Person objects as they are much lighter
final Set<String> authorities = getAllAuthoritiesInTxn(systemUserName);
final ParentFolderStructure parentFolderStructure = new ParentFolderStructure();
final Map<NodeRef, String> tmpFolders = new HashMap<NodeRef, String>();
// Define the phases
final String createParentFoldersPhaseName = "createParentFolders";
final String moveFolderThatClashesPhaseName = "moveHomeFolderThatClashesWithParentFolderStructure";
RunAsWorker[] workers = new RunAsWorker[] { new RunAsWorker(systemUserName, tenantDomain, "calculateParentFolderStructure") {
@Override
public void doWork(NodeRef person) throws Exception {
calculateParentFolderStructure(parentFolderStructure, person, overrideProvider);
}
}, new RunAsWorker(systemUserName, tenantDomain, moveFolderThatClashesPhaseName) {
@Override
public void doWork(NodeRef person) throws Exception {
moveHomeFolderThatClashesWithParentFolderStructure(parentFolderStructure, tmpFolders, person, overrideProvider);
}
}, new RunAsWorker(systemUserName, tenantDomain, createParentFoldersPhaseName) {
@Override
public void doWork(NodeRef person) throws Exception {
createParentFolders(person, overrideProvider);
}
}, new RunAsWorker(systemUserName, tenantDomain, "moveHomeFolderIfRequired") {
@Override
public void doWork(NodeRef person) throws Exception {
moveHomeFolderIfRequired(person, overrideProvider);
}
} };
// Run the phases
for (RunAsWorker worker : workers) {
String name = worker.getName();
if (logger.isInfoEnabled()) {
logger.info(" -- " + (TenantService.DEFAULT_DOMAIN.equals(tenantDomain) ? "" : tenantDomain + " ") + name + " --");
}
int threadCount = (name.equals(createParentFoldersPhaseName) || name.equals(moveFolderThatClashesPhaseName)) ? 1 : 2;
int peoplePerTransaction = 20;
// Use 2 threads, 20 person objects per transaction. Log every 100 entries.
BatchProcessor<NodeRef> processor = new BatchProcessor<NodeRef>("HomeFolderProviderSynchronizer", transactionService.getRetryingTransactionHelper(), new WorkProvider(authorities), threadCount, peoplePerTransaction, null, batchLogger, 100);
processor.process(worker, true);
if (processor.getTotalErrors() > 0) {
logger.info(" -- Give up after error --");
break;
}
}
}
use of org.alfresco.repo.batch.BatchProcessor in project alfresco-repository by Alfresco.
the class ChainingUserRegistrySynchronizer method syncWithPlugin.
/**
* Synchronizes local groups and users with a {@link UserRegistry} for a particular zone, optionally handling
* deletions.
*
* @param zone
* the zone id. This identifier is used to tag all created groups and users, so that in the future we can
* tell those that have been deleted from the registry.
* @param userRegistry
* the user registry for the zone.
* @param forceUpdate
* Should the complete set of users and groups be updated / created locally or just those known to have
* changed since the last sync? When <code>true</code> then <i>all</i> users and groups are queried from
* the user registry and updated locally. When <code>false</code> then each source is only queried for
* those users and groups modified since the most recent modification date of all the objects last
* queried from that same source.
* @param isFullSync
* Should a complete set of user and group IDs be queried from the user registries in order to determine
* deletions? This parameter is independent of <code>force</code> as a separate query is run to process
* updates.
* @param splitTxns
* Can the modifications to Alfresco be split across multiple transactions for maximum performance? If
* <code>true</code>, users and groups are created/updated in batches for increased performance. If
* <code>false</code>, all users and groups are processed in the current transaction. This is required if
* calling synchronously (e.g. in response to an authentication event in the same transaction).
* @param visitedZoneIds
* the set of zone ids already processed. These zones have precedence over the current zone when it comes
* to group name 'collisions'. If a user or group is queried that already exists locally but is tagged
* with one of the zones in this set, then it will be ignored as this zone has lower priority.
* @param allZoneIds
* the set of all zone ids in the authentication chain. Helps us work out whether the zone information
* recorded against a user or group is invalid for the current authentication chain and whether the user
* or group needs to be 're-zoned'.
*/
private void syncWithPlugin(final String zone, UserRegistry userRegistry, boolean forceUpdate, boolean isFullSync, boolean splitTxns, final Set<String> visitedZoneIds, final Set<String> allZoneIds) {
// Create a prefixed zone ID for use with the authority service
final String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + zone;
// Batch Process Names
final String[] reservedBatchProcessNames = { SyncProcess.GROUP_ANALYSIS.getTitle(zone), SyncProcess.USER_CREATION.getTitle(zone), SyncProcess.MISSING_AUTHORITY.getTitle(zone), SyncProcess.GROUP_CREATION_AND_ASSOCIATION_DELETION.getTitle(zone), SyncProcess.GROUP_ASSOCIATION_CREATION.getTitle(zone), SyncProcess.PERSON_ASSOCIATION.getTitle(zone), SyncProcess.AUTHORITY_DELETION.getTitle(zone) };
notifySyncDirectoryStart(zone, reservedBatchProcessNames);
// Ensure that the zoneId exists before multiple threads start using it
this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable {
authorityService.getOrCreateZone(zoneId);
return null;
}
}, false, splitTxns);
// The set of zones we associate with new objects (default plus registry specific)
final Set<String> zoneSet = getZones(zoneId);
long lastModifiedMillis = forceUpdate ? -1 : getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns);
Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) {
if (lastModified == null) {
ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zone + "'");
} else {
ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since " + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'");
}
}
// First, analyze the group structure. Create maps of authorities to their parents for associations to create
// and delete. Also deal with 'overlaps' with other zones in the authentication chain.
final BatchProcessor<NodeDescription> groupProcessor = new BatchProcessor<NodeDescription>(SyncProcess.GROUP_ANALYSIS.getTitle(zone), this.transactionService.getRetryingTransactionHelper(), userRegistry.getGroups(lastModified), this.workerThreads, 20, this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, this.loggingInterval);
class Analyzer extends BaseBatchProcessWorker<NodeDescription> {
private final Map<String, String> groupsToCreate = new TreeMap<String, String>();
private final Map<String, Set<String>> personParentAssocsToCreate = newPersonMap();
private final Map<String, Set<String>> personParentAssocsToDelete = newPersonMap();
private Map<String, Set<String>> groupParentAssocsToCreate = new TreeMap<String, Set<String>>();
private final Map<String, Set<String>> groupParentAssocsToDelete = new TreeMap<String, Set<String>>();
private final Map<String, Set<String>> finalGroupChildAssocs = new TreeMap<String, Set<String>>();
private List<String> personsProcessed = new LinkedList<String>();
private Set<String> allZonePersons = Collections.emptySet();
private Set<String> deletionCandidates;
private long latestTime;
public Analyzer(final long latestTime) {
this.latestTime = latestTime;
}
public long getLatestTime() {
return this.latestTime;
}
public Set<String> getDeletionCandidates() {
return this.deletionCandidates;
}
public String getIdentifier(NodeDescription entry) {
return entry.getSourceId();
}
public void process(NodeDescription group) throws Throwable {
PropertyMap groupProperties = group.getProperties();
String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME);
String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName);
Set<String> groupZones = ChainingUserRegistrySynchronizer.this.authorityService.getAuthorityZones(groupName);
if (groupZones == null) {
// The group did not exist at all
updateGroup(group, false);
} else {
// Check whether the group is in any of the authentication chain zones
Set<String> intersection = new TreeSet<String>(groupZones);
intersection.retainAll(allZoneIds);
// Check whether the group is in any of the higher priority authentication chain zones
Set<String> visited = new TreeSet<String>(intersection);
visited.retainAll(visitedZoneIds);
if (groupZones.contains(zoneId)) {
// The group already existed in this zone: update the group
updateGroup(group, true);
} else if (!visited.isEmpty()) {
// A group that exists in a different zone with higher precedence
return;
} else if (!allowDeletions || intersection.isEmpty()) {
// chain. May be due to upgrade or zone changes. Let's re-zone them
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) {
ChainingUserRegistrySynchronizer.logger.warn("Updating group '" + groupShortName + "'. This group will in future be assumed to originate from user registry '" + zone + "'.");
}
updateAuthorityZones(groupName, groupZones, zoneSet);
// The group now exists in this zone: update the group
updateGroup(group, true);
} else {
// The group existed, but in a zone with lower precedence
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) {
ChainingUserRegistrySynchronizer.logger.warn("Recreating occluded group '" + groupShortName + "'. This group was previously created through synchronization with a lower priority user registry.");
}
ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(groupName);
// create the group
updateGroup(group, false);
}
}
synchronized (this) {
// Maintain the last modified date
Date groupLastModified = group.getLastModified();
if (groupLastModified != null) {
this.latestTime = Math.max(this.latestTime, groupLastModified.getTime());
}
}
}
// Recursively walks and caches the authorities relating to and from this group so that we can later detect potential cycles
private Set<String> getContainedAuthorities(String groupName) {
// Return the cached children if it is processed
Set<String> children = this.finalGroupChildAssocs.get(groupName);
if (children != null) {
return children;
}
// First, recurse to the parent most authorities
for (String parent : ChainingUserRegistrySynchronizer.this.authorityService.getContainingAuthorities(null, groupName, true)) {
getContainedAuthorities(parent);
}
// Now descend on unprocessed parents.
return cacheContainedAuthorities(groupName);
}
private Set<String> cacheContainedAuthorities(String groupName) {
// Return the cached children if it is processed
Set<String> children = this.finalGroupChildAssocs.get(groupName);
if (children != null) {
return children;
}
// Descend on unprocessed parents.
children = ChainingUserRegistrySynchronizer.this.authorityService.getContainedAuthorities(null, groupName, true);
this.finalGroupChildAssocs.put(groupName, children);
for (String child : children) {
if (AuthorityType.getAuthorityType(child) != AuthorityType.USER) {
cacheContainedAuthorities(child);
}
}
return children;
}
private synchronized void updateGroup(NodeDescription group, boolean existed) {
PropertyMap groupProperties = group.getProperties();
String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME);
String groupDisplayName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME);
if (groupDisplayName == null) {
groupDisplayName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName);
}
// Divide the child associations into person and group associations, dealing with case sensitivity
Set<String> newChildPersons = newPersonSet();
Set<String> newChildGroups = new TreeSet<String>();
for (String child : group.getChildAssociations()) {
if (AuthorityType.getAuthorityType(child) == AuthorityType.USER) {
newChildPersons.add(child);
} else {
newChildGroups.add(child);
}
}
// Account for differences if already existing
if (existed) {
// Update the display name now
ChainingUserRegistrySynchronizer.this.authorityService.setAuthorityDisplayName(groupName, groupDisplayName);
// Work out the association differences
for (String child : new TreeSet<String>(getContainedAuthorities(groupName))) {
if (AuthorityType.getAuthorityType(child) == AuthorityType.USER) {
if (!newChildPersons.remove(child)) {
recordParentAssociationDeletion(child, groupName);
}
} else {
if (!newChildGroups.remove(child)) {
recordParentAssociationDeletion(child, groupName);
}
}
}
} else // Mark as created if new
{
// Make sure each group to be created features in the association deletion map (as these are handled in the same phase)
recordParentAssociationDeletion(groupName, null);
this.groupsToCreate.put(groupName, groupDisplayName);
}
// Create new associations
for (String child : newChildPersons) {
// Make sure each person with association changes features as a key in the deletion map
recordParentAssociationDeletion(child, null);
recordParentAssociationCreation(child, groupName);
}
for (String child : newChildGroups) {
// Make sure each group with association changes features as a key in the deletion map
recordParentAssociationDeletion(child, null);
recordParentAssociationCreation(child, groupName);
}
}
private void recordParentAssociationDeletion(String child, String parent) {
Map<String, Set<String>> parentAssocs;
if (AuthorityType.getAuthorityType(child) == AuthorityType.USER) {
parentAssocs = this.personParentAssocsToDelete;
} else {
// Reflect the change in the map of final group associations (for cycle detection later)
parentAssocs = this.groupParentAssocsToDelete;
if (parent != null) {
Set<String> children = this.finalGroupChildAssocs.get(parent);
children.remove(child);
}
}
Set<String> parents = parentAssocs.get(child);
if (parents == null) {
parents = new TreeSet<String>();
parentAssocs.put(child, parents);
}
if (parent != null) {
parents.add(parent);
}
}
private void recordParentAssociationCreation(String child, String parent) {
Map<String, Set<String>> parentAssocs = AuthorityType.getAuthorityType(child) == AuthorityType.USER ? this.personParentAssocsToCreate : this.groupParentAssocsToCreate;
Set<String> parents = parentAssocs.get(child);
if (parents == null) {
parents = new TreeSet<String>();
parentAssocs.put(child, parents);
}
if (parent != null) {
parents.add(parent);
}
}
private void validateGroupParentAssocsToCreate() {
Iterator<Map.Entry<String, Set<String>>> i = this.groupParentAssocsToCreate.entrySet().iterator();
while (i.hasNext()) {
Map.Entry<String, Set<String>> entry = i.next();
String group = entry.getKey();
Set<String> parents = entry.getValue();
Deque<String> visited = new LinkedList<String>();
Iterator<String> j = parents.iterator();
while (j.hasNext()) {
String parent = j.next();
visited.add(parent);
if (validateAuthorityChildren(visited, group)) {
// The association validated - commit it
Set<String> children = finalGroupChildAssocs.get(parent);
if (children == null) {
children = new TreeSet<String>();
finalGroupChildAssocs.put(parent, children);
}
children.add(group);
} else {
// The association did not validate - prune it out
if (logger.isWarnEnabled()) {
ChainingUserRegistrySynchronizer.logger.warn("Not adding group '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(group) + "' to group '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(parent) + "' as this creates a cyclic relationship");
}
j.remove();
}
visited.removeLast();
}
if (parents.isEmpty()) {
i.remove();
}
}
// Sort the group associations in parent-first order (root groups first) to minimize reindexing overhead
Map<String, Set<String>> sortedGroupAssociations = new LinkedHashMap<String, Set<String>>(this.groupParentAssocsToCreate.size() * 2);
Deque<String> visited = new LinkedList<String>();
for (String authority : this.groupParentAssocsToCreate.keySet()) {
visitGroupParentAssocs(visited, authority, this.groupParentAssocsToCreate, sortedGroupAssociations);
}
this.groupParentAssocsToCreate = sortedGroupAssociations;
}
private boolean validateAuthorityChildren(Deque<String> visited, String authority) {
if (AuthorityType.getAuthorityType(authority) == AuthorityType.USER) {
return true;
}
if (visited.contains(authority)) {
return false;
}
visited.add(authority);
try {
Set<String> children = this.finalGroupChildAssocs.get(authority);
if (children != null) {
for (String child : children) {
if (!validateAuthorityChildren(visited, child)) {
return false;
}
}
}
return true;
} finally {
visited.removeLast();
}
}
/**
* Visits the given authority by recursively visiting its parents in associationsOld and then adding the
* authority to associationsNew. Used to sort associationsOld into 'parent-first' order to minimize
* reindexing overhead.
*
* @param visited
* The ancestors that form the path to the authority to visit. Allows detection of cyclic child
* associations.
* @param authority
* the authority to visit
* @param associationsOld
* the association map to sort
* @param associationsNew
* the association map to add to in parent-first order
*/
private boolean visitGroupParentAssocs(Deque<String> visited, String authority, Map<String, Set<String>> associationsOld, Map<String, Set<String>> associationsNew) {
if (visited.contains(authority)) {
// Prevent cyclic paths (Shouldn't happen as we've already validated)
return false;
}
visited.add(authority);
try {
if (!associationsNew.containsKey(authority)) {
Set<String> oldParents = associationsOld.get(authority);
if (oldParents != null) {
Set<String> newParents = new TreeSet<String>();
for (String parent : oldParents) {
if (visitGroupParentAssocs(visited, parent, associationsOld, associationsNew)) {
newParents.add(parent);
}
}
associationsNew.put(authority, newParents);
}
}
return true;
} finally {
visited.removeLast();
}
}
private Set<String> newPersonSet() {
return ChainingUserRegistrySynchronizer.this.personService.getUserNamesAreCaseSensitive() ? new TreeSet<String>() : new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
}
private Map<String, Set<String>> newPersonMap() {
return ChainingUserRegistrySynchronizer.this.personService.getUserNamesAreCaseSensitive() ? new TreeMap<String, Set<String>>() : new TreeMap<String, Set<String>>(String.CASE_INSENSITIVE_ORDER);
}
private void logRetainParentAssociations(Map<String, Set<String>> parentAssocs, Set<String> toRetain) {
Iterator<Map.Entry<String, Set<String>>> i = parentAssocs.entrySet().iterator();
StringBuilder groupList = null;
while (i.hasNext()) {
Map.Entry<String, Set<String>> entry = i.next();
String child = entry.getKey();
if (!toRetain.contains(child)) {
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
if (groupList == null) {
groupList = new StringBuilder(1024);
} else {
groupList.setLength(0);
}
for (String parent : entry.getValue()) {
if (groupList.length() > 0) {
groupList.append(", ");
}
groupList.append('\'').append(ChainingUserRegistrySynchronizer.this.authorityService.getShortName(parent)).append('\'');
}
ChainingUserRegistrySynchronizer.logger.debug("Ignoring non-existent member '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) + "' in groups {" + groupList.toString() + "}");
}
i.remove();
}
}
}
private void processGroups(UserRegistry userRegistry, boolean isFullSync, boolean splitTxns) {
// MNT-12454 fix. If syncDelete is false, there is no need to pull all users and all groups from LDAP during the full synchronization.
if ((syncDelete || !groupsToCreate.isEmpty()) && (isFullSync || !this.groupParentAssocsToDelete.isEmpty())) {
final Set<String> allZonePersons = newPersonSet();
final Set<String> allZoneGroups = new TreeSet<String>();
// Add in current set of known authorities
ChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable {
allZonePersons.addAll(ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId, AuthorityType.USER));
allZoneGroups.addAll(ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId, AuthorityType.GROUP));
return null;
}
}, true, splitTxns);
allZoneGroups.addAll(this.groupsToCreate.keySet());
// Prune our set of authorities according to deletions
if (isFullSync) {
final Set<String> personDeletionCandidates = newPersonSet();
personDeletionCandidates.addAll(allZonePersons);
final Set<String> groupDeletionCandidates = new TreeSet<String>();
groupDeletionCandidates.addAll(allZoneGroups);
this.deletionCandidates = new TreeSet<String>();
for (String person : userRegistry.getPersonNames()) {
personDeletionCandidates.remove(person);
}
for (String group : userRegistry.getGroupNames()) {
groupDeletionCandidates.remove(group);
}
this.deletionCandidates = new TreeSet<String>();
this.deletionCandidates.addAll(personDeletionCandidates);
this.deletionCandidates.addAll(groupDeletionCandidates);
if (allowDeletions) {
allZonePersons.removeAll(personDeletionCandidates);
allZoneGroups.removeAll(groupDeletionCandidates);
} else {
// Complete association deletion information by scanning deleted groups
BatchProcessor<String> groupScanner = new BatchProcessor<String>(zone + " Missing Authority Scanning", ChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper(), this.deletionCandidates, ChainingUserRegistrySynchronizer.this.workerThreads, 20, ChainingUserRegistrySynchronizer.this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, ChainingUserRegistrySynchronizer.this.loggingInterval);
groupScanner.process(new BaseBatchProcessWorker<String>() {
@Override
public String getIdentifier(String entry) {
return entry;
}
@Override
public void process(String authority) throws Throwable {
// MNT-12454 fix. Modifies an authority's zone. Move authority from AUTH.EXT.LDAP1 to AUTH.ALF.
updateAuthorityZones(authority, Collections.singleton(zoneId), Collections.singleton(AuthorityService.ZONE_AUTH_ALFRESCO));
}
}, splitTxns);
}
}
// Prune the group associations now that we have complete information
this.groupParentAssocsToCreate.keySet().retainAll(allZoneGroups);
logRetainParentAssociations(this.groupParentAssocsToCreate, allZoneGroups);
this.finalGroupChildAssocs.keySet().retainAll(allZoneGroups);
// Pruning person associations will have to wait until we have passed over all persons and built up
// this set
this.allZonePersons = allZonePersons;
if (!this.groupParentAssocsToDelete.isEmpty()) {
// Create/update the groups and delete parent associations to be deleted
// Batch 4 Group Creation and Association Deletion
BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>(SyncProcess.GROUP_CREATION_AND_ASSOCIATION_DELETION.getTitle(zone), ChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper(), this.groupParentAssocsToDelete.entrySet(), ChainingUserRegistrySynchronizer.this.workerThreads, 20, ChainingUserRegistrySynchronizer.this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, ChainingUserRegistrySynchronizer.this.loggingInterval);
groupCreator.process(new BaseBatchProcessWorker<Map.Entry<String, Set<String>>>() {
public String getIdentifier(Map.Entry<String, Set<String>> entry) {
return entry.getKey() + " " + entry.getValue();
}
public void process(Map.Entry<String, Set<String>> entry) throws Throwable {
String child = entry.getKey();
String groupDisplayName = Analyzer.this.groupsToCreate.get(child);
if (groupDisplayName != null) {
String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child);
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'");
}
// create the group
ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType.getAuthorityType(child), groupShortName, groupDisplayName, zoneSet);
} else {
// Maintain association deletions now. The creations will have to be done later once
// we have performed all the deletions in order to avoid creating cycles
maintainAssociationDeletions(child);
}
}
}, splitTxns);
}
}
}
private void finalizeAssociations(UserRegistry userRegistry, boolean splitTxns) {
// First validate the group associations to be created for potential cycles. Remove any offending association
validateGroupParentAssocsToCreate();
// Now go ahead and create the group associations
if (!this.groupParentAssocsToCreate.isEmpty()) {
// Batch 5 Group Association Creation
BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>(SyncProcess.GROUP_ASSOCIATION_CREATION.getTitle(zone), ChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper(), this.groupParentAssocsToCreate.entrySet(), ChainingUserRegistrySynchronizer.this.workerThreads, 20, ChainingUserRegistrySynchronizer.this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, ChainingUserRegistrySynchronizer.this.loggingInterval);
groupCreator.process(new BaseBatchProcessWorker<Map.Entry<String, Set<String>>>() {
public String getIdentifier(Map.Entry<String, Set<String>> entry) {
return entry.getKey() + " " + entry.getValue();
}
public void process(Map.Entry<String, Set<String>> entry) throws Throwable {
maintainAssociationCreations(entry.getKey());
}
}, splitTxns);
}
// Remove all the associations we have already dealt with
this.personParentAssocsToDelete.keySet().removeAll(this.personsProcessed);
// Filter out associations to authorities that simply can't exist (and log if debugging is enabled)
logRetainParentAssociations(this.personParentAssocsToCreate, this.allZonePersons);
// Update associations to persons not updated themselves
if (!this.personParentAssocsToDelete.isEmpty()) {
// Batch 6 Person Association
BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>(SyncProcess.PERSON_ASSOCIATION.getTitle(zone), ChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper(), this.personParentAssocsToDelete.entrySet(), ChainingUserRegistrySynchronizer.this.workerThreads, 20, ChainingUserRegistrySynchronizer.this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, ChainingUserRegistrySynchronizer.this.loggingInterval);
groupCreator.process(new BaseBatchProcessWorker<Map.Entry<String, Set<String>>>() {
public String getIdentifier(Map.Entry<String, Set<String>> entry) {
return entry.getKey() + " " + entry.getValue();
}
public void process(Map.Entry<String, Set<String>> entry) throws Throwable {
maintainAssociationDeletions(entry.getKey());
maintainAssociationCreations(entry.getKey());
}
}, splitTxns);
}
}
private void maintainAssociationDeletions(String authorityName) {
boolean isPerson = AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER;
Set<String> parentsToDelete = isPerson ? this.personParentAssocsToDelete.get(authorityName) : this.groupParentAssocsToDelete.get(authorityName);
if (parentsToDelete != null && !parentsToDelete.isEmpty()) {
for (String parent : parentsToDelete) {
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
ChainingUserRegistrySynchronizer.logger.debug("Removing '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(authorityName) + "' from group '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(parent) + "'");
}
ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, authorityName);
}
}
}
private void maintainAssociationCreations(String authorityName) {
boolean isPerson = AuthorityType.getAuthorityType(authorityName) == AuthorityType.USER;
Set<String> parents = isPerson ? this.personParentAssocsToCreate.get(authorityName) : this.groupParentAssocsToCreate.get(authorityName);
if (parents != null && !parents.isEmpty()) {
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
for (String groupName : parents) {
ChainingUserRegistrySynchronizer.logger.debug("Adding '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(authorityName) + "' to group '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName) + "'");
}
}
try {
ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, authorityName);
} catch (UnknownAuthorityException e) {
// waiting for another worker thread to create it
throw new ConcurrencyFailureException("Forcing batch retry for unknown authority", e);
} catch (InvalidNodeRefException e) {
// See: ALF-5471: 'authorityMigration' patch can report 'Node does not exist'
throw new ConcurrencyFailureException("Forcing batch retry for invalid node", e);
}
}
// Remember that this person's associations have been maintained
if (isPerson) {
synchronized (this) {
this.personsProcessed.add(authorityName);
}
}
}
}
// end of Analyzer class
// Run the first process the Group Analyzer
final Analyzer groupAnalyzer = new Analyzer(lastModifiedMillis);
int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns);
groupAnalyzer.processGroups(userRegistry, isFullSync, splitTxns);
// Process persons and their parent associations
lastModifiedMillis = forceUpdate ? -1 : getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns);
lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) {
if (lastModified == null) {
ChainingUserRegistrySynchronizer.logger.info("Retrieving all users from user registry '" + zone + "'");
} else {
ChainingUserRegistrySynchronizer.logger.info("Retrieving users changed since " + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'");
}
}
// User Creation and Association
final BatchProcessor<NodeDescription> personProcessor = new BatchProcessor<NodeDescription>(SyncProcess.USER_CREATION.getTitle(zone), this.transactionService.getRetryingTransactionHelper(), userRegistry.getPersons(lastModified), this.workerThreads, 10, this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, this.loggingInterval);
final UserRegistry userRegistryFinalRef = userRegistry;
class PersonWorker extends BaseBatchProcessWorker<NodeDescription> {
private long latestTime;
public PersonWorker(final long latestTime) {
this.latestTime = latestTime;
}
public long getLatestTime() {
return this.latestTime;
}
public String getIdentifier(NodeDescription entry) {
return entry.getSourceId();
}
public void process(NodeDescription person) throws Throwable {
// Make a mutable copy of the person properties, since they get written back to by person service
HashMap<QName, Serializable> personProperties = new HashMap<QName, Serializable>(person.getProperties());
String personName = personProperties.get(ContentModel.PROP_USERNAME).toString().trim();
personProperties.put(ContentModel.PROP_USERNAME, personName);
if (Boolean.parseBoolean(ChainingUserRegistrySynchronizer.this.externalUserControl) && ChainingUserRegistrySynchronizer.this.externalUserControlSubsystemName.equals(zone) && userRegistryFinalRef instanceof LDAPUserRegistry) {
try {
LDAPUserRegistry ldapUserRegistry = (LDAPUserRegistry) userRegistryFinalRef;
if (ldapUserRegistry.getUserAccountStatusInterpreter() != null) {
QName propertyNameToCheck = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "userAccountStatusProperty");
if (personProperties.get(propertyNameToCheck) != null || ldapUserRegistry.getUserAccountStatusInterpreter().acceptsNullArgument()) {
boolean isUserAccountDisabled = ldapUserRegistry.getUserAccountStatusInterpreter().isUserAccountDisabled(personProperties.get(propertyNameToCheck));
personProperties.put(ContentModel.PROP_ENABLED, !isUserAccountDisabled);
}
}
} catch (IllegalArgumentException iae) {
// Can be thrown by certain implementations of AbstractDirectoryServiceUserAccountStatusInterpreter;
// We'll just log it.
ChainingUserRegistrySynchronizer.logger.debug(iae.getMessage(), iae);
}
}
// for invalid names will throw ConstraintException that will be catched by BatchProcessor$TxnCallback
nameChecker.evaluate(personName);
Set<String> zones = ChainingUserRegistrySynchronizer.this.authorityService.getAuthorityZones(personName);
if (zones == null) {
// The person did not exist at all
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
ChainingUserRegistrySynchronizer.logger.debug("Creating user '" + personName + "'");
}
ChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet);
} else if (zones.contains(zoneId)) {
// The person already existed in this zone: update the person
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
ChainingUserRegistrySynchronizer.logger.debug("Updating user '" + personName + "'");
}
ChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, personProperties, false);
} else {
// Check whether the user is in any of the authentication chain zones
Set<String> intersection = new TreeSet<String>(zones);
intersection.retainAll(allZoneIds);
// Check whether the user is in any of the higher priority authentication chain zones
Set<String> visited = new TreeSet<String>(intersection);
visited.retainAll(visitedZoneIds);
if (visited.size() > 0) {
// A person that exists in a different zone with higher precedence - ignore
return;
} else if (!allowDeletions || intersection.isEmpty()) {
// not in the authentication chain. May be due to upgrade or zone changes. Let's re-zone them
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) {
ChainingUserRegistrySynchronizer.logger.warn("Updating user '" + personName + "'. This user will in future be assumed to originate from user registry '" + zone + "'.");
}
updateAuthorityZones(personName, zones, zoneSet);
ChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, personProperties, false);
} else {
// The person existed, but in a zone with lower precedence
if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) {
ChainingUserRegistrySynchronizer.logger.warn("Recreating occluded user '" + personName + "'. This user was previously created through synchronization with a lower priority user registry.");
}
ChainingUserRegistrySynchronizer.this.personService.deletePerson(personName);
ChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet);
}
}
// Maintain association deletions and creations in one shot (safe to do this with persons as we can't
// create cycles)
groupAnalyzer.maintainAssociationDeletions(personName);
groupAnalyzer.maintainAssociationCreations(personName);
synchronized (this) {
// Maintain the last modified date
Date personLastModified = person.getLastModified();
if (personLastModified != null) {
this.latestTime = Math.max(this.latestTime, personLastModified.getTime());
}
}
}
}
PersonWorker persons = new PersonWorker(lastModifiedMillis);
int personProcessedCount = personProcessor.process(persons, splitTxns);
// Process those associations to persons who themselves have not been updated
groupAnalyzer.finalizeAssociations(userRegistry, splitTxns);
// Only now that the whole tree has been processed is it safe to persist the last modified dates
long latestTime = groupAnalyzer.getLatestTime();
if (latestTime != -1) {
setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, latestTime, splitTxns);
}
latestTime = persons.getLatestTime();
if (latestTime != -1) {
setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, latestTime, splitTxns);
}
// Delete authorities if we have complete information for the zone
Set<String> deletionCandidates = groupAnalyzer.getDeletionCandidates();
if (isFullSync && allowDeletions && !deletionCandidates.isEmpty()) {
// Batch 7 Authority Deletion
BatchProcessor<String> authorityDeletionProcessor = new BatchProcessor<String>(SyncProcess.AUTHORITY_DELETION.getTitle(zone), this.transactionService.getRetryingTransactionHelper(), deletionCandidates, this.workerThreads, 10, this.applicationEventPublisher, ChainingUserRegistrySynchronizer.logger, this.loggingInterval);
class AuthorityDeleter extends BaseBatchProcessWorker<String> {
private int personProcessedCount;
private int groupProcessedCount;
public int getPersonProcessedCount() {
return this.personProcessedCount;
}
public int getGroupProcessedCount() {
return this.groupProcessedCount;
}
public String getIdentifier(String entry) {
return entry;
}
public void process(String authority) throws Throwable {
if (AuthorityType.getAuthorityType(authority) == AuthorityType.USER) {
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
ChainingUserRegistrySynchronizer.logger.debug("Deleting user '" + authority + "'");
}
ChainingUserRegistrySynchronizer.this.personService.deletePerson(authority);
synchronized (this) {
this.personProcessedCount++;
}
} else {
if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) {
ChainingUserRegistrySynchronizer.logger.debug("Deleting group '" + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(authority) + "'");
}
ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(authority);
synchronized (this) {
this.groupProcessedCount++;
}
}
}
}
AuthorityDeleter authorityDeleter = new AuthorityDeleter();
authorityDeletionProcessor.process(authorityDeleter, splitTxns);
groupProcessedCount += authorityDeleter.getGroupProcessedCount();
personProcessedCount += authorityDeleter.getPersonProcessedCount();
}
// Remember we have visited this zone
visitedZoneIds.add(zoneId);
Object[] statusParams = { personProcessedCount, groupProcessedCount };
final String statusMessage = I18NUtil.getMessage("synchronization.summary.status", statusParams);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) {
ChainingUserRegistrySynchronizer.logger.info("Finished synchronizing users and groups with user registry '" + zone + "'");
ChainingUserRegistrySynchronizer.logger.info(statusMessage);
}
notifySyncDirectoryEnd(zone, statusMessage);
}
use of org.alfresco.repo.batch.BatchProcessor in project alfresco-repository by Alfresco.
the class FeedNotifierImpl method executeInternal.
private void executeInternal(final int repeatIntervalMins) {
final String emailTemplateRef = getEmailTemplateRef();
if (emailTemplateRef == null) {
return;
}
final String shareUrl = UrlUtil.getShareUrl(sysAdminParams);
if (logger.isDebugEnabled()) {
logger.debug("Share URL configured as: " + shareUrl);
}
final AtomicInteger userCnt = new AtomicInteger(0);
final AtomicInteger feedEntryCnt = new AtomicInteger(0);
final long startTime = System.currentTimeMillis();
// local cache for this execution
final Map<String, String> siteNames = new ConcurrentHashMap<String, String>(10);
try {
final String currentUser = AuthenticationUtil.getRunAsUser();
final String tenantDomain = TenantUtil.getCurrentDomain();
// process the feeds using the batch processor {@link BatchProcessor}
BatchProcessor.BatchProcessWorker<PersonInfo> worker = new BatchProcessor.BatchProcessWorker<PersonInfo>() {
public String getIdentifier(final PersonInfo person) {
StringBuilder sb = new StringBuilder("Person ");
sb.append(person.getUserName());
return sb.toString();
}
public void beforeProcess() throws Throwable {
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(currentUser);
}
public void afterProcess() throws Throwable {
AuthenticationUtil.popAuthentication();
}
public void process(final PersonInfo person) throws Throwable {
final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper();
txHelper.setMaxRetries(0);
TenantUtil.runAsTenant(new TenantRunAsWork<Void>() {
@Override
public Void doWork() throws Exception {
txHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable {
processInternal(person);
return null;
}
}, false, true);
return null;
}
}, tenantDomain);
}
private void processInternal(final PersonInfo person) throws Exception {
final NodeRef personNodeRef = person.getNodeRef();
try {
Pair<Integer, Long> result = userNotifier.notifyUser(personNodeRef, MSG_EMAIL_SUBJECT, new Object[] { ModelUtil.getProductName(repoAdminService) }, siteNames, shareUrl, repeatIntervalMins, emailTemplateRef);
if (result != null) {
int entryCnt = result.getFirst();
final long maxFeedId = result.getSecond();
Long currentMaxFeedId = (Long) nodeService.getProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID);
if ((currentMaxFeedId == null) || (currentMaxFeedId < maxFeedId)) {
nodeService.setProperty(personNodeRef, ContentModel.PROP_EMAIL_FEED_ID, maxFeedId);
}
userCnt.incrementAndGet();
feedEntryCnt.addAndGet(entryCnt);
}
} catch (InvalidNodeRefException inre) {
// skip this person - eg. no longer exists ?
logger.warn("Skip feed notification for user (" + personNodeRef + "): " + inre.getMessage());
}
}
};
// grab people for the batch processor in chunks of size batchSize
BatchProcessWorkProvider<PersonInfo> provider = new BatchProcessWorkProvider<PersonInfo>() {
private int skip = 0;
private int maxItems = batchSize;
private boolean hasMore = true;
@Override
public int getTotalEstimatedWorkSize() {
return personService.countPeople();
}
@Override
public Collection<PersonInfo> getNextWork() {
if (!hasMore) {
return Collections.emptyList();
}
PagingResults<PersonInfo> people = personService.getPeople(null, null, null, new PagingRequest(skip, maxItems));
List<PersonInfo> page = people.getPage();
skip += page.size();
hasMore = people.hasMoreItems();
return page;
}
};
final RetryingTransactionHelper txHelper = transactionService.getRetryingTransactionHelper();
txHelper.setMaxRetries(0);
new BatchProcessor<PersonInfo>("FeedNotifier", txHelper, provider, numThreads, batchSize, applicationContext, logger, 100).process(worker, true);
} catch (Throwable e) {
// If the VM is shutting down, then ignore
if (vmShutdownListener.isVmShuttingDown()) {
// Ignore
} else {
logger.error("Exception during notification of feeds", e);
}
} finally {
int count = userCnt.get();
int entryCount = feedEntryCnt.get();
// assume sends are synchronous - hence bump up to last max feed id
if (count > 0) {
if (logger.isInfoEnabled()) {
// TODO i18n of info message
StringBuilder sb = new StringBuilder();
sb.append("Notified ").append(userCnt).append(" user").append(count != 1 ? "s" : "");
sb.append(" of ").append(feedEntryCnt).append(" activity feed entr").append(entryCount != 1 ? "ies" : "y");
sb.append(" (in ").append(System.currentTimeMillis() - startTime).append(" msecs)");
logger.info(sb.toString());
}
} else {
if (logger.isTraceEnabled()) {
logger.trace("Nothing to send since no new user activities found");
}
}
}
}
use of org.alfresco.repo.batch.BatchProcessor in project alfresco-repository by Alfresco.
the class LocalFeedGenerator method generate.
protected boolean generate() throws Exception {
final Long maxSequence = getPostDaoService().getMaxActivitySeq();
final Long minSequence = getPostDaoService().getMinActivitySeq();
final Integer maxNodeHash = getPostDaoService().getMaxNodeHash();
if ((maxSequence == null) || (minSequence == null) || (maxNodeHash == null)) {
return false;
}
// TODO ... or push this upto to job scheduler ... ?
AuthenticationUtil.runAs(new RunAsWork<Object>() {
public Object doWork() {
getWebScriptsCtx().setTicket(getAuthenticationService().getCurrentTicket());
return null;
}
}, // need web scripts to support System-level authentication ... see RepositoryContainer !
AuthenticationUtil.getSystemUserName());
// process the activity posts using the batch processor {@link BatchProcessor}
BatchProcessor.BatchProcessWorker<JobSettings> worker = new BatchProcessor.BatchProcessWorker<JobSettings>() {
@Override
public String getIdentifier(final JobSettings js) {
// TODO
StringBuilder sb = new StringBuilder("JobSettings ");
sb.append(js);
return sb.toString();
}
@Override
public void beforeProcess() throws Throwable {
}
@Override
public void afterProcess() throws Throwable {
}
@Override
public void process(final JobSettings js) throws Throwable {
final RetryingTransactionHelper txHelper = getTransactionService().getRetryingTransactionHelper();
txHelper.setMaxRetries(0);
txHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
public Void execute() throws Throwable {
int jobTaskNode = js.getJobTaskNode();
long minSeq = js.getMinSeq();
long maxSeq = js.getMaxSeq();
RepoCtx webScriptsCtx = js.getWebScriptsCtx();
// FeedTaskProcessor takes JobSettings parameters instead collection of ActivityPost. FeedTaskProcessor can be refactored.
feedTaskProcessor.process(jobTaskNode, minSeq, maxSeq, webScriptsCtx);
return null;
}
}, false, true);
}
};
// provides a JobSettings object
BatchProcessWorkProvider<JobSettings> provider = new BatchProcessWorkProvider<JobSettings>() {
private Long skip = minSequence;
private boolean hasMore = true;
@Override
public int getTotalEstimatedWorkSize() {
long size = maxSequence - minSequence + 1;
long remain = size % batchSize;
long workSize = (remain == 0) ? (size / batchSize) : (size / batchSize + 1);
return (int) workSize;
}
@Override
public Collection<JobSettings> getNextWork() {
if (!hasMore) {
return Collections.emptyList();
}
JobSettings js = new JobSettings();
js.setMinSeq(skip);
js.setMaxSeq(skip + batchSize - 1);
js.setJobTaskNode(maxNodeHash);
js.setWebScriptsCtx(getWebScriptsCtx());
skip += batchSize;
hasMore = skip > maxSequence ? false : true;
// One JobSettings object will be returned. Because FeedTaskProcessor fetches list activity posts by itself before processing.
List<JobSettings> result = new ArrayList<JobSettings>(1);
result.add(js);
return result;
}
};
final RetryingTransactionHelper txHelper = getTransactionService().getRetryingTransactionHelper();
txHelper.setMaxRetries(0);
// batchSize and loggingInterval parameters are equal 1 because provider always will provide collection with one JobSettings object.
// FeedTaskProcessor fetches list activity posts by itself before processing. It needs only JobSettings parameters. FeedTaskProcessor can be refactored.
new BatchProcessor<JobSettings>("LocalFeedGenerator", txHelper, provider, numThreads, 1, null, logger, 1).process(worker, true);
return true;
}
Aggregations