Search in sources :

Example 11 with BatchProcessor

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();
}
Also used : BatchProcessor(org.alfresco.repo.batch.BatchProcessor) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) RunAsWork(org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork) BatchProcessWorkerAdaptor(org.alfresco.repo.batch.BatchProcessor.BatchProcessWorkerAdaptor) BatchProcessWorkProvider(org.alfresco.repo.batch.BatchProcessWorkProvider)

Example 12 with BatchProcessor

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;
        }
    }
}
Also used : NodeRef(org.alfresco.service.cmr.repository.NodeRef) BatchProcessor(org.alfresco.repo.batch.BatchProcessor) HashMap(java.util.HashMap) BatchProcessWorkProvider(org.alfresco.repo.batch.BatchProcessWorkProvider)

Example 13 with BatchProcessor

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);
}
Also used : Serializable(java.io.Serializable) ConcurrencyFailureException(org.springframework.dao.ConcurrencyFailureException) InvalidNodeRefException(org.alfresco.service.cmr.repository.InvalidNodeRefException) UnknownAuthorityException(org.alfresco.repo.security.authority.UnknownAuthorityException) LDAPUserRegistry(org.alfresco.repo.security.sync.ldap.LDAPUserRegistry) PropertyMap(org.alfresco.util.PropertyMap) BatchProcessor(org.alfresco.repo.batch.BatchProcessor) LDAPUserRegistry(org.alfresco.repo.security.sync.ldap.LDAPUserRegistry) QName(org.alfresco.service.namespace.QName) PropertyMap(org.alfresco.util.PropertyMap)

Example 14 with BatchProcessor

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");
            }
        }
    }
}
Also used : PersonInfo(org.alfresco.service.cmr.security.PersonService.PersonInfo) RetryingTransactionHelper(org.alfresco.repo.transaction.RetryingTransactionHelper) NodeRef(org.alfresco.service.cmr.repository.NodeRef) BatchProcessor(org.alfresco.repo.batch.BatchProcessor) InvalidNodeRefException(org.alfresco.service.cmr.repository.InvalidNodeRefException) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) BatchProcessWorkProvider(org.alfresco.repo.batch.BatchProcessWorkProvider) InvalidNodeRefException(org.alfresco.service.cmr.repository.InvalidNodeRefException) SearcherException(org.alfresco.repo.search.SearcherException) BeansException(org.springframework.beans.BeansException) VmShutdownException(org.alfresco.util.VmShutdownListener.VmShutdownException) LockAcquisitionException(org.alfresco.repo.lock.LockAcquisitionException) PagingRequest(org.alfresco.query.PagingRequest) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) RetryingTransactionCallback(org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback)

Example 15 with BatchProcessor

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;
}
Also used : JobSettings(org.alfresco.repo.activities.feed.JobSettings) RepoCtx(org.alfresco.repo.activities.feed.RepoCtx) RetryingTransactionHelper(org.alfresco.repo.transaction.RetryingTransactionHelper) ArrayList(java.util.ArrayList) BatchProcessWorkProvider(org.alfresco.repo.batch.BatchProcessWorkProvider) BatchProcessor(org.alfresco.repo.batch.BatchProcessor)

Aggregations

BatchProcessor (org.alfresco.repo.batch.BatchProcessor)15 BatchProcessWorkProvider (org.alfresco.repo.batch.BatchProcessWorkProvider)10 ArrayList (java.util.ArrayList)7 RetryingTransactionHelper (org.alfresco.repo.transaction.RetryingTransactionHelper)7 NodeRef (org.alfresco.service.cmr.repository.NodeRef)7 List (java.util.List)4 BatchProcessWorker (org.alfresco.repo.batch.BatchProcessor.BatchProcessWorker)4 Serializable (java.io.Serializable)2 Iterator (java.util.Iterator)2 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)2 AlfrescoRuntimeException (org.alfresco.error.AlfrescoRuntimeException)2 NodePropertyEntity (org.alfresco.repo.domain.node.NodePropertyEntity)2 LockAcquisitionException (org.alfresco.repo.lock.LockAcquisitionException)2 RunAsWork (org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork)2 ChildAssociationRef (org.alfresco.service.cmr.repository.ChildAssociationRef)2 InvalidNodeRefException (org.alfresco.service.cmr.repository.InvalidNodeRefException)2 QName (org.alfresco.service.namespace.QName)2 File (java.io.File)1 HashMap (java.util.HashMap)1 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)1