List of usage examples for java.util Set retainAll
boolean retainAll(Collection<?> c);
From source file:org.structr.core.property.CollectionNotionProperty.java
@Override public SearchAttribute getSearchAttribute(SecurityContext securityContext, BooleanClause.Occur occur, List<T> searchValues, boolean exactMatch, final Query query) { final Predicate<GraphObject> predicate = query != null ? query.toPredicate() : null; final SourceSearchAttribute attr = new SourceSearchAttribute(occur); final Set<GraphObject> intersectionResult = new LinkedHashSet<>(); boolean alreadyAdded = false; try {//from ww w . j av a2s . c om if (searchValues != null && !searchValues.isEmpty()) { final PropertyKey key = notion.getPrimaryPropertyKey(); final PropertyConverter inputConverter = key.inputConverter(securityContext); final List<Object> transformedValues = new LinkedList<>(); boolean allBlank = true; // transform search values using input convert of notion property for (T searchValue : searchValues) { if (inputConverter != null) { transformedValues.add(inputConverter.convert(searchValue)); } else { transformedValues.add(searchValue); } } // iterate over transformed values for (Object searchValue : transformedValues) { // check if the list contains non-empty search values if (StringUtils.isBlank(searchValue.toString())) { continue; } else { allBlank = false; } final App app = StructrApp.getInstance(securityContext); if (exactMatch) { Result<AbstractNode> result = app.nodeQuery(collectionProperty.relatedType()) .and(notion.getPrimaryPropertyKey(), searchValue).getResult(); for (AbstractNode node : result.getResults()) { switch (occur) { case MUST: if (!alreadyAdded) { // the first result is the basis of all subsequent intersections intersectionResult.addAll(collectionProperty.getRelatedNodesReverse( securityContext, node, declaringClass, predicate)); // the next additions are intersected with this one alreadyAdded = true; } else { intersectionResult.retainAll(collectionProperty.getRelatedNodesReverse( securityContext, node, declaringClass, predicate)); } break; case SHOULD: intersectionResult.addAll(collectionProperty.getRelatedNodesReverse(securityContext, node, declaringClass, predicate)); break; case MUST_NOT: break; } } } else { Result<AbstractNode> result = app.nodeQuery(collectionProperty.relatedType(), false) .and(notion.getPrimaryPropertyKey(), searchValue, false).getResult(); // loose search behaves differently, all results must be combined for (AbstractNode node : result.getResults()) { intersectionResult.addAll(collectionProperty.getRelatedNodesReverse(securityContext, node, declaringClass, predicate)); } } } if (allBlank) { // experimental filter attribute that // removes entities with a non-empty // value in the given field return new EmptySearchAttribute(this, Collections.emptyList()); } else { attr.setResult(intersectionResult); } } else { // experimental filter attribute that // removes entities with a non-empty // value in the given field return new EmptySearchAttribute(this, Collections.emptyList()); } } catch (FrameworkException fex) { fex.printStackTrace(); } return attr; }
From source file:ca.uhn.fhir.jpa.dao.BaseFhirResourceDao.java
private Set<Long> addPredicateId(Set<Long> theExistingPids, Set<Long> thePids) { if (thePids == null || thePids.isEmpty()) { return Collections.emptySet(); }//from ww w . j a v a 2s .c o m CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); CriteriaQuery<Long> cq = builder.createQuery(Long.class); Root<ResourceTable> from = cq.from(ResourceTable.class); cq.select(from.get("myId").as(Long.class)); Predicate typePredicate = builder.equal(from.get("myResourceType"), myResourceName); Predicate idPrecidate = from.get("myId").in(thePids); cq.where(builder.and(typePredicate, idPrecidate)); TypedQuery<Long> q = myEntityManager.createQuery(cq); HashSet<Long> found = new HashSet<Long>(q.getResultList()); if (!theExistingPids.isEmpty()) { theExistingPids.retainAll(found); } return found; }
From source file:ubc.pavlab.aspiredb.server.service.QueryServiceImpl.java
@Override @Transactional(readOnly = true)//from w ww . j a v a 2 s . com @RemoteMethod public Map<Integer, Collection<Object>> getSubjectsVariants(Set<AspireDbFilterConfig> filters) throws NotLoggedInException, ExternalDependencyException { Map<Integer, Collection<Long>> svIds = new HashMap<>(); Map<Integer, Collection<Object>> ret = new HashMap<>(); Set<Long> svoIds = new HashSet<Long>(); Set<Long> vvoIds = new HashSet<Long>(); Set<AspireDbFilterConfig> filtersTrimmed = new HashSet<>(); Collection<Long> subjectPhenoIds = new HashSet<>(); Collection<Long> variantPhenoIds = new HashSet<>(); for (AspireDbFilterConfig f : filters) { // treat PhenotypeFilters as a special case because calling this twice // is both redundant and slow (see Bug #3892) if (f instanceof PhenotypeFilterConfig) { svIds = variantDao.getSubjectVariantIdsByPhenotype((PhenotypeFilterConfig) f); subjectPhenoIds = svIds.get(VariantDao.SUBJECT_IDS_KEY); variantPhenoIds = svIds.get(VariantDao.VARIANT_IDS_KEY); } else { filtersTrimmed.add(f); } } StopWatch timer = new StopWatch(); timer.start(); List<VariantValueObject> vvos = queryVariants(filtersTrimmed).getItems(); timer.stop(); log.info(" query variants took " + timer); for (VariantValueObject v : vvos) { vvoIds.add(v.getId()); } // need a separate call for Subject and Variants because // we can have a Subject without any Variants and those won't get counted! if (ConfigUtils.hasSubjectConfig(filters) || !ConfigUtils.hasVariantConfig(filters)) { timer = new StopWatch(); timer.start(); List<SubjectValueObject> svos = querySubjects(filtersTrimmed).getItems(); timer.stop(); log.info(" query subjects took " + timer); for (SubjectValueObject s : svos) { svoIds.add(s.getId()); } } else { // if there's no Subject filter then we can just get it // from Variant IDs! This saves us from executing redundant queries. for (VariantValueObject v : vvos) { svoIds.add(v.getSubjectId()); } } // intersect PhenoIds with Ids from other filters if (!subjectPhenoIds.isEmpty()) { svoIds.retainAll(subjectPhenoIds); vvoIds.retainAll(variantPhenoIds); } Collection<Object> retSvos = new ArrayList<>(); for (Subject s : subjectDao.load(svoIds)) { retSvos.add(s.convertToValueObjectWithPhenotypes()); } Collection<Object> retVvos = new ArrayList<>(); for (Variant v : variantDao.load(vvoIds)) { retVvos.add(v.toValueObject()); } ret.put(VariantDao.SUBJECT_IDS_KEY, retSvos); ret.put(VariantDao.VARIANT_IDS_KEY, retVvos); return ret; }
From source file:ubc.pavlab.aspiredb.server.service.QueryServiceImpl.java
@Override @Transactional(readOnly = true)/*from w ww. j av a 2s . c o m*/ @RemoteMethod public Map<Integer, Integer> getSubjectGenes(Set<AspireDbFilterConfig> filters) throws NotLoggedInException, ExternalDependencyException { Map<Integer, Collection<Long>> svIds = new HashMap<>(); Map<Integer, Integer> ret = new HashMap<>(); Set<Long> svoIds = new HashSet<>(); Set<Long> vvoIds = new HashSet<>(); int subjectCount = 0; int variantCount = 0; Set<AspireDbFilterConfig> filtersTrimmed = new HashSet<>(); Collection<Long> subjectPhenoIds = new HashSet<>(); Collection<Long> variantPhenoIds = new HashSet<>(); for (AspireDbFilterConfig f : filters) { // treat PhenotypeFilters as a special case because calling this twice // is both redundant and slow (see Bug #3892) if (f instanceof PhenotypeFilterConfig) { svIds = variantDao.getSubjectVariantIdsByPhenotype((PhenotypeFilterConfig) f); subjectPhenoIds = svIds.get(VariantDao.SUBJECT_IDS_KEY); variantPhenoIds = svIds.get(VariantDao.VARIANT_IDS_KEY); } else { filtersTrimmed.add(f); } } StopWatch timer = new StopWatch(); timer.start(); List<VariantValueObject> vvos = queryVariants(filtersTrimmed).getItems(); timer.stop(); log.info(" query variants took " + timer); for (VariantValueObject v : vvos) { vvoIds.add(v.getId()); } // need a separate call for Subject and Variants because // we can have a Subject without any Variants and those won't get counted! if (ConfigUtils.hasSubjectConfig(filters)) { timer = new StopWatch(); timer.start(); List<SubjectValueObject> svos = querySubjects(filtersTrimmed).getItems(); timer.stop(); log.info(" query subjects took " + timer); for (SubjectValueObject s : svos) { svoIds.add(s.getId()); } } else { // if there's no Subject filter then we can just get it // from Variant IDs! This saves us from executing redundant queries. for (VariantValueObject v : vvos) { svoIds.add(v.getSubjectId()); } } // intersect PhenoIds with Ids from other filters if (!subjectPhenoIds.isEmpty()) { svoIds.retainAll(subjectPhenoIds); vvoIds.retainAll(variantPhenoIds); } subjectCount = svoIds.size(); variantCount = vvoIds.size(); ret.put(VariantDao.SUBJECT_IDS_KEY, subjectCount); ret.put(VariantDao.VARIANT_IDS_KEY, variantCount); return ret; }
From source file:com.vmware.vhadoop.vhm.hadoop.HadoopAdaptor.java
@Override /* Returns the set of active dnsNames based on input Set */ public Set<String> checkTargetTTsSuccess(String opType, Set<String> ttDnsNames, int totalTargetEnabled, HadoopClusterInfo cluster) {/*from w ww . jav a2 s. c o m*/ String scriptRemoteFilePath = JOB_TRACKER_DEFAULT_SCRIPT_DEST_PATH + JOB_TRACKER_CHECK_SCRIPT_FILE_NAME; String listRemoteFilePath = null; String opDesc = "checkTargetTTsSuccess"; if (ttDnsNames == null) { _log.warning("No valid TT names provided"); return null; } /* We don't expect null or empty values, but weed out anyway */ ttDnsNames.remove(null); ttDnsNames.remove(""); if (ttDnsNames.size() == 0) { _log.warning("No valid TT names provided"); return null; } _log.log(Level.INFO, "Affected TTs: " + ttDnsNames); setErrorParamsForCommand(cluster, opDesc, scriptRemoteFilePath, listRemoteFilePath); int iterations = 0; CompoundStatus getActiveStatus = null; int rc = UNKNOWN_ERROR; Set<String> allActiveTTs = null; long lastCheckAttemptTime = Long.MAX_VALUE; do { if (iterations > 0) { /* 1141429: Ensure that if the script fails, there is a minimum wait before the next retry attempt */ long millisSinceLastCheck = (System.currentTimeMillis() - lastCheckAttemptTime); long underWaitMillis = JOB_TRACKER_CHECK_SCRIPT_MIN_RETRY_MILLIS - millisSinceLastCheck; if (underWaitMillis > 0) { try { _log.fine("Sleeping for underWaitMillis = " + underWaitMillis); Thread.sleep(underWaitMillis); } catch (InterruptedException e) { } } _log.log(Level.INFO, "Target TTs not yet achieved...checking again - " + iterations); _log.log(Level.INFO, "Affected TTs: " + ttDnsNames); } getActiveStatus = new CompoundStatus(ACTIVE_TTS_STATUS_KEY); lastCheckAttemptTime = System.currentTimeMillis(); allActiveTTs = getActiveTTs(cluster, totalTargetEnabled, getActiveStatus); //Declare success as long as the we manage to de/recommission only the TTs we set out to handle (rather than checking correctness for all TTs) if ((allActiveTTs != null) && ((opType.equals("Recommission") && allActiveTTs.containsAll(ttDnsNames)) || (opType.equals("Decommission") && ttDnsNames.retainAll(allActiveTTs) && ttDnsNames.isEmpty()))) { _log.log(Level.INFO, "All selected TTs correctly %sed", opType.toLowerCase()); rc = SUCCESS; break; } /* If there was an error reported by getActiveTTs... */ TaskStatus taskStatus = getActiveStatus.getFirstFailure(STATUS_INTERPRET_ERROR_CODE); if (taskStatus != null) { rc = taskStatus.getErrorCode(); } else { /* * JG: Sometimes we don't know the hostnames (e.g., localhost); in these cases as long as the check script returns success based * on target #TTs we are good. * TODO: Change check script to return success if #newly added + #current_enabled is met rather than target #TTs is met. This is * to address scenarios where there is a mismatch (#Active TTs != #poweredOn VMs) to begin with... * CHANGED: We have changed the time at which this function is invoked -- it gets invoked only when dns/hostnames are available. * So we no longer have this issue of not knowing hostnames and still meeting target #TTs. Our only successful exit is when the * TTs that have been explicitly asked to be checked, have been correctly de/recommissioned. * * rc = SUCCESS; //Note: removing this * * We also notice that in this case, where #Active TTs matches target, but all the requested TTs haven't been de/recommissioned yet, * the check script returns immediately (because it only looks for a match of these values, which is true here). So we recompute * target TTs based on latest information to essentially put back the delay... */ Set<String> deltaTTs = new HashSet<String>(ttDnsNames); if (opType.equals("Recommission")) { deltaTTs.removeAll(allActiveTTs); //get TTs that haven't been recommissioned yet... totalTargetEnabled = allActiveTTs.size() + deltaTTs.size(); } else { //optype = Decommission deltaTTs.retainAll(allActiveTTs); //get TTs that haven't been decommissioned yet... totalTargetEnabled = allActiveTTs.size() - deltaTTs.size(); } _log.log(Level.INFO, "Even though #ActiveTTs = #TargetTTs, not all requested TTs have been " + opType.toLowerCase() + "ed yet - Trying again with updated target: " + totalTargetEnabled); } /* Break out if there is an error other than the ones we expect to be resolved in a subsequent invocation of the check script */ if (rc != ERROR_FEWER_TTS && rc != ERROR_EXCESS_TTS && rc != UNKNOWN_ERROR) { break; } } while (iterations++ < ACTIVE_TASK_TRACKERS_CHECK_RETRY_ITERATIONS); getCompoundStatus().addStatus(_errorCodes.interpretErrorCode(_log, rc, getErrorParamValues(cluster))); if (rc != SUCCESS) { getActiveStatus.registerTaskFailed(false, "Check Test Failed"); getCompoundStatus().addStatus(getActiveStatus); } return allActiveTTs; }
From source file:com.jeans.iservlet.service.asset.impl.AssetServiceImpl.java
@Override @Transactional(readOnly = true)/*from w ww.j a v a2 s . co m*/ public Set<Byte> checkNextStates(Set<Long> ids, byte type) { Set<Byte> states = new HashSet<Byte>(); if (ids.size() == 0) { return states; } states.add(AssetConstants.IN_USE); states.add(AssetConstants.IDLE); states.add(AssetConstants.DISUSE); if (type == AssetConstants.HARDWARE_ASSET) { states.add(AssetConstants.FIXING); states.add(AssetConstants.ELIMINATED); } // ? Set<Byte> n_h_iu = new HashSet<Byte>(); Set<Byte> n_h_id = new HashSet<Byte>(); Set<Byte> n_h_fx = new HashSet<Byte>(); Set<Byte> n_h_du = new HashSet<Byte>(); Set<Byte> n_s_iu = new HashSet<Byte>(); Set<Byte> n_s_id = new HashSet<Byte>(); n_h_iu.add(AssetConstants.IN_USE); n_h_iu.add(AssetConstants.IDLE); n_h_iu.add(AssetConstants.FIXING); n_h_id.add(AssetConstants.IN_USE); n_h_id.add(AssetConstants.FIXING); n_h_id.add(AssetConstants.DISUSE); n_h_fx.add(AssetConstants.IN_USE); n_h_fx.add(AssetConstants.IDLE); n_h_fx.add(AssetConstants.DISUSE); n_h_du.add(AssetConstants.ELIMINATED); n_s_iu.add(AssetConstants.IDLE); n_s_iu.add(AssetConstants.DISUSE); n_s_id.add(AssetConstants.IN_USE); n_s_id.add(AssetConstants.DISUSE); List<Asset> assets = loadAssets(ids, type); if (type == AssetConstants.HARDWARE_ASSET) { /* * (???) -> // -> // -> //? -> ? ? -> null */ for (Asset asset : assets) { byte oldState = asset.getState(); if (oldState == AssetConstants.ELIMINATED) { // ???? states.clear(); break; } else { if (states.size() == 0) { // ????? break; } else { switch (oldState) { case AssetConstants.IN_USE: states.retainAll(n_h_iu); break; case AssetConstants.IDLE: states.retainAll(n_h_id); break; case AssetConstants.FIXING: states.retainAll(n_h_fx); break; case AssetConstants.DISUSE: states.retainAll(n_h_du); } } } } } else { /* * -> / -> / -> null */ for (Asset asset : assets) { byte oldState = asset.getState(); if (oldState == AssetConstants.DISUSE) { // ??? states.clear(); break; } else { if (states.size() == 1) { // ????????????? continue; } else { if (oldState == AssetConstants.IN_USE) { states.retainAll(n_s_iu); } else { states.retainAll(n_s_id); } } } } } return states; }
From source file:org.alfresco.repo.security.sync.ChainingUserRegistrySynchronizer.java
/** * Synchronizes local groups and users with a {@link UserRegistry} for a particular zone, optionally handling * deletions./*from w w w .j a v a2 s .c o m*/ * * @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()) { // Deletions are disallowed or the group exists, but not in a zone that's 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 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); } } } } // Mark as created if new else { // 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) { // Let's force a transaction retry if a parent doesn't exist. It may be because we are // waiting for another worker thread to create it throw new ConcurrencyFailureException("Forcing batch retry for unknown authority", e); } catch (InvalidNodeRefException e) { // Another thread may have written the node, but it is not visible to this transaction // 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()) { // The person exists, but in a different zone. Either deletions are disallowed or the zone is // 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); }
From source file:org.alfresco.repo.security.sync.TenantChainingUserRegistrySynchronizer.java
/** * Synchronizes local groups and users with a {@link UserRegistry} for a * particular zone, optionally handling deletions. * /* www . ja va2 s . com*/ * @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; // 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(TenantChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns); Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (TenantChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { if (lastModified == null) { TenantChainingUserRegistrySynchronizer.logger .info("Retrieving all groups from user registry '" + zone + "'"); } else { TenantChainingUserRegistrySynchronizer.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>( zone + " Group Analysis", this.transactionService.getRetryingTransactionHelper(), userRegistry.getGroups(lastModified), this.workerThreads, 20, this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.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(final NodeDescription group) throws Throwable { PropertyMap groupProperties = group.getProperties(); String tenantDomain = (String) groupProperties.get(ContentModel.PROP_ORGANIZATION); logger.debug("Process group: " + groupProperties.get(ContentModel.PROP_AUTHORITY_NAME) + (tenantDomain == null ? "" : ("/" + tenantDomain))); if (tenantDomain != null) { if (!isTenantEnabled(tenantDomain)) { return; } AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { processInTenantMode(group); return null; } }, SEIPTenantIntegration.getSystemUserByTenantId(tenantDomain)); } else { processInTenantMode(group); } } private void processInTenantMode(NodeDescription group) { PropertyMap groupProperties = group.getProperties(); String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME); String groupShortName = TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(groupName); Set<String> groupZones = TenantChainingUserRegistrySynchronizer.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()) { // Deletions are disallowed or the group exists, but not // in a zone that's in the authentication // chain. May be due to upgrade or zone changes. Let's // re-zone them if (TenantChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { TenantChainingUserRegistrySynchronizer.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 (TenantChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { TenantChainingUserRegistrySynchronizer.logger.warn("Recreating occluded group '" + groupShortName + "'. This group was previously created through synchronization with a lower priority user registry."); } TenantChainingUserRegistrySynchronizer.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 : TenantChainingUserRegistrySynchronizer.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 = TenantChainingUserRegistrySynchronizer.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 = TenantChainingUserRegistrySynchronizer.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 TenantChainingUserRegistrySynchronizer.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); } } } } // Mark as created if new else { // 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()) { TenantChainingUserRegistrySynchronizer.logger.warn("Not adding group '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(group) + "' to group '" + TenantChainingUserRegistrySynchronizer.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 TenantChainingUserRegistrySynchronizer.this.personService.getUserNamesAreCaseSensitive() ? new TreeSet<String>() : new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); } private Map<String, Set<String>> newPersonMap() { return TenantChainingUserRegistrySynchronizer.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) { if (toRetain.isEmpty()) { parentAssocs.clear(); return; } 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 (TenantChainingUserRegistrySynchronizer.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(TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(parent)) .append('\''); } TenantChainingUserRegistrySynchronizer.logger.debug("Ignoring non-existent member '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(child) + "' in groups {" + groupList.toString() + "}. RunAs user:" + AuthenticationUtil.getRunAsUser()); } i.remove(); } } } public void processGroups(UserRegistry userRegistry, boolean isFullSync, boolean splitTxns) { // If we got back some groups, we have to cross reference them // with the set of known authorities if (isFullSync || !this.groupParentAssocsToDelete.isEmpty() || !this.groupParentAssocsToDelete.isEmpty()) { processGroupSynchInTenantMode(userRegistry, isFullSync, splitTxns); } } private void processGroupSynchInTenantMode(UserRegistry userRegistry, boolean isFullSync, boolean splitTxns) { final Set<String> allZonePersons = newPersonSet(); final Set<String> allZoneGroups = new TreeSet<String>(); final String tenantId = SEIPTenantIntegration.getTenantId(); // Add in current set of known authorities TenantChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper() .doInTransaction(new RetryingTransactionCallback<Void>() { public Void execute() throws Throwable { allZonePersons.addAll(TenantChainingUserRegistrySynchronizer.this.authorityService .getAllAuthoritiesInZone(zoneId, AuthorityType.USER)); allZoneGroups.addAll(TenantChainingUserRegistrySynchronizer.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 { if (!personDeletionCandidates.isEmpty()) { TenantChainingUserRegistrySynchronizer.logger.warn( "The following missing users are not being deleted as allowDeletions == false"); for (String person : personDeletionCandidates) { TenantChainingUserRegistrySynchronizer.logger.warn(" " + person); } } if (!groupDeletionCandidates.isEmpty()) { TenantChainingUserRegistrySynchronizer.logger.warn( "The following missing groups are not being deleted as allowDeletions == false"); for (String group : groupDeletionCandidates) { TenantChainingUserRegistrySynchronizer.logger.warn(" " + group); } } // Complete association deletion information by // scanning deleted groups BatchProcessor<String> groupScanner = new BatchProcessor<String>( zone + " Missing Authority Scanning", TenantChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.deletionCandidates, TenantChainingUserRegistrySynchronizer.this.workerThreads, 20, TenantChainingUserRegistrySynchronizer.this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.logger, TenantChainingUserRegistrySynchronizer.this.loggingInterval); groupScanner.process(new BaseBatchProcessWorker<String>() { @Override public String getIdentifier(String entry) { return entry; } @Override public void process(final String authority) throws Throwable { AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { proceesInTenantMode(zoneId, authority); return null; } }, SEIPTenantIntegration.getSystemUserByTenantId(tenantId)); } private void proceesInTenantMode(final String zoneId, String authority) { // Disassociate it from this zone, allowing // it to be reclaimed by something further // down the chain TenantChainingUserRegistrySynchronizer.this.authorityService .removeAuthorityFromZones(authority, Collections.singleton(zoneId)); // For groups, remove all members if (AuthorityType.getAuthorityType(authority) != AuthorityType.USER) { String groupShortName = TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(authority); String groupDisplayName = TenantChainingUserRegistrySynchronizer.this.authorityService .getAuthorityDisplayName(authority); NodeDescription dummy = new NodeDescription(groupShortName + " (Deleted)"); PropertyMap dummyProperties = dummy.getProperties(); dummyProperties.put(ContentModel.PROP_AUTHORITY_NAME, authority); if (groupDisplayName != null) { dummyProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, groupDisplayName); } updateGroup(dummy, true); } } }, 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 BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>( zone + " Group Creation and Association Deletion", TenantChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.groupParentAssocsToDelete.entrySet(), TenantChainingUserRegistrySynchronizer.this.workerThreads, 20, TenantChainingUserRegistrySynchronizer.this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.logger, TenantChainingUserRegistrySynchronizer.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(final Map.Entry<String, Set<String>> entry) throws Throwable { AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { processInternal(zoneSet, entry.getKey()); return null; } }, SEIPTenantIntegration.getSystemUserByTenantId(tenantId)); } private void processInternal(final Set<String> zoneSet, String child) { String groupDisplayName = Analyzer.this.groupsToCreate.get(child); if (groupDisplayName != null) { String groupShortName = TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(child); if (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { TenantChainingUserRegistrySynchronizer.logger .debug("Creating group '" + groupShortName + "'"); } // create the group TenantChainingUserRegistrySynchronizer.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); } } public 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()) { BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>( zone + " Group Association Creation", TenantChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.groupParentAssocsToCreate.entrySet(), TenantChainingUserRegistrySynchronizer.this.workerThreads, 20, TenantChainingUserRegistrySynchronizer.this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.logger, TenantChainingUserRegistrySynchronizer.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 { final String user = entry.getKey(); AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { maintainAssociationCreations(user); return null; } }, SEIPTenantIntegration.getSystemUser(user)); } }, 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()) { BatchProcessor<Map.Entry<String, Set<String>>> groupCreator = new BatchProcessor<Map.Entry<String, Set<String>>>( zone + " Person Association", TenantChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.personParentAssocsToDelete.entrySet(), TenantChainingUserRegistrySynchronizer.this.workerThreads, 20, TenantChainingUserRegistrySynchronizer.this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.logger, TenantChainingUserRegistrySynchronizer.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(final Map.Entry<String, Set<String>> entry) throws Throwable { final String user = entry.getKey(); AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { maintainAssociationDeletions(user); maintainAssociationCreations(user); return null; } }, SEIPTenantIntegration.getSystemUser(user)); } }, 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 (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { TenantChainingUserRegistrySynchronizer.logger.debug("Removing '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(authorityName) + "' from group '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(parent) + "'"); } TenantChainingUserRegistrySynchronizer.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 (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { for (String groupName : parents) { TenantChainingUserRegistrySynchronizer.logger.debug("Adding '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(authorityName) + "' to group '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(groupName) + "'"); } } try { TenantChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, authorityName); } catch (UnknownAuthorityException e) { // Let's force a transaction retry if a parent doesn't // exist. It may be because we are // waiting for another worker thread to create it throw new ConcurrencyFailureException("Forcing batch retry for unknown authority", e); } catch (InvalidNodeRefException e) { // Another thread may have written the node, but it is // not visible to this transaction // 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); } } } } 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(TenantChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns); lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (TenantChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { if (lastModified == null) { TenantChainingUserRegistrySynchronizer.logger .info("Retrieving all users from user registry '" + zone + "'"); } else { TenantChainingUserRegistrySynchronizer.logger.info( "Retrieving users changed since " + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'"); } } final BatchProcessor<NodeDescription> personProcessor = new BatchProcessor<NodeDescription>( zone + " User Creation and Association", this.transactionService.getRetryingTransactionHelper(), userRegistry.getPersons(lastModified), this.workerThreads, 10, this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.logger, this.loggingInterval); 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(final 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 = (String) personProperties.get(ContentModel.PROP_USERNAME); String ou = (String) personProperties.get(ContentModel.PROP_ORGANIZATION); if (SEIPTenantIntegration.isValidTenant(ou) && !personName.endsWith(ou)) { personName += ("@" + ou); person.getProperties().put(ContentModel.PROP_USERNAME, personName); } final String personFullName = personName; logger.debug("Check user: " + personFullName); String tenantDomain = SEIPTenantIntegration.getTenantId(personFullName); if (tenantDomain != null && !tenantDomain.isEmpty()) { if (!isTenantEnabled(tenantDomain)) { logger.debug("Tenant is missing/disabled for user: " + personFullName); return; } logger.debug("Process user: " + personFullName); AuthenticationUtil.runAs(new RunAsWork<Void>() { @Override public Void doWork() throws Exception { processInTenantMode(personFullName, person); return null; } }, SEIPTenantIntegration.getSystemUserByTenantId(tenantDomain)); } else { logger.debug("Process user: " + personFullName); processInTenantMode(personFullName, person); } } private void processInTenantMode(String personName, NodeDescription person) { HashMap<QName, Serializable> personProperties = person.getProperties(); // Make a mutable copy of the person properties, since they get // written back to by person service Set<String> zones = TenantChainingUserRegistrySynchronizer.this.authorityService .getAuthorityZones(personName); if (zones == null) { // The person did not exist at all if (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { TenantChainingUserRegistrySynchronizer.logger.debug("Creating user '" + personName + "'"); } TenantChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet); } else if (zones.contains(zoneId)) { // The person already existed in this zone: update the // person if (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { TenantChainingUserRegistrySynchronizer.logger.debug("Updating user '" + personName + "'"); } TenantChainingUserRegistrySynchronizer.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()) { // The person exists, but in a different zone. Either // deletions are disallowed or the zone is // not in the authentication chain. May be due to // upgrade or zone changes. Let's re-zone them if (TenantChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { TenantChainingUserRegistrySynchronizer.logger.warn("Updating user '" + personName + "'. This user will in future be assumed to originate from user registry '" + zone + "'."); } updateAuthorityZones(personName, zones, zoneSet); TenantChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, personProperties, false); } else { // The person existed, but in a zone with lower // precedence if (TenantChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { TenantChainingUserRegistrySynchronizer.logger.warn("Recreating occluded user '" + personName + "'. This user was previously created through synchronization with a lower priority user registry."); } TenantChainingUserRegistrySynchronizer.this.personService.deletePerson(personName); TenantChainingUserRegistrySynchronizer.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(TenantChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, latestTime, splitTxns); } latestTime = persons.getLatestTime(); if (latestTime != -1) { setMostRecentUpdateTime(TenantChainingUserRegistrySynchronizer.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()) { BatchProcessor<String> authorityDeletionProcessor = new BatchProcessor<String>( zone + " Authority Deletion", this.transactionService.getRetryingTransactionHelper(), deletionCandidates, this.workerThreads, 10, this.applicationEventPublisher, TenantChainingUserRegistrySynchronizer.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 (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { TenantChainingUserRegistrySynchronizer.logger .debug("Deleting user '" + authority + "'"); } TenantChainingUserRegistrySynchronizer.this.personService.deletePerson(authority); synchronized (this) { this.personProcessedCount++; } } else { if (TenantChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { TenantChainingUserRegistrySynchronizer.logger.debug("Deleting group '" + TenantChainingUserRegistrySynchronizer.this.authorityService .getShortName(authority) + "'"); } TenantChainingUserRegistrySynchronizer.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); if (TenantChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { TenantChainingUserRegistrySynchronizer.logger .info("Finished synchronizing users and groups with user registry '" + zone + "'"); TenantChainingUserRegistrySynchronizer.logger .info(personProcessedCount + " user(s) and " + groupProcessedCount + " group(s) processed"); } }
From source file:org.cggh.repo.security.sync.CustomChainingUserRegistrySynchronizer.java
/** * Synchronizes local groups and users with a {@link UserRegistry} for a particular zone, optionally handling * deletions.//w w w .j ava 2s .co m * * @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(CustomChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns); Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (CustomChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { if (lastModified == null) { CustomChainingUserRegistrySynchronizer.logger .info("Retrieving all groups from user registry '" + zone + "'"); } else { CustomChainingUserRegistrySynchronizer.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, CustomChainingUserRegistrySynchronizer.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 = CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(groupName); Set<String> groupZones = CustomChainingUserRegistrySynchronizer.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()) { // Deletions are disallowed or the group exists, but not in a zone that's in the authentication // chain. May be due to upgrade or zone changes. Let's re-zone them if (CustomChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { CustomChainingUserRegistrySynchronizer.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 (CustomChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { CustomChainingUserRegistrySynchronizer.logger.warn("Recreating occluded group '" + groupShortName + "'. This group was previously created through synchronization with a lower priority user registry."); } CustomChainingUserRegistrySynchronizer.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 : CustomChainingUserRegistrySynchronizer.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 = CustomChainingUserRegistrySynchronizer.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 = CustomChainingUserRegistrySynchronizer.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 CustomChainingUserRegistrySynchronizer.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); } } } } // Mark as created if new else { // 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()) { CustomChainingUserRegistrySynchronizer.logger.warn("Not adding group '" + CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(group) + "' to group '" + CustomChainingUserRegistrySynchronizer.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 CustomChainingUserRegistrySynchronizer.this.personService.getUserNamesAreCaseSensitive() ? new TreeSet<String>() : new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); } private Map<String, Set<String>> newPersonMap() { return CustomChainingUserRegistrySynchronizer.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 (CustomChainingUserRegistrySynchronizer.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(CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(parent)) .append('\''); } CustomChainingUserRegistrySynchronizer.logger .debug("Ignoring non-existent member '" + CustomChainingUserRegistrySynchronizer.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 CustomChainingUserRegistrySynchronizer.this.transactionService.getRetryingTransactionHelper() .doInTransaction(new RetryingTransactionCallback<Void>() { public Void execute() throws Throwable { allZonePersons .addAll(CustomChainingUserRegistrySynchronizer.this.authorityService .getAllAuthoritiesInZone(zoneId, AuthorityType.USER)); allZoneGroups .addAll(CustomChainingUserRegistrySynchronizer.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", CustomChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.deletionCandidates, CustomChainingUserRegistrySynchronizer.this.workerThreads, 20, CustomChainingUserRegistrySynchronizer.this.applicationEventPublisher, CustomChainingUserRegistrySynchronizer.logger, CustomChainingUserRegistrySynchronizer.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), CustomChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.groupParentAssocsToDelete.entrySet(), CustomChainingUserRegistrySynchronizer.this.workerThreads, 20, CustomChainingUserRegistrySynchronizer.this.applicationEventPublisher, CustomChainingUserRegistrySynchronizer.logger, CustomChainingUserRegistrySynchronizer.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 = CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(child); if (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { CustomChainingUserRegistrySynchronizer.logger .debug("Creating group '" + groupShortName + "'"); } // create the group CustomChainingUserRegistrySynchronizer.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), CustomChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.groupParentAssocsToCreate.entrySet(), CustomChainingUserRegistrySynchronizer.this.workerThreads, 20, CustomChainingUserRegistrySynchronizer.this.applicationEventPublisher, CustomChainingUserRegistrySynchronizer.logger, CustomChainingUserRegistrySynchronizer.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), CustomChainingUserRegistrySynchronizer.this.transactionService .getRetryingTransactionHelper(), this.personParentAssocsToDelete.entrySet(), CustomChainingUserRegistrySynchronizer.this.workerThreads, 20, CustomChainingUserRegistrySynchronizer.this.applicationEventPublisher, CustomChainingUserRegistrySynchronizer.logger, CustomChainingUserRegistrySynchronizer.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 (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { CustomChainingUserRegistrySynchronizer.logger.debug("Removing '" + CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(authorityName) + "' from group '" + CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(parent) + "'"); } CustomChainingUserRegistrySynchronizer.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 (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { for (String groupName : parents) { CustomChainingUserRegistrySynchronizer.logger.debug("Adding '" + CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(authorityName) + "' to group '" + CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(groupName) + "'"); } } try { CustomChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, authorityName); } catch (UnknownAuthorityException e) { // Let's force a transaction retry if a parent doesn't exist. It may be because we are // waiting for another worker thread to create it throw new ConcurrencyFailureException("Forcing batch retry for unknown authority", e); } catch (InvalidNodeRefException e) { // Another thread may have written the node, but it is not visible to this transaction // 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(CustomChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns); lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (CustomChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { if (lastModified == null) { CustomChainingUserRegistrySynchronizer.logger .info("Retrieving all users from user registry '" + zone + "'"); } else { CustomChainingUserRegistrySynchronizer.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, CustomChainingUserRegistrySynchronizer.logger, this.loggingInterval); 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); // for invalid names will throw ConstraintException that will be catched by BatchProcessor$TxnCallback nameChecker.evaluate(personName); Set<String> zones = CustomChainingUserRegistrySynchronizer.this.authorityService .getAuthorityZones(personName); if (zones == null) { // The person did not exist at all if (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { CustomChainingUserRegistrySynchronizer.logger.debug("Creating user '" + personName + "'"); } CustomChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet); CustomChainingUserRegistrySynchronizer.this.avatarService.setAvatar(personName, person.getProperties(), getLatestTime()); } else if (zones.contains(zoneId)) { // The person already existed in this zone: update the person if (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { CustomChainingUserRegistrySynchronizer.logger.debug("Updating user '" + personName + "'"); } CustomChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, personProperties, false); CustomChainingUserRegistrySynchronizer.this.avatarService.setAvatar(personName, person.getProperties(), getLatestTime()); } 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()) { // The person exists, but in a different zone. Either deletions are disallowed or the zone is // not in the authentication chain. May be due to upgrade or zone changes. Let's re-zone them if (CustomChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { CustomChainingUserRegistrySynchronizer.logger.warn("Updating user '" + personName + "'. This user will in future be assumed to originate from user registry '" + zone + "'."); } updateAuthorityZones(personName, zones, zoneSet); CustomChainingUserRegistrySynchronizer.this.personService.setPersonProperties(personName, personProperties, false); CustomChainingUserRegistrySynchronizer.this.avatarService.setAvatar(personName, person.getProperties(), getLatestTime()); } else { // The person existed, but in a zone with lower precedence if (CustomChainingUserRegistrySynchronizer.logger.isWarnEnabled()) { CustomChainingUserRegistrySynchronizer.logger.warn("Recreating occluded user '" + personName + "'. This user was previously created through synchronization with a lower priority user registry."); } CustomChainingUserRegistrySynchronizer.this.personService.deletePerson(personName); CustomChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet); CustomChainingUserRegistrySynchronizer.this.avatarService.setAvatar(personName, person.getProperties(), getLatestTime()); } } // 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(CustomChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, latestTime, splitTxns); } latestTime = persons.getLatestTime(); if (latestTime != -1) { setMostRecentUpdateTime(CustomChainingUserRegistrySynchronizer.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, CustomChainingUserRegistrySynchronizer.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 (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { CustomChainingUserRegistrySynchronizer.logger .debug("Deleting user '" + authority + "'"); } CustomChainingUserRegistrySynchronizer.this.personService.deletePerson(authority); synchronized (this) { this.personProcessedCount++; } } else { if (CustomChainingUserRegistrySynchronizer.logger.isDebugEnabled()) { CustomChainingUserRegistrySynchronizer.logger.debug("Deleting group '" + CustomChainingUserRegistrySynchronizer.this.authorityService .getShortName(authority) + "'"); } CustomChainingUserRegistrySynchronizer.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 (CustomChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { CustomChainingUserRegistrySynchronizer.logger .info("Finished synchronizing users and groups with user registry '" + zone + "'"); CustomChainingUserRegistrySynchronizer.logger.info(statusMessage); } notifySyncDirectoryEnd(zone, statusMessage); }
From source file:org.dataconservancy.packaging.tool.impl.DcsBoPackageOntologyServiceImpl.java
/** * Gets the available types for the given artifact, based on it's contextual location in the tree. * /*from w ww . ja v a 2s . c o m*/ * If called on ignored node, take into account ignored children, otherwise do not take into account ignored children. * * @param tree The tree representing the {@link org.dataconservancy.packaging.tool.model.PackageDescription} * @param currentArtifactId The id of the artifact to retrieve the types for. * @return A set of all the available relationships for the artifact. */ @Override public Set<String> getValidTypes(PackageTree tree, String currentArtifactId) throws PackageOntologyException { if (ontology == null) { throw new RuntimeException("Ontology and hierarchical relationship names all have to be specified, " + "before service could be used."); } if (tree == null || currentArtifactId == null) { throw new PackageOntologyException("Package tree and currentArifactId cannot be null"); } //retrieve current Node from nodeMaps PackageNode currentNode = tree.getNodesMap().get(currentArtifactId); //filter out valid types for artifact's filesystem types (ie. directories cannot be files and bytestreams have //to be files Set<String> validTypes = getValidTypesPerFileSystemType(currentNode.getValue().isByteStream()); Set<String> validTypesDictatedByParent; validTypesDictatedByParent = new HashSet<>(); PackageNode parentNode = currentNode.getParentNode(); //if a parent node of with the same artifactId exists in the NodeMap //calculate the valid types based on parent's type if (parentNode != null) { //if node exists, check for its PackageArtifact's type String parentNodeType = parentNode.getValue().getType(); //look up the source target pairs by relationship name: for (String relationshipToParent : DcsBoPackageOntology.relationshipsToParent) { Set<RelationshipSourceTargetPair> sourceTargetPairs = relationshipCatalog.get(relationshipToParent); if (sourceTargetPairs != null) { //For every combination of source-target for the relationship for (RelationshipSourceTargetPair pair : sourceTargetPairs) { if (pair.getTargetType().equals(parentNodeType)) { //add up all valid types determined by this relationship to parent validTypesDictatedByParent.add(pair.getSourceType()); } } } } //By this point, should have a set of types dictated by the parent of the relationship //intersect with all types to get an intermediate set of valid types. //Only intersect the set if validByParent set is not empty if (validTypesDictatedByParent.size() > 0) { validTypes.retainAll(validTypesDictatedByParent); } } // If node is ignored, consider all kids. // If node is not ignored, only consider kids that are not ignored. List<PackageNode> kids; if (currentNode.getValue().isIgnored()) { kids = currentNode.getChildrenNodes(); } else { kids = getKidsWithIgnoredStatus(currentNode, false); } if (kids.size() > 0) { Set<String> validTypesDictatedByChildren; for (PackageNode childNode : kids) { //if node exists, check for its PackageArtifact's type String childNodeType = childNode.getValue().getType(); //If child is not a File type if (!childNodeType.contains("File")) { for (String relationshipToParent : DcsBoPackageOntology.relationshipsToParent) { validTypesDictatedByChildren = new HashSet<>(); //look up the source target pairs by relationship name: Set<RelationshipSourceTargetPair> sourceTargetPairs = relationshipCatalog .get(relationshipToParent); if (sourceTargetPairs != null) { //For every combination of source of the relationship for (RelationshipSourceTargetPair pair : sourceTargetPairs) { //get valid relationship's target if (pair.getSourceType().equals(childNodeType)) { validTypesDictatedByChildren.add(pair.getTargetType()); } } } //If there are some type restriction by the children of this relationship if (validTypesDictatedByChildren.size() > 0) { //intersect the set of types dictate by children with the main validTypes set. validTypes.retainAll(validTypesDictatedByChildren); } } } else { // if child is a file Type //to deal with the isMetadataFor and isMemberOf being interchangeable in the case of Files. validTypesDictatedByChildren = new HashSet<>(); for (String relationshipToParent : DcsBoPackageOntology.relationshipsToParent) { //look up the source target pairs by relationship name: Set<RelationshipSourceTargetPair> sourceTargetPairs = relationshipCatalog .get(relationshipToParent); if (sourceTargetPairs != null) { for (RelationshipSourceTargetPair pair : sourceTargetPairs) { if (pair.getSourceType().contains("File")) { validTypesDictatedByChildren.add(pair.getTargetType()); } } } } //If there are some type restriction by the children of this relationship if (validTypesDictatedByChildren.size() > 0) { //intersect the set of types dictate by children with the main validTypes set. validTypes.retainAll(validTypesDictatedByChildren); } } } } else if (canBeDataItemFile(tree, currentArtifactId)) { validTypes.add(didfComboType); } return validTypes; }