diff --git a/src/main/java/org/olat/admin/user/course/CourseOverviewController.java b/src/main/java/org/olat/admin/user/course/CourseOverviewController.java index 103f540bd5480aacd80305d28a503f2df9271f3b..417b41071bf6aaf625d972157a530c317404eb0b 100644 --- a/src/main/java/org/olat/admin/user/course/CourseOverviewController.java +++ b/src/main/java/org/olat/admin/user/course/CourseOverviewController.java @@ -399,7 +399,7 @@ public class CourseOverviewController extends BasicController { MailPackage mailing = new MailPackage(sendMail); if(re != null) { List<RepositoryEntryPermissionChangeEvent> changes = Collections.singletonList((RepositoryEntryPermissionChangeEvent)e); - repositoryManager.updateRepositoryEntryMembership(getIdentity(), ureq.getUserSession().getRoles(), re, changes, mailing); + repositoryManager.updateRepositoryEntryMemberships(getIdentity(), ureq.getUserSession().getRoles(), re, changes, mailing); } businessGroupService.updateMemberships(getIdentity(), e.getGroupChanges(), mailing); @@ -448,7 +448,7 @@ public class CourseOverviewController extends BasicController { } List<RepositoryEntryPermissionChangeEvent> repoChanges = Collections.singletonList(changeEvent); for(RepositoryEntry repoEntry:res) { - repositoryManager.updateRepositoryEntryMembership(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, reMailing); + repositoryManager.updateRepositoryEntryMemberships(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, reMailing); } //make sure all is committed before loading the model again (I see issues without) diff --git a/src/main/java/org/olat/basesecurity/model/IdentityRefImpl.java b/src/main/java/org/olat/basesecurity/model/IdentityRefImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..8d86fee6c06e0de60d4bbad201a5b208714db313 --- /dev/null +++ b/src/main/java/org/olat/basesecurity/model/IdentityRefImpl.java @@ -0,0 +1,58 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.basesecurity.model; + +import org.olat.basesecurity.IdentityRef; + +/** + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class IdentityRefImpl implements IdentityRef { + + private final Long identityKey; + + public IdentityRefImpl(Long identityKey) { + this.identityKey = identityKey; + } + + @Override + public Long getKey() { + return identityKey; + } + + @Override + public int hashCode() { + return identityKey == null ? 86583206 : identityKey.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if(obj instanceof IdentityRefImpl) { + IdentityRefImpl ref = (IdentityRefImpl)obj; + return identityKey != null && identityKey.equals(ref.identityKey); + } + return false; + } +} diff --git a/src/main/java/org/olat/commons/coordinate/cluster/jms/ClusterEventBus.java b/src/main/java/org/olat/commons/coordinate/cluster/jms/ClusterEventBus.java index 2a7e4d88596d8c445a50cefba29f6a76157a2709..6a374464e8f23d0e76c3c0f5ba1d51419ea25505 100644 --- a/src/main/java/org/olat/commons/coordinate/cluster/jms/ClusterEventBus.java +++ b/src/main/java/org/olat/commons/coordinate/cluster/jms/ClusterEventBus.java @@ -222,6 +222,11 @@ public class ClusterEventBus extends AbstractEventBus implements MessageListener final long msgId = ++latestSentMsgId; final Integer nodeId = clusterConfig.getNodeId(); + if(ores != null && ores.getResourceableId() != null + && ores.getResourceableId().equals(0l) && "BusinessGroup".equals(ores.getResourceableTypeName())) { + System.out.println(); + } + jmsExecutor.execute(new Runnable() { public void run() { try { diff --git a/src/main/java/org/olat/core/commons/services/notifications/NotificationsManager.java b/src/main/java/org/olat/core/commons/services/notifications/NotificationsManager.java index c1fca403a7ed2ad56c4c8183c875cf4cc8528078..4c38b37448c3f864943abff7a0d6b12351926095 100644 --- a/src/main/java/org/olat/core/commons/services/notifications/NotificationsManager.java +++ b/src/main/java/org/olat/core/commons/services/notifications/NotificationsManager.java @@ -28,6 +28,7 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import org.olat.basesecurity.IdentityRef; import org.olat.core.gui.translator.Translator; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; @@ -275,7 +276,16 @@ public abstract class NotificationsManager extends BasicManager { * @param types * @return */ - public abstract List<Subscriber> getSubscribers(Identity identity, List<String> types); + public abstract List<Subscriber> getSubscribers(IdentityRef identity, List<String> types); + + /** + * subscribers for ONE person (e.g. subscribed to 5 forums -> 5 subscribers + * belonging to this person) restricted to the specified types + * + * @param identity + * @return List of Subscriber Objects which belong to the identity + */ + public abstract List<Subscriber> getSubscribers(IdentityRef identity, long resId); /** * @param identity @@ -299,6 +309,8 @@ public abstract class NotificationsManager extends BasicManager { public abstract void unsubscribe(Subscriber s); + public abstract void unsubscribeAllForIdentityAndResId(IdentityRef identity, Long resId); + /** * @param identity * @param subscriptionContext diff --git a/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java b/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java index 7150ff1a632087c6f0ed130d9d39717bb6613901..2a11526cda0a5fae1e1767c757405d45d76d3ea2 100644 --- a/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java +++ b/src/main/java/org/olat/core/commons/services/notifications/manager/NotificationsManagerImpl.java @@ -45,6 +45,7 @@ import javax.persistence.TypedQuery; import org.hibernate.FlushMode; import org.olat.NewControllerFactory; import org.olat.basesecurity.BaseSecurity; +import org.olat.basesecurity.IdentityRef; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.DBQuery; @@ -194,6 +195,7 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us * @param identity * @return List of Subscriber Objects which belong to the identity */ + @Override public List<Subscriber> getSubscribers(Identity identity) { return getSubscribers(identity, Collections.<String>emptyList()); } @@ -206,22 +208,45 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us * @return List of Subscriber Objects which belong to the identity */ @Override - public List<Subscriber> getSubscribers(Identity identity, List<String> types) { + public List<Subscriber> getSubscribers(IdentityRef identity, List<String> types) { StringBuilder sb = new StringBuilder(); sb.append("select sub from notisub as sub ") .append("inner join fetch sub.publisher as publisher ") - .append("where sub.identity = :anIdentity"); + .append("where sub.identity.key = :identityKey"); if(types != null && !types.isEmpty()) { sb.append(" and publisher.type in (:types)"); } - TypedQuery<Subscriber> query = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Subscriber.class); - query.setParameter("anIdentity", identity); + TypedQuery<Subscriber> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Subscriber.class) + .setParameter("identityKey", identity.getKey()); if(types != null && !types.isEmpty()) { query.setParameter("types", types); } return query.getResultList(); } + /** + * subscribers for ONE person (e.g. subscribed to 5 forums -> 5 subscribers + * belonging to this person) restricted to the specified Olat resourceable id + * + * @param identity + * @param resId + * @return List of Subscriber Objects which belong to the identity + */ + @Override + public List<Subscriber> getSubscribers(IdentityRef identity, long resId) { + StringBuilder sb = new StringBuilder(); + sb.append("select sub from notisub as sub ") + .append("inner join fetch sub.publisher as publisher ") + .append("where sub.identity.key = :identityKey") + .append(" and publisher.resId = :resId)"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Subscriber.class) + .setParameter("identityKey", identity.getKey()) + .setParameter("resId", resId) + .getResultList(); + } + /** * @param identity * @return a list of all subscribers which belong to the identity and which @@ -893,6 +918,14 @@ public class NotificationsManagerImpl extends NotificationsManager implements Us } } + @Override + public void unsubscribeAllForIdentityAndResId(IdentityRef identity, Long resId) { + List<Subscriber> subscribers = getSubscribers(identity, resId.longValue()); + for (Subscriber sub:subscribers) { + unsubscribe (sub); + } + } + /** * @param identity * @param subscriptionContext diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java b/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java index 240aa15002f0f4dc41c56958f914bb396172bc58..b6ec61df1b0b99e927f387b1660ef8f8fd27850d 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentModeCoordinationServiceImpl.java @@ -27,7 +27,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import org.olat.basesecurity.IdentityRef; +import org.olat.basesecurity.model.IdentityRefImpl; import org.olat.core.commons.persistence.DB; import org.olat.core.gui.control.Event; import org.olat.core.logging.OLog; @@ -157,12 +157,7 @@ public class AssessmentModeCoordinationServiceImpl implements AssessmentModeCoor } private void sendEventAfterMembershipChange(final Long identityKey) { - List<AssessmentMode> modes = assessmentModeManager.getAssessmentModeFor(new IdentityRef() { - @Override - public Long getKey() { - return identityKey; - } - }); + List<AssessmentMode> modes = assessmentModeManager.getAssessmentModeFor(new IdentityRefImpl(identityKey)); for(AssessmentMode mode:modes) { Status status = mode.getStatus(); if(status == Status.leadtime ) { diff --git a/src/main/java/org/olat/course/assessment/ui/AssessmentModeAdminSettingsController.java b/src/main/java/org/olat/course/assessment/ui/AssessmentModeAdminSettingsController.java index 17438d64e3ab6e10a087aba118fe5529f9749ab0..7b604ae5501a6af2ed849c040ed836a622bef815 100644 --- a/src/main/java/org/olat/course/assessment/ui/AssessmentModeAdminSettingsController.java +++ b/src/main/java/org/olat/course/assessment/ui/AssessmentModeAdminSettingsController.java @@ -1,3 +1,22 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ package org.olat.course.assessment.ui; import org.olat.core.gui.UserRequest; diff --git a/src/main/java/org/olat/course/member/MembersOverviewController.java b/src/main/java/org/olat/course/member/MembersOverviewController.java index 92f6bd6baabf7703b2729c17715be2c90c4c197e..81733df69f196ecb213dd8121c82419e3970e63a 100644 --- a/src/main/java/org/olat/course/member/MembersOverviewController.java +++ b/src/main/java/org/olat/course/member/MembersOverviewController.java @@ -312,7 +312,7 @@ public class MembersOverviewController extends BasicController implements Activa MailPackage reMailing = new MailPackage(template, result, getWindowControl().getBusinessControl().getAsString(), template != null); List<RepositoryEntryPermissionChangeEvent> repoChanges = changes.generateRepositoryChanges(members); - repositoryManager.updateRepositoryEntryMembership(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, reMailing); + repositoryManager.updateRepositoryEntryMemberships(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, reMailing); //commit all changes to the group memberships List<BusinessGroupMembershipChange> allModifications = changes.generateBusinessGroupMembershipChange(members); diff --git a/src/main/java/org/olat/course/run/CourseRuntimeController.java b/src/main/java/org/olat/course/run/CourseRuntimeController.java index 28122cbd662f847b69697e1096066d6adce6b652..10ca9f9786e4d1fd268da35ed31fcd5878d8c5bb 100644 --- a/src/main/java/org/olat/course/run/CourseRuntimeController.java +++ b/src/main/java/org/olat/course/run/CourseRuntimeController.java @@ -32,7 +32,6 @@ import org.olat.core.commons.fullWebApp.LayoutMain3ColsController; import org.olat.core.commons.fullWebApp.popup.BaseFullWebappPopupLayoutFactory; import org.olat.core.commons.modules.bc.FolderRunController; import org.olat.core.commons.modules.glossary.GlossaryMainController; -import org.olat.core.commons.persistence.DBFactory; import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.commons.services.mark.Mark; import org.olat.core.gui.UserRequest; @@ -995,8 +994,6 @@ public class CourseRuntimeController extends RepositoryEntryRuntimeController im repositoryManager.leave(getIdentity(), getRepositoryEntry(), status, reMailing); //leave groups businessGroupService.leave(getIdentity(), getRepositoryEntry(), status, reMailing); - //reload this but make sure all changes are committed before reload - DBFactory.getInstance().commit(); if(status.isWarningManagedGroup() || status.isWarningManagedCourse()) { showWarning("sign.out.warning.managed"); diff --git a/src/main/java/org/olat/group/BusinessGroupService.java b/src/main/java/org/olat/group/BusinessGroupService.java index 27eeee4fee7a83e09ab1491afbbca820dc877a9f..67d43fd7d11dfd4389ea2f122df713b10ecf6ee3 100644 --- a/src/main/java/org/olat/group/BusinessGroupService.java +++ b/src/main/java/org/olat/group/BusinessGroupService.java @@ -296,7 +296,7 @@ public interface BusinessGroupService { public void addResourcesTo(List<BusinessGroup> groups, List<RepositoryEntry> resources); - public void removeResourceFrom(List<BusinessGroup> group, RepositoryEntry re); + public void removeResourceFrom(List<BusinessGroup> group, RepositoryEntryRef re); public void removeResource(RepositoryEntryRef resource); diff --git a/src/main/java/org/olat/group/manager/BusinessGroupMembershipProcessor.java b/src/main/java/org/olat/group/manager/BusinessGroupMembershipProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..1f6bcbdeda2ba0e715d48bacedaa1421404efc48 --- /dev/null +++ b/src/main/java/org/olat/group/manager/BusinessGroupMembershipProcessor.java @@ -0,0 +1,126 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.group.manager; + +import java.util.Collections; +import java.util.List; + +import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.IdentityRef; +import org.olat.basesecurity.model.IdentityRefImpl; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.gui.control.Event; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; +import org.olat.core.util.resource.OresHelper; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupRef; +import org.olat.group.model.BGRepositoryEntryRelation; +import org.olat.group.model.BusinessGroupRefImpl; +import org.olat.group.model.BusinessGroupRelationModified; +import org.olat.group.ui.edit.BusinessGroupModifiedEvent; +import org.olat.repository.RepositoryEntryRef; +import org.olat.repository.RepositoryManager; +import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.repository.model.RepositoryEntryRefImpl; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * process event related to membership removed from groups. + * + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class BusinessGroupMembershipProcessor implements InitializingBean, GenericEventListener { + + @Autowired + private CoordinatorManager coordinator; + @Autowired + private NotificationsManager notificationsManager; + @Autowired + private RepositoryManager repositoryManager; + @Autowired + private BusinessGroupRelationDAO businessGroupRelationDao; + @Autowired + private RepositoryEntryRelationDAO repositoryEntryRelationDao; + + @Override + public void afterPropertiesSet() throws Exception { + coordinator.getCoordinator().getEventBus().registerFor(this, null, OresHelper.lookupType(BusinessGroup.class)); + } + + @Override + public void event(Event event) { + if(event instanceof BusinessGroupModifiedEvent) { + BusinessGroupModifiedEvent e = (BusinessGroupModifiedEvent)event; + if(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT.equals(e.getCommand())) { + processIdentityRemoved(e.getModifiedGroupKey(), e.getAffectedIdentityKey()); + } + } else if(event instanceof BusinessGroupRelationModified) { + BusinessGroupRelationModified e = (BusinessGroupRelationModified)event; + if(BusinessGroupRelationModified.RESOURCE_REMOVED_EVENT.equals(e.getCommand())) { + processResourceRemoved(e.getGroupKey(), e.getRepositoryEntryKey()); + } + } + } + + private void processResourceRemoved(Long groupKey, Long repoKey) { + BusinessGroupRef groupRef = new BusinessGroupRefImpl(groupKey); + RepositoryEntryRef entryRef = new RepositoryEntryRefImpl(repoKey); + OLATResource resource = repositoryManager.lookupRepositoryEntryResource(entryRef.getKey()); + + List<Long> memberKeys = businessGroupRelationDao + .getMemberKeys(Collections.singletonList(groupRef), GroupRoles.coach.name(), GroupRoles.participant.name()); + for(Long memberKey:memberKeys) { + IdentityRef member = new IdentityRefImpl(memberKey); + List<String> remaingRoles = repositoryEntryRelationDao.getRoles(member, entryRef); + if(remaingRoles.isEmpty()) { + notificationsManager.unsubscribeAllForIdentityAndResId(member, resource.getResourceableId()); + } + } + } + + private void processIdentityRemoved(Long groupKey, Long identityKey) { + IdentityRef identityRef = new IdentityRefImpl(identityKey); + BusinessGroupRef groupRef = new BusinessGroupRefImpl(groupKey); + + if(!businessGroupRelationDao.hasAnyRole(identityRef, groupRef)) { + notificationsManager.unsubscribeAllForIdentityAndResId(identityRef, groupRef.getKey()); + + List<BGRepositoryEntryRelation> relations = businessGroupRelationDao + .findRelationToRepositoryEntries(Collections.singletonList(groupKey), 0, -1); + for(BGRepositoryEntryRelation relation:relations) { + Long repositoryEntryKey = relation.getRepositoryEntryKey(); + RepositoryEntryRef entryRef = new RepositoryEntryRefImpl(repositoryEntryKey); + List<String> remaingRoles = repositoryEntryRelationDao.getRoles(identityRef, entryRef); + if(remaingRoles.isEmpty()) { + OLATResource resource = repositoryManager.lookupRepositoryEntryResource(entryRef.getKey()); + notificationsManager.unsubscribeAllForIdentityAndResId(identityRef, resource.getResourceableId()); + } + } + } + } +} diff --git a/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java b/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java index 2ab194a79680556f7b803530c12e72ae4781ba05..3bc0fd280553298fd7f8580d4270e7da914cc10f 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupRelationDAO.java @@ -117,7 +117,7 @@ public class BusinessGroupRelationDAO { .getResultList(); } - public int countRoles(BusinessGroup group, String... role) { + public int countRoles(BusinessGroupRef group, String... role) { StringBuilder sb = new StringBuilder(); sb.append("select count(membership) from ").append(BusinessGroupImpl.class.getName()).append(" as bgroup ") .append(" inner join bgroup.baseGroup as baseGroup") @@ -183,6 +183,20 @@ public class BusinessGroupRelationDAO { return count == null ? false : count.intValue() > 0; } + public boolean hasAnyRole(IdentityRef identity, BusinessGroupRef group) { + StringBuilder sb = new StringBuilder(); + sb.append("select count(membership) from ").append(BusinessGroupImpl.class.getName()).append(" as bgroup ") + .append(" inner join bgroup.baseGroup as baseGroup") + .append(" inner join baseGroup.members as membership") + .append(" where bgroup.key=:businessGroupKey and membership.identity.key=:identityKey"); + + Number count = dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Number.class) + .setParameter("businessGroupKey", group.getKey()) + .setParameter("identityKey", identity.getKey()) + .getSingleResult(); + return count == null ? false : count.intValue() > 0; + } + public void touchMembership(IdentityRef identity, BusinessGroupRef group) { StringBuilder sb = new StringBuilder(); sb.append("select membership from ").append(BusinessGroupImpl.class.getName()).append(" as bgroup ") @@ -252,7 +266,7 @@ public class BusinessGroupRelationDAO { return members; } - public void deleteRelation(BusinessGroup group, RepositoryEntry entry) { + public void deleteRelation(BusinessGroup group, RepositoryEntryRef entry) { repositoryEntryRelationDao.removeRelation(group.getBaseGroup(), entry); } diff --git a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java index 8164dc6b136fa35b51154ee43982626dd695c7ab..ecb3cd915968336d05ce4f4aada8f8b1870d78c3 100644 --- a/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java +++ b/src/main/java/org/olat/group/manager/BusinessGroupServiceImpl.java @@ -48,7 +48,6 @@ import org.olat.collaboration.CollaborationToolsFactory; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.services.notifications.NotificationsManager; -import org.olat.core.commons.services.notifications.Subscriber; import org.olat.core.id.Identity; import org.olat.core.id.Roles; import org.olat.core.logging.DBRuntimeException; @@ -71,7 +70,6 @@ import org.olat.core.util.resource.OLATResourceableJustBeforeDeletedEvent; import org.olat.core.util.resource.OresHelper; import org.olat.group.BusinessGroup; import org.olat.group.BusinessGroupAddResponse; -import org.olat.group.BusinessGroupImpl; import org.olat.group.BusinessGroupManagedFlag; import org.olat.group.BusinessGroupMembership; import org.olat.group.BusinessGroupModule; @@ -92,6 +90,7 @@ import org.olat.group.model.BusinessGroupMembershipChange; import org.olat.group.model.BusinessGroupMembershipImpl; import org.olat.group.model.BusinessGroupMembershipViewImpl; import org.olat.group.model.BusinessGroupMembershipsChanges; +import org.olat.group.model.BusinessGroupRelationModified; import org.olat.group.model.EnrollState; import org.olat.group.model.IdentityGroupKey; import org.olat.group.model.LeaveOption; @@ -810,6 +809,7 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD params.setIdentity(identity); params.setAttendee(true); List<BusinessGroup> groups = businessGroupDAO.findBusinessGroups(params, entry, 0, -1); + List<BusinessGroupModifiedEvent.Deferred> events = new ArrayList<BusinessGroupModifiedEvent.Deferred>(); for(BusinessGroup group:groups) { if(BusinessGroupManagedFlag.isManaged(group, BusinessGroupManagedFlag.membersmanagement)) { status.setWarningManagedGroup(true); @@ -819,6 +819,8 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD removeParticipant(identity, identity, group, mailing, null); } } + dbInstance.commit(); + BusinessGroupModifiedEvent.fireDeferredEvents(events); } @Override @@ -1012,9 +1014,6 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD boolean removed = businessGroupRelationDAO.removeRole(identity, group, GroupRoles.participant.name()); if(removed) { - //remove subscriptions if user gets removed - removeSubscriptions(identity, group); - // notify currently active users of this business group BusinessGroupModifiedEvent.Deferred event = BusinessGroupModifiedEvent.createDeferredEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group, identity); if(events != null) { @@ -1459,15 +1458,12 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD businessGroupRelationDAO.removeRole(identityToRemove, group, GroupRoles.coach.name()); - //remove subsciptions if user gets removed - removeSubscriptions(identityToRemove, group); - // notify currently active users of this business group BusinessGroupModifiedEvent.Deferred event; if (identityToRemove.getKey().equals(ureqIdentity.getKey()) ) { event = BusinessGroupModifiedEvent.createDeferredEvent(BusinessGroupModifiedEvent.MYSELF_ASOWNER_REMOVED_EVENT, group, identityToRemove); } else { - event = BusinessGroupModifiedEvent.createDeferredEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group, identityToRemove); + event = BusinessGroupModifiedEvent.createDeferredEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, group, identityToRemove); } if(events != null) { events.add(event); @@ -1488,19 +1484,6 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD BusinessGroupModifiedEvent.fireDeferredEvents(events); } - private void removeSubscriptions(Identity identity, BusinessGroup group) { - NotificationsManager notiMgr = NotificationsManager.getInstance(); - List<Subscriber> l = notiMgr.getSubscribers(identity); - for (Iterator<Subscriber> iterator = l.iterator(); iterator.hasNext();) { - Subscriber subscriber = iterator.next(); - Long resId = subscriber.getPublisher().getResId(); - Long groupKey = group.getKey(); - if (resId != null && groupKey != null && resId.equals(groupKey)) { - notiMgr.unsubscribe(subscriber); - } - } - } - @Override public boolean hasResources(BusinessGroup group) { return businessGroupRelationDAO.countResources(group) > 0; @@ -1636,29 +1619,34 @@ public class BusinessGroupServiceImpl implements BusinessGroupService, UserDataD } @Override - public void removeResourceFrom(List<BusinessGroup> groups, RepositoryEntry re) { + public void removeResourceFrom(List<BusinessGroup> groups, RepositoryEntryRef re) { if(groups == null || groups.isEmpty()) { return; // nothing to do } + List<BusinessGroupRelationModified> events = new ArrayList<BusinessGroupRelationModified>(); + int count = 0; for(BusinessGroup group:groups) { businessGroupRelationDAO.deleteRelation(group, re); + events.add(new BusinessGroupRelationModified(BusinessGroupRelationModified.RESOURCE_REMOVED_EVENT, group.getKey(), re.getKey())); if(count++ % 20 == 0) { dbInstance.commit(); } } dbInstance.commit(); + + for(BusinessGroupRelationModified event:events) { + CoordinatorManager.getInstance().getCoordinator().getEventBus() + .fireEventToListenersOf(event, OresHelper.lookupType(BusinessGroup.class)); + } } @Override - public void removeResource(RepositoryEntryRef resource) { + public void removeResource(RepositoryEntryRef re) { SearchBusinessGroupParams params = new SearchBusinessGroupParams(); - List<BusinessGroup> groups = findBusinessGroups(params, resource, 0, -1); - for(BusinessGroup group:groups) { - repositoryEntryRelationDao.removeRelation(((BusinessGroupImpl)group).getBaseGroup(), resource); - } - dbInstance.commit(); + List<BusinessGroup> groups = findBusinessGroups(params, re, 0, -1); + removeResourceFrom(groups, re); } @Override diff --git a/src/main/java/org/olat/group/model/BusinessGroupRefImpl.java b/src/main/java/org/olat/group/model/BusinessGroupRefImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5c1214e4bca0d55d047ad3d4b7ed4bf70659f909 --- /dev/null +++ b/src/main/java/org/olat/group/model/BusinessGroupRefImpl.java @@ -0,0 +1,58 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.group.model; + +import org.olat.group.BusinessGroupRef; + +/** + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public final class BusinessGroupRefImpl implements BusinessGroupRef { + + private final Long groupKey; + + public BusinessGroupRefImpl(Long groupKey) { + this.groupKey = groupKey; + } + + @Override + public Long getKey() { + return groupKey; + } + + @Override + public int hashCode() { + return groupKey == null ? 98376802 : groupKey.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if(obj instanceof BusinessGroupRefImpl) { + BusinessGroupRefImpl ref = (BusinessGroupRefImpl)obj; + return groupKey != null && groupKey.equals(ref.groupKey); + } + return false; + } +} diff --git a/src/main/java/org/olat/group/model/BusinessGroupRelationModified.java b/src/main/java/org/olat/group/model/BusinessGroupRelationModified.java new file mode 100644 index 0000000000000000000000000000000000000000..bedc18df7cdc2fd42aed5b2946e850dd26a89571 --- /dev/null +++ b/src/main/java/org/olat/group/model/BusinessGroupRelationModified.java @@ -0,0 +1,52 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.group.model; + +import org.olat.core.util.event.MultiUserEvent; + +/** + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BusinessGroupRelationModified extends MultiUserEvent { + + private static final long serialVersionUID = 4694938528412441148L; + + public static final String RESOURCE_REMOVED_EVENT = "resource.removed.event"; + + private Long groupKey; + private Long repositoryEntryKey; + + public BusinessGroupRelationModified(String cmd, Long groupKey, Long repositoryEntryKey) { + super(cmd); + this.groupKey = groupKey; + this.repositoryEntryKey = repositoryEntryKey; + } + + public Long getGroupKey() { + return groupKey; + } + + public Long getRepositoryEntryKey() { + return repositoryEntryKey; + } +} diff --git a/src/main/java/org/olat/group/ui/main/AbstractMemberListController.java b/src/main/java/org/olat/group/ui/main/AbstractMemberListController.java index 33e39333f92b6f586b539905fd201953961829a3..2fcb1cec311002599d59e72ef246a911e561ca76 100644 --- a/src/main/java/org/olat/group/ui/main/AbstractMemberListController.java +++ b/src/main/java/org/olat/group/ui/main/AbstractMemberListController.java @@ -462,7 +462,7 @@ public abstract class AbstractMemberListController extends BasicController imple MailPackage mailing = new MailPackage(sendMail); if(repoEntry != null) { List<RepositoryEntryPermissionChangeEvent> changes = Collections.singletonList((RepositoryEntryPermissionChangeEvent)e); - repositoryManager.updateRepositoryEntryMembership(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, changes, mailing); + repositoryManager.updateRepositoryEntryMemberships(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, changes, mailing); } businessGroupService.updateMemberships(getIdentity(), e.getGroupChanges(), mailing); @@ -475,7 +475,7 @@ public abstract class AbstractMemberListController extends BasicController imple MailPackage mailing = new MailPackage(sendMail); if(repoEntry != null) { List<RepositoryEntryPermissionChangeEvent> repoChanges = changes.generateRepositoryChanges(members); - repositoryManager.updateRepositoryEntryMembership(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, mailing); + repositoryManager.updateRepositoryEntryMemberships(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, mailing); } //commit all changes to the group memberships diff --git a/src/main/java/org/olat/repository/RepositoryManager.java b/src/main/java/org/olat/repository/RepositoryManager.java index 87b558ec2dad0939c46feecdd8d17ddc5dae3618..8cfadd46551881a590b757705adaa04669b8b77b 100644 --- a/src/main/java/org/olat/repository/RepositoryManager.java +++ b/src/main/java/org/olat/repository/RepositoryManager.java @@ -54,6 +54,7 @@ import org.olat.core.commons.persistence.PersistenceHelper; import org.olat.core.commons.services.image.ImageService; import org.olat.core.commons.services.image.Size; import org.olat.core.commons.services.mark.impl.MarkImpl; +import org.olat.core.commons.services.notifications.NotificationsManager; import org.olat.core.gui.UserRequest; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; @@ -66,7 +67,11 @@ import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.manager.BasicManager; import org.olat.core.util.FileUtils; import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.EventBus; +import org.olat.core.util.event.MultiUserEvent; import org.olat.core.util.mail.MailPackage; +import org.olat.core.util.resource.OresHelper; import org.olat.core.util.vfs.LocalFolderImpl; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSItem; @@ -77,6 +82,7 @@ import org.olat.group.GroupLoggingAction; import org.olat.repository.manager.RepositoryEntryRelationDAO; import org.olat.repository.model.RepositoryEntryLifecycle; import org.olat.repository.model.RepositoryEntryMembership; +import org.olat.repository.model.RepositoryEntryMembershipModifiedEvent; import org.olat.repository.model.RepositoryEntryPermissionChangeEvent; import org.olat.repository.model.RepositoryEntrySecurity; import org.olat.repository.model.RepositoryEntryShortImpl; @@ -123,6 +129,8 @@ public class RepositoryManager extends BasicManager { private ACReservationDAO reservationDao; @Autowired private LifeFullIndexer lifeIndexer; + @Autowired + private NotificationsManager notificationsManager; /** * @return Singleton. @@ -821,21 +829,6 @@ public class RepositoryManager extends BasicManager { .append(" where v.access > 0 and (") .append(" membership.identity.key=:editorKey and membership.role='").append(GroupRoles.owner.name()).append("'") .append(" )"); - /* - //TODO groups match policy - .append(" and ((") - .append(" ownerGroup in (select ownerSgmsi.securityGroup from ").append(SecurityGroupMembershipImpl.class.getName()).append(" ownerSgmsi where ownerSgmsi.identity.key=:editorKey)") - .append(" ) or (") - .append(" reResource in (select groupRelation.resource from ").append(BGResourceRelation.class.getName()).append(" as groupRelation, ") - .append(" ").append(SecurityGroupMembershipImpl.class.getName()).append(" as sgmsi,") - .append(" ").append(PolicyImpl.class.getName()).append(" as poi,") - .append(" ").append(OLATResourceImpl.class.getName()).append(" as ori") - .append(" where sgmsi.identity.key = :editorKey and sgmsi.securityGroup = poi.securityGroup") - .append(" and poi.permission = 'bgr.editor' and poi.olatResource = ori") - .append(" and groupRelation.resource=ori") - .append(" )") - .append(" ))"); - */ if(resourceTypes != null && resourceTypes.length > 0) { query.append(" and reResource.resName in (:resnames)"); @@ -1430,12 +1423,23 @@ public class RepositoryManager extends BasicManager { return dbQuery; } + /** + * Leave the course, commit to the database and send events + * + * @param identity + * @param re + * @param status + * @param mailing + */ public void leave(Identity identity, RepositoryEntry re, LeavingStatusList status, MailPackage mailing) { if(RepositoryEntryManagedFlag.isManaged(re, RepositoryEntryManagedFlag.membersmanagement)) { status.setWarningManagedCourse(true); } else { - List<Identity> removeIdentities = Collections.singletonList(identity); - removeParticipants(identity, removeIdentities, re, mailing, true); + List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); + removeParticipant(identity, identity, re, mailing, true); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); + dbInstance.commit(); + sendDeferredEvents(deferredEvents, re); } } @@ -1477,20 +1481,39 @@ public class RepositoryManager extends BasicManager { * @param logger */ public void removeOwners(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re){ - for (Identity identity : removeIdentities) { - repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.owner.name()); + List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); + + for (Identity identity : removeIdentities) { + removeOwner(ureqIdentity, identity, re); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); + } + + dbInstance.commit(); + sendDeferredEvents(deferredEvents, re); + } + + private void sendDeferredEvents(List<? extends MultiUserEvent> events, OLATResourceable ores) { + EventBus eventBus = CoordinatorManager.getInstance().getCoordinator().getEventBus(); + for(MultiUserEvent event:events) { + eventBus.fireEventToListenersOf(event, ores); + eventBus.fireEventToListenersOf(event, OresHelper.lookupType(RepositoryEntry.class)); + } + } + + private void removeOwner(Identity ureqIdentity, Identity identity, RepositoryEntry re) { + repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.owner.name()); - ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); - ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); - try{ - ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), - LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry), LoggingResourceable.wrap(identity)); - } finally { - ThreadLocalUserActivityLogger.setStickyActionType(actionType); - } - logAudit("Identity(.key):" + ureqIdentity.getKey() + " removed identity '" + identity.getName() - + "' from repositoryentry with key " + re.getKey()); - } + + ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); + ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); + try{ + ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), + LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry), LoggingResourceable.wrap(identity)); + } finally { + ThreadLocalUserActivityLogger.setStickyActionType(actionType); + } + logAudit("Identity(.key):" + ureqIdentity.getKey() + " removed identity '" + identity.getName() + + "' from repositoryentry with key " + re.getKey()); } public void acceptPendingParticipation(Identity ureqIdentity, Identity identityToAdd, OLATResource resource, ResourceReservation reservation) { @@ -1583,21 +1606,29 @@ public class RepositoryManager extends BasicManager { * @param re * @param logger */ - public void removeTutors(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re){ + public void removeTutors(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re) { + List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for (Identity identity : removeIdentities) { - repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.coach.name()); - - ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); - ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); - try{ - ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), - LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry), LoggingResourceable.wrap(identity)); - } finally { - ThreadLocalUserActivityLogger.setStickyActionType(actionType); - } - logAudit("Identity(.key):" + ureqIdentity.getKey() + " removed identity '" + identity.getName() - + "' from repositoryentry with key " + re.getKey()); + removeTutor(ureqIdentity, identity, re); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); + } + dbInstance.commit(); + sendDeferredEvents(deferredEvents, re); + } + + private void removeTutor(Identity ureqIdentity, Identity identity, RepositoryEntry re) { + repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.coach.name()); + + ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); + ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); + try{ + ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), + LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry), LoggingResourceable.wrap(identity)); + } finally { + ThreadLocalUserActivityLogger.setStickyActionType(actionType); } + logAudit("Identity(.key):" + ureqIdentity.getKey() + " removed identity '" + identity.getName() + + "' from repositoryentry with key " + re.getKey()); } /** @@ -1674,24 +1705,32 @@ public class RepositoryManager extends BasicManager { * @param logger */ public void removeParticipants(Identity ureqIdentity, List<Identity> removeIdentities, RepositoryEntry re, MailPackage mailing, boolean sendMail) { + List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for (Identity identity : removeIdentities) { - repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.participant.name()); - - if(sendMail) { - RepositoryMailing.sendEmail(ureqIdentity, identity, re, RepositoryMailing.Type.removeParticipant, mailing); - } + removeParticipant(ureqIdentity, identity, re, mailing, sendMail); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); + } + dbInstance.commit(); + sendDeferredEvents(deferredEvents, re); + } + + private void removeParticipant(Identity ureqIdentity, Identity identity, RepositoryEntry re, MailPackage mailing, boolean sendMail) { + repositoryEntryRelationDao.removeRole(identity, re, GroupRoles.participant.name()); + + if(sendMail) { + RepositoryMailing.sendEmail(ureqIdentity, identity, re, RepositoryMailing.Type.removeParticipant, mailing); + } - ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); - ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); - try{ - ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), - LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry), LoggingResourceable.wrap(identity)); - } finally { - ThreadLocalUserActivityLogger.setStickyActionType(actionType); - } - logAudit("Identity(.key):" + ureqIdentity.getKey() + " removed identity '" + identity.getName() - + "' from repositoryentry with key " + re.getKey()); + ActionType actionType = ThreadLocalUserActivityLogger.getStickyActionType(); + ThreadLocalUserActivityLogger.setStickyActionType(ActionType.admin); + try{ + ThreadLocalUserActivityLogger.log(GroupLoggingAction.GROUP_OWNER_REMOVED, getClass(), + LoggingResourceable.wrap(re, OlatResourceableType.genRepoEntry), LoggingResourceable.wrap(identity)); + } finally { + ThreadLocalUserActivityLogger.setStickyActionType(actionType); } + logAudit("Identity(.key):" + ureqIdentity.getKey() + " removed identity '" + identity.getName() + + "' from repositoryentry with key " + re.getKey()); } /** @@ -1722,6 +1761,14 @@ public class RepositoryManager extends BasicManager { } boolean allOk = repositoryEntryRelationDao.removeMembers(re, members); + if (allOk) { + List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); + for(Identity identity:members) { + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(identity, re)); + } + dbInstance.commit(); + sendDeferredEvents(deferredEvents, re); + } if (allOk) { // do logging - not optimal but StringBuilder sb = new StringBuilder(); @@ -2137,31 +2184,46 @@ public class RepositoryManager extends BasicManager { return entries; } - public void updateRepositoryEntryMembership(Identity ureqIdentity, Roles ureqRoles, RepositoryEntry re, + public void updateRepositoryEntryMemberships(Identity ureqIdentity, Roles ureqRoles, RepositoryEntry re, List<RepositoryEntryPermissionChangeEvent> changes, MailPackage mailing) { + + List<RepositoryEntryMembershipModifiedEvent> deferredEvents = new ArrayList<>(); for(RepositoryEntryPermissionChangeEvent e:changes) { - if(e.getRepoOwner() != null) { - if(e.getRepoOwner().booleanValue()) { - addOwners(ureqIdentity, new IdentitiesAddEvent(e.getMember()), re); - } else { - removeOwners(ureqIdentity, Collections.singletonList(e.getMember()), re); - } + updateRepositoryEntryMembership(ureqIdentity, ureqRoles, re, e, mailing, deferredEvents); + } + + dbInstance.commit(); + sendDeferredEvents(deferredEvents, re); + } + + private void updateRepositoryEntryMembership(Identity ureqIdentity, Roles ureqRoles, RepositoryEntry re, + RepositoryEntryPermissionChangeEvent changes, MailPackage mailing, + List<RepositoryEntryMembershipModifiedEvent> deferredEvents) { + + if(changes.getRepoOwner() != null) { + if(changes.getRepoOwner().booleanValue()) { + addOwners(ureqIdentity, new IdentitiesAddEvent(changes.getMember()), re); + } else { + removeOwner(ureqIdentity, changes.getMember(), re); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(changes.getMember(), re)); } - - if(e.getRepoTutor() != null) { - if(e.getRepoTutor().booleanValue()) { - addTutors(ureqIdentity, ureqRoles, new IdentitiesAddEvent(e.getMember()), re, mailing); - } else { - removeTutors(ureqIdentity, Collections.singletonList(e.getMember()), re); - } + } + + if(changes.getRepoTutor() != null) { + if(changes.getRepoTutor().booleanValue()) { + addTutors(ureqIdentity, ureqRoles, new IdentitiesAddEvent(changes.getMember()), re, mailing); + } else { + removeTutor(ureqIdentity, changes.getMember(), re); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(changes.getMember(), re)); } - - if(e.getRepoParticipant() != null) { - if(e.getRepoParticipant().booleanValue()) { - addParticipants(ureqIdentity, ureqRoles, new IdentitiesAddEvent(e.getMember()), re, mailing); - } else { - removeParticipants(ureqIdentity, Collections.singletonList(e.getMember()), re, mailing, true); - } + } + + if(changes.getRepoParticipant() != null) { + if(changes.getRepoParticipant().booleanValue()) { + addParticipants(ureqIdentity, ureqRoles, new IdentitiesAddEvent(changes.getMember()), re, mailing); + } else { + removeParticipant(ureqIdentity, changes.getMember(), re, mailing, true); + deferredEvents.add(RepositoryEntryMembershipModifiedEvent.removed(changes.getMember(), re)); } } } diff --git a/src/main/java/org/olat/repository/manager/RepositoryEntryMembershipProcessor.java b/src/main/java/org/olat/repository/manager/RepositoryEntryMembershipProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..03169c542f617752b9af7bd64c62b885189e67fe --- /dev/null +++ b/src/main/java/org/olat/repository/manager/RepositoryEntryMembershipProcessor.java @@ -0,0 +1,89 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.repository.manager; + +import java.util.List; + +import org.olat.basesecurity.IdentityRef; +import org.olat.basesecurity.model.IdentityRefImpl; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.gui.control.Event; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.olat.core.util.event.GenericEventListener; +import org.olat.core.util.resource.OresHelper; +import org.olat.group.manager.BusinessGroupRelationDAO; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryRef; +import org.olat.repository.RepositoryManager; +import org.olat.repository.model.RepositoryEntryMembershipModifiedEvent; +import org.olat.repository.model.RepositoryEntryRefImpl; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * Process the removed membership of repository entries. + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service +public class RepositoryEntryMembershipProcessor implements InitializingBean, GenericEventListener { + + @Autowired + private CoordinatorManager coordinator; + @Autowired + private NotificationsManager notificationsManager; + @Autowired + private RepositoryManager repositoryManager; + @Autowired + private BusinessGroupRelationDAO businessGroupRelationDao; + @Autowired + private RepositoryEntryRelationDAO repositoryEntryRelationDao; + + @Override + public void afterPropertiesSet() throws Exception { + coordinator.getCoordinator().getEventBus().registerFor(this, null, OresHelper.lookupType(RepositoryEntry.class)); + } + + @Override + public void event(Event event) { + if(event instanceof RepositoryEntryMembershipModifiedEvent) { + RepositoryEntryMembershipModifiedEvent e = (RepositoryEntryMembershipModifiedEvent)event; + if(RepositoryEntryMembershipModifiedEvent.IDENTITY_REMOVED.equals(e.getCommand())) { + processIdentityRemoved(e.getRepositoryEntryKey(), e.getIdentityKey()); + } + } + } + + private void processIdentityRemoved(Long repoKey, Long identityKey) { + IdentityRef identity = new IdentityRefImpl(identityKey); + RepositoryEntryRef re = new RepositoryEntryRefImpl(repoKey); + + List<String> remainingRoles = repositoryEntryRelationDao.getRoles(identity, re); + if(remainingRoles.isEmpty()) { + OLATResource resource = repositoryManager.lookupRepositoryEntryResource(repoKey); + notificationsManager.unsubscribeAllForIdentityAndResId(identity, resource.getResourceableId()); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/repository/model/RepositoryEntryMembershipModifiedEvent.java b/src/main/java/org/olat/repository/model/RepositoryEntryMembershipModifiedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..a41033967ec6fd7ca7855e3d8e9c5e4e1803bbbf --- /dev/null +++ b/src/main/java/org/olat/repository/model/RepositoryEntryMembershipModifiedEvent.java @@ -0,0 +1,63 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.repository.model; + +import org.olat.basesecurity.IdentityRef; +import org.olat.core.util.event.MultiUserEvent; +import org.olat.repository.RepositoryEntryRef; + +/** + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RepositoryEntryMembershipModifiedEvent extends MultiUserEvent { + + private static final long serialVersionUID = -8624039692057985920L; + + public static final String IDENTITY_REMOVED = "identity.removed.re"; + private Long identityKey; + private Long repositoryEntryKey; + + + /** + * @param command one of the class constants + * @param group + * @param identity + */ + public RepositoryEntryMembershipModifiedEvent(String command, Long identityKey, Long repositoryEntryKey) { + super(command); + this.identityKey = identityKey; + this.repositoryEntryKey = repositoryEntryKey; + } + + public Long getIdentityKey() { + return identityKey; + } + + public Long getRepositoryEntryKey() { + return repositoryEntryKey; + } + + public static RepositoryEntryMembershipModifiedEvent removed(IdentityRef identity, RepositoryEntryRef re) { + return new RepositoryEntryMembershipModifiedEvent(IDENTITY_REMOVED, identity.getKey(), re.getKey()); + } +} diff --git a/src/main/java/org/olat/repository/model/RepositoryEntryRefImpl.java b/src/main/java/org/olat/repository/model/RepositoryEntryRefImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ad010fe21dd6d1dc74e498e420997de769b2a5e6 --- /dev/null +++ b/src/main/java/org/olat/repository/model/RepositoryEntryRefImpl.java @@ -0,0 +1,58 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.repository.model; + +import org.olat.repository.RepositoryEntryRef; + +/** + * + * Initial date: 09.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RepositoryEntryRefImpl implements RepositoryEntryRef { + + private final Long repoKey; + + public RepositoryEntryRefImpl(Long repoKey) { + this.repoKey = repoKey; + } + + @Override + public Long getKey() { + return repoKey; + } + + @Override + public int hashCode() { + return repoKey == null ? -635465 : repoKey.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(this == obj) { + return true; + } else if (obj instanceof RepositoryEntryRefImpl) { + RepositoryEntryRefImpl ref = (RepositoryEntryRefImpl)obj; + return repoKey != null && repoKey.equals(ref.getKey()); + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/repository/ui/author/RepositoryMembersController.java b/src/main/java/org/olat/repository/ui/author/RepositoryMembersController.java index 5911cfda18d65782ceb1d9a9e22aa9ec4e7854e9..417ccba510d4ed6181e321347c75c90e7c865212 100644 --- a/src/main/java/org/olat/repository/ui/author/RepositoryMembersController.java +++ b/src/main/java/org/olat/repository/ui/author/RepositoryMembersController.java @@ -167,7 +167,7 @@ public class RepositoryMembersController extends AbstractMemberListController { MailerResult result = new MailerResult(); MailPackage reMailing = new MailPackage(template, result, getWindowControl().getBusinessControl().getAsString(), template != null); List<RepositoryEntryPermissionChangeEvent> repoChanges = changes.generateRepositoryChanges(members); - repositoryManager.updateRepositoryEntryMembership(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, reMailing); + repositoryManager.updateRepositoryEntryMemberships(getIdentity(), ureq.getUserSession().getRoles(), repoEntry, repoChanges, reMailing); //commit all changes to the group memberships List<BusinessGroupMembershipChange> allModifications = changes.generateBusinessGroupMembershipChange(members); diff --git a/src/test/java/org/olat/group/test/BusinessGroupMembershipProcessorTest.java b/src/test/java/org/olat/group/test/BusinessGroupMembershipProcessorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a01f5203d76f038da0d96792e051bd144f1af4d9 --- /dev/null +++ b/src/test/java/org/olat/group/test/BusinessGroupMembershipProcessorTest.java @@ -0,0 +1,208 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.group.test; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; + +import junit.framework.Assert; + +import org.junit.Test; +import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.manager.GroupDAO; +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.commons.services.notifications.Publisher; +import org.olat.core.commons.services.notifications.PublisherData; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.id.Identity; +import org.olat.core.util.mail.MailPackage; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; +import org.olat.group.manager.BusinessGroupDAO; +import org.olat.group.manager.BusinessGroupMembershipProcessor; +import org.olat.group.manager.BusinessGroupRelationDAO; +import org.olat.group.ui.edit.BusinessGroupModifiedEvent; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.manager.RepositoryEntryRelationDAO; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 10.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class BusinessGroupMembershipProcessorTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private GroupDAO groupDao; + @Autowired + private BusinessGroupDAO businessGroupDao; + @Autowired + private NotificationsManager notificationManager; + @Autowired + private BusinessGroupService businessGroupService; + @Autowired + private BusinessGroupRelationDAO businessGroupRelationDao; + @Autowired + private RepositoryEntryRelationDAO repositoryEntryRelationDao; + @Autowired + private BusinessGroupMembershipProcessor businessGroupMembershipProcessor; + + @Test + public void testUnlinkMemberOfBusinessGroup() { + //create a group with members + Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-1"); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-2"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-3"); + BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "mbr-proc-1", "mbr-proc-desc", -1, -1, false, false, false, false, false); + businessGroupRelationDao.addRole(id1, businessGroup, GroupRoles.participant.name()); + businessGroupRelationDao.addRole(id2, businessGroup, GroupRoles.participant.name()); + + //create a publisher + SubscriptionContext context = new SubscriptionContext(businessGroup, ""); + PublisherData publisherData = new PublisherData("testGroupPublishers", "e.g. something", null); + Publisher publisher = notificationManager.getOrCreatePublisher(context, publisherData); + Assert.assertNotNull(publisher); + dbInstance.commitAndCloseSession(); + + //subscribe + notificationManager.subscribe(coach, context, publisherData); + notificationManager.subscribe(id1, context, publisherData); + notificationManager.subscribe(id2, context, publisherData); + dbInstance.commitAndCloseSession(); + + //remove id1 and check subscription + MailPackage mailing = new MailPackage(false); + List<Identity> identitiesToRemove = Collections.singletonList(id1); + businessGroupService.removeParticipants(coach, identitiesToRemove, businessGroup, mailing); + + //wait for the remove of subscription + waitForCondition(new CheckUnsubscription(id1, context, dbInstance, notificationManager), 5000); + + //check that subscription of id1 was deleted but not the ones of id2 and coach + boolean subscribedId1 = notificationManager.isSubscribed(id1, context); + Assert.assertFalse(subscribedId1); + boolean subscribedId2 = notificationManager.isSubscribed(id2, context); + Assert.assertTrue(subscribedId2); + boolean subscribedCoach = notificationManager.isSubscribed(coach, context); + Assert.assertTrue(subscribedCoach); + } + + @Test + public void testUnlinkMemberOfBusinessGroup_with2Roles() { + //create a group with members + Identity member = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-4"); + BusinessGroup businessGroup = businessGroupDao.createAndPersist(member, "mbr-proc-2", "mbr-proc-desc", -1, -1, false, false, false, false, false); + businessGroupRelationDao.addRole(member, businessGroup, GroupRoles.participant.name()); + + //create a publisher + SubscriptionContext context = new SubscriptionContext(businessGroup, ""); + PublisherData publisherData = new PublisherData("testGroupPublishers", "e.g. something", null); + notificationManager.getOrCreatePublisher(context, publisherData); + notificationManager.subscribe(member, context, publisherData); + dbInstance.commitAndCloseSession(); + + //remove id1 as participant and check subscription + businessGroupRelationDao.removeRole(member, businessGroup, GroupRoles.participant.name()); + dbInstance.commitAndCloseSession(); + + //manually trigger the event + businessGroupMembershipProcessor.event(new BusinessGroupModifiedEvent(BusinessGroupModifiedEvent.IDENTITY_REMOVED_EVENT, businessGroup, member)); + dbInstance.commitAndCloseSession(); + + //check that subscription of member was not deleted because it's still coach + boolean subscribed = notificationManager.isSubscribed(member, context); + Assert.assertTrue(subscribed); + } + + @Test + public void testUnlinkRepositoryEntry() { + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry(); + //create a group with members + Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-1"); + Identity id1 = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-2"); + Identity id2 = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-3"); + BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "mbr-proc-1", "mbr-proc-desc", -1, -1, false, false, false, false, false); + businessGroupRelationDao.addRelationToResource(businessGroup, re); + businessGroupRelationDao.addRole(id1, businessGroup, GroupRoles.participant.name()); + businessGroupRelationDao.addRole(id2, businessGroup, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(coach, re, GroupRoles.owner.name()); + + //create a publisher + SubscriptionContext context = new SubscriptionContext(re.getOlatResource(), ""); + PublisherData publisherData = new PublisherData("testGroupPublishers", "e.g. something", null); + Publisher publisher = notificationManager.getOrCreatePublisher(context, publisherData); + Assert.assertNotNull(publisher); + dbInstance.commitAndCloseSession(); + + //subscribe + notificationManager.subscribe(coach, context, publisherData); + notificationManager.subscribe(id1, context, publisherData); + notificationManager.subscribe(id2, context, publisherData); + dbInstance.commitAndCloseSession(); + + //remove link between group and repository entry + businessGroupService.removeResourceFrom(Collections.singletonList(businessGroup), re); + dbInstance.commitAndCloseSession(); + + //wait for the remove of subscription + waitForCondition(new CheckUnsubscription(id1, context, dbInstance, notificationManager), 5000); + waitForCondition(new CheckUnsubscription(id2, context, dbInstance, notificationManager), 5000); + + //check that subscription of id1 was deleted but not the ones of id2 and coach + boolean subscribedId1 = notificationManager.isSubscribed(id1, context); + Assert.assertFalse(subscribedId1); + boolean subscribedId2 = notificationManager.isSubscribed(id2, context); + Assert.assertFalse(subscribedId2); + boolean subscribedCoach = notificationManager.isSubscribed(coach, context); + Assert.assertTrue(subscribedCoach); + } + + private static class CheckUnsubscription implements Callable<Boolean> { + + private final DB db; + private final NotificationsManager notificationMgr; + + private final Identity identity; + private final SubscriptionContext context; + + public CheckUnsubscription(Identity identity, SubscriptionContext context, DB db, NotificationsManager notificationMgr) { + this.identity = identity; + this.context = context; + this.db = db; + this.notificationMgr = notificationMgr; + } + + @Override + public Boolean call() throws Exception { + boolean subscribed = notificationMgr.isSubscribed(identity, context); + db.commitAndCloseSession(); + return !subscribed; + } + + } +} diff --git a/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java b/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..41a66da1d2dd1a341b4f12d0bf1682a56708efed --- /dev/null +++ b/src/test/java/org/olat/repository/manager/RepositoryEntryMembershipProcessorTest.java @@ -0,0 +1,193 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.repository.manager; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import junit.framework.Assert; + +import org.junit.Test; +import org.olat.basesecurity.GroupRoles; +import org.olat.basesecurity.manager.GroupDAO; +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.services.notifications.NotificationsManager; +import org.olat.core.commons.services.notifications.Publisher; +import org.olat.core.commons.services.notifications.PublisherData; +import org.olat.core.commons.services.notifications.SubscriptionContext; +import org.olat.core.id.Identity; +import org.olat.core.util.mail.MailPackage; +import org.olat.group.BusinessGroup; +import org.olat.group.BusinessGroupService; +import org.olat.group.manager.BusinessGroupDAO; +import org.olat.group.manager.BusinessGroupMembershipProcessor; +import org.olat.group.manager.BusinessGroupRelationDAO; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 10.02.2015<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RepositoryEntryMembershipProcessorTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private GroupDAO groupDao; + @Autowired + private BusinessGroupDAO businessGroupDao; + @Autowired + private NotificationsManager notificationManager; + @Autowired + private BusinessGroupService businessGroupService; + @Autowired + private BusinessGroupRelationDAO businessGroupRelationDao; + @Autowired + private RepositoryManager repositoryManager; + @Autowired + private RepositoryEntryRelationDAO repositoryEntryRelationDao; + @Autowired + private BusinessGroupMembershipProcessor businessGroupMembershipProcessor; + + @Test + public void testRemoveParticipant() { + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry(); + //create a group with members + Identity owner = JunitTestHelper.createAndPersistIdentityAsRndUser("remp-proc-1"); + Identity member = JunitTestHelper.createAndPersistIdentityAsRndUser("remp-proc-2"); + Identity participant = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-3"); + + repositoryEntryRelationDao.addRole(owner, re, GroupRoles.owner.name()); + repositoryEntryRelationDao.addRole(member, re, GroupRoles.coach.name()); + repositoryEntryRelationDao.addRole(member, re, GroupRoles.participant.name()); + repositoryEntryRelationDao.addRole(participant, re, GroupRoles.participant.name()); + + //create a publisher + SubscriptionContext context = new SubscriptionContext(re.getOlatResource(), ""); + PublisherData publisherData = new PublisherData("testGroupPublishers", "e.g. something", null); + Publisher publisher = notificationManager.getOrCreatePublisher(context, publisherData); + Assert.assertNotNull(publisher); + dbInstance.commitAndCloseSession(); + + //subscribe + notificationManager.subscribe(owner, context, publisherData); + notificationManager.subscribe(member, context, publisherData); + notificationManager.subscribe(participant, context, publisherData); + dbInstance.commitAndCloseSession(); + + //remove member and participant as participant of the repo entry + List<Identity> removeIdentities = new ArrayList<>(2); + removeIdentities.add(member); + removeIdentities.add(participant); + MailPackage mailing = new MailPackage(false); + repositoryManager.removeParticipants(owner, removeIdentities, re, mailing, false); + + //wait for the remove of subscription + waitForCondition(new CheckUnsubscription(participant, context, dbInstance, notificationManager), 5000); + sleep(1000); + + //check that subscription of id1 was deleted but not the ones of id2 and coach + boolean subscribedPart = notificationManager.isSubscribed(participant, context); + Assert.assertFalse(subscribedPart); + boolean subscribedMember = notificationManager.isSubscribed(member, context); + Assert.assertTrue(subscribedMember); + boolean subscribedOwner = notificationManager.isSubscribed(owner, context); + Assert.assertTrue(subscribedOwner); + } + + @Test + public void testRemoveCoach_withBusinessGroups() { + RepositoryEntry re = JunitTestHelper.createAndPersistRepositoryEntry(); + //create a group with members + Identity owner = JunitTestHelper.createAndPersistIdentityAsRndUser("remp-proc-1"); + Identity member = JunitTestHelper.createAndPersistIdentityAsRndUser("remp-proc-2"); + Identity coach = JunitTestHelper.createAndPersistIdentityAsRndUser("mbr-proc-3"); + + repositoryEntryRelationDao.addRole(owner, re, GroupRoles.owner.name()); + repositoryEntryRelationDao.addRole(member, re, GroupRoles.coach.name()); + repositoryEntryRelationDao.addRole(coach, re, GroupRoles.coach.name()); + + BusinessGroup businessGroup = businessGroupDao.createAndPersist(coach, "mbr-proc-1", "mbr-proc-desc", -1, -1, false, false, false, false, false); + businessGroupRelationDao.addRelationToResource(businessGroup, re); + + //create a publisher + SubscriptionContext context = new SubscriptionContext(re.getOlatResource(), ""); + PublisherData publisherData = new PublisherData("testGroupPublishers", "e.g. something", null); + Publisher publisher = notificationManager.getOrCreatePublisher(context, publisherData); + Assert.assertNotNull(publisher); + dbInstance.commitAndCloseSession(); + + //subscribe + notificationManager.subscribe(owner, context, publisherData); + notificationManager.subscribe(member, context, publisherData); + notificationManager.subscribe(coach, context, publisherData); + dbInstance.commitAndCloseSession(); + + //remove member and coach as coach of the repo entry + List<Identity> removeIdentities = new ArrayList<>(2); + removeIdentities.add(member); + removeIdentities.add(coach); + repositoryManager.removeTutors(owner, removeIdentities, re); + + //wait for the remove of subscription + waitForCondition(new CheckUnsubscription(member, context, dbInstance, notificationManager), 5000); + sleep(1000); + + //check that subscription of id1 was deleted but not the ones of id2 and coach + boolean subscribedMember = notificationManager.isSubscribed(member, context); + Assert.assertFalse(subscribedMember); + boolean subscribedCoach = notificationManager.isSubscribed(coach, context); + Assert.assertTrue(subscribedCoach); + boolean subscribedOwner = notificationManager.isSubscribed(owner, context); + Assert.assertTrue(subscribedOwner); + } + + private static class CheckUnsubscription implements Callable<Boolean> { + + private final DB db; + private final NotificationsManager notificationMgr; + + private final Identity identity; + private final SubscriptionContext context; + + public CheckUnsubscription(Identity identity, SubscriptionContext context, DB db, NotificationsManager notificationMgr) { + this.identity = identity; + this.context = context; + this.db = db; + this.notificationMgr = notificationMgr; + } + + @Override + public Boolean call() throws Exception { + boolean subscribed = notificationMgr.isSubscribed(identity, context); + db.commitAndCloseSession(); + return !subscribed; + } + + } + +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 151319e3458690840ef94eadeaca78fc462b0ad2..652e4c483faa499a4c815e039c4fb453cbca7521 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -70,78 +70,80 @@ import org.junit.runners.Suite; org.olat.core.id.IdentityEnvironmentTest.class, org.olat.core.gui.render.VelocityTemplateTest.class, org.olat.core.gui.control.generic.iframe.IFrameDeliveryMapperTest.class, - org.olat.note.NoteTest.class,//ok - org.olat.user.UserPropertiesPerformanceTest.class,//ok + org.olat.note.NoteTest.class, + org.olat.user.UserPropertiesPerformanceTest.class, org.olat.user.EmailCheckPerformanceTest.class,//fail - org.olat.user.UserTest.class,//ok - org.olat.user.UserPropertiesTest.class,//ok - org.olat.commons.calendar.ICalFileCalendarManagerTest.class,//ok - org.olat.commons.calendar.CalendarImportTest.class,//ok - org.olat.commons.calendar.test.CalendarUtilsTest.class,//ok + org.olat.user.UserTest.class, + org.olat.user.UserPropertiesTest.class, + org.olat.commons.calendar.ICalFileCalendarManagerTest.class, + org.olat.commons.calendar.CalendarImportTest.class, + org.olat.commons.calendar.test.CalendarUtilsTest.class, org.olat.commons.lifecycle.LifeCycleManagerTest.class,//fail christian fragen... - org.olat.commons.coordinate.cluster.jms.JMSTest.class,//ok - org.olat.commons.coordinate.cluster.lock.LockTest.class,//ok - org.olat.commons.coordinate.CoordinatorTest.class,//ok - org.olat.core.commons.services.help.spi.ConfluenceLinkSPITest.class,//ok - org.olat.core.commons.services.webdav.WebDAVCommandsTest.class,//ok - org.olat.core.commons.services.webdav.manager.WebDAVManagerTest.class,//ok - org.olat.core.commons.services.webdav.servlets.RequestUtilsTest.class,//ok - org.olat.core.commons.services.taskexecutor.PersistentTaskDAOTest.class,//ok - org.olat.core.commons.services.taskexecutor.TaskExecutorManagerTest.class,//ok - org.olat.admin.user.delete.service.UserDeletionManagerTest.class,//ok - org.olat.group.BusinessGroupManagedFlagsTest.class,//ok - org.olat.group.test.BGRightManagerTest.class,//ok - org.olat.group.test.BGAreaManagerTest.class,//ok - org.olat.group.test.BusinessGroupServiceTest.class,//ok - org.olat.group.test.BusinessGroupDAOTest.class,//ok - org.olat.group.test.BusinessGroupRelationDAOTest.class,//ok - org.olat.group.test.BusinessGroupConcurrentTest.class,//ok - org.olat.group.test.ContactDAOTest.class,//ok - org.olat.fileresource.FileResourceTest.class,//ok - org.olat.resource.lock.pessimistic.PLockTest.class,//ok - org.olat.resource.references.ReferenceManagerTest.class,//ok - org.olat.resource.OLATResourceManagerTest.class,//ok - org.olat.basesecurity.manager.GroupDAOTest.class,//ok - org.olat.basesecurity.SecurityManagerTest.class,//ok - org.olat.basesecurity.GetIdentitiesByPowerSearchTest.class,//ok - org.olat.basesecurity.BaseSecurityManagerTest.class,//ok - org.olat.user.UserManagerTest.class,//ok - org.olat.user.UserNameAndPasswordSyntaxCheckerWithRegexpTest.class,//ok - org.olat.repository.manager.RepositoryEntryDAOTest.class,//ok - org.olat.repository.manager.RepositoryEntryLifecycleDAOTest.class,//ok - org.olat.repository.manager.RepositoryEntryRelationDAOTest.class,//ok - org.olat.repository.manager.RepositoryServiceImplTest.class,//ok - org.olat.repository.manager.RepositoryEntryStatisticsDAOTest.class,//ok - org.olat.repository.manager.RepositoryEntryAuthorQueriesTest.class,//ok - org.olat.repository.manager.RepositoryEntryMyCourseQueriesTest.class,//ok - org.olat.repository.RepositoryManagerTest.class,//ok - org.olat.repository.RepositoryManagerQueryTest.class,//ok - org.olat.instantMessaging.InstantMessageDAOTest.class,//ok - org.olat.instantMessaging.InstantMessagePreferencesDAOTest.class,//ok - org.olat.instantMessaging.RosterDAOTest.class,//ok - org.olat.instantMessaging.InstantMessageServiceTest.class,//ok - org.olat.course.nodes.en.EnrollmentManagerTest.class,//ok - org.olat.course.assessment.AssessmentManagerTest.class,//ok - org.olat.course.assessment.manager.UserCourseInformationsManagerTest.class,//ok - org.olat.course.assessment.manager.AssessmentModeManagerTest.class,//ok + org.olat.commons.coordinate.cluster.jms.JMSTest.class, + org.olat.commons.coordinate.cluster.lock.LockTest.class, + org.olat.commons.coordinate.CoordinatorTest.class, + org.olat.core.commons.services.help.spi.ConfluenceLinkSPITest.class, + org.olat.core.commons.services.webdav.WebDAVCommandsTest.class, + org.olat.core.commons.services.webdav.manager.WebDAVManagerTest.class, + org.olat.core.commons.services.webdav.servlets.RequestUtilsTest.class, + org.olat.core.commons.services.taskexecutor.PersistentTaskDAOTest.class, + org.olat.core.commons.services.taskexecutor.TaskExecutorManagerTest.class, + org.olat.admin.user.delete.service.UserDeletionManagerTest.class, + org.olat.group.BusinessGroupManagedFlagsTest.class, + org.olat.group.test.BGRightManagerTest.class, + org.olat.group.test.BGAreaManagerTest.class, + org.olat.group.test.BusinessGroupServiceTest.class, + org.olat.group.test.BusinessGroupDAOTest.class, + org.olat.group.test.BusinessGroupRelationDAOTest.class, + org.olat.group.test.BusinessGroupConcurrentTest.class, + org.olat.group.test.ContactDAOTest.class, + org.olat.group.test.BusinessGroupMembershipProcessorTest.class, + org.olat.fileresource.FileResourceTest.class, + org.olat.resource.lock.pessimistic.PLockTest.class, + org.olat.resource.references.ReferenceManagerTest.class, + org.olat.resource.OLATResourceManagerTest.class, + org.olat.basesecurity.manager.GroupDAOTest.class, + org.olat.basesecurity.SecurityManagerTest.class, + org.olat.basesecurity.GetIdentitiesByPowerSearchTest.class, + org.olat.basesecurity.BaseSecurityManagerTest.class, + org.olat.user.UserManagerTest.class, + org.olat.user.UserNameAndPasswordSyntaxCheckerWithRegexpTest.class, + org.olat.repository.manager.RepositoryEntryDAOTest.class, + org.olat.repository.manager.RepositoryEntryLifecycleDAOTest.class, + org.olat.repository.manager.RepositoryEntryRelationDAOTest.class, + org.olat.repository.manager.RepositoryServiceImplTest.class, + org.olat.repository.manager.RepositoryEntryStatisticsDAOTest.class, + org.olat.repository.manager.RepositoryEntryAuthorQueriesTest.class, + org.olat.repository.manager.RepositoryEntryMyCourseQueriesTest.class, + org.olat.repository.manager.RepositoryEntryMembershipProcessorTest.class, + org.olat.repository.RepositoryManagerTest.class, + org.olat.repository.RepositoryManagerQueryTest.class, + org.olat.instantMessaging.InstantMessageDAOTest.class, + org.olat.instantMessaging.InstantMessagePreferencesDAOTest.class, + org.olat.instantMessaging.RosterDAOTest.class, + org.olat.instantMessaging.InstantMessageServiceTest.class, + org.olat.course.nodes.en.EnrollmentManagerTest.class, + org.olat.course.assessment.AssessmentManagerTest.class, + org.olat.course.assessment.manager.UserCourseInformationsManagerTest.class, + org.olat.course.assessment.manager.AssessmentModeManagerTest.class, org.olat.course.certificate.manager.CertificatesManagerTest.class, - org.olat.course.config.CourseConfigManagerImplTest.class,//ok - org.olat.course.groupsandrights.CourseGroupManagementTest.class,//ok - org.olat.course.editor.PublishProcessTest.class,//ok - org.olat.course.CourseXStreamAliasesTest.class,//ok - org.olat.modules.iq.IQManagerTest.class,//ok + org.olat.course.config.CourseConfigManagerImplTest.class, + org.olat.course.groupsandrights.CourseGroupManagementTest.class, + org.olat.course.editor.PublishProcessTest.class, + org.olat.course.CourseXStreamAliasesTest.class, + org.olat.modules.iq.IQManagerTest.class, org.olat.modules.fo.ForumManagerTest.class,//fail - org.olat.modules.wiki.WikiUnitTest.class,//ok - org.olat.modules.wiki.versioning.diff.CookbookDiffTest.class,//ok + org.olat.modules.wiki.WikiUnitTest.class, + org.olat.modules.wiki.versioning.diff.CookbookDiffTest.class, org.olat.modules.wiki.gui.components.wikiToHtml.FilterUtilTest.class, - org.olat.modules.coach.manager.CoachingDAOTest.class,//ok - org.olat.modules.coach.CoachingLargeTest.class,//ok - org.olat.properties.PropertyTest.class,//ok + org.olat.modules.coach.manager.CoachingDAOTest.class, + org.olat.modules.coach.CoachingLargeTest.class, + org.olat.properties.PropertyTest.class, org.olat.search.service.document.file.FileDocumentFactoryTest.class, org.olat.search.service.document.file.PDFDocumentTest.class, org.olat.search.service.document.file.OfficeDocumentTest.class, - org.olat.core.commons.services.notifications.manager.NotificationsManagerTest.class,//fail - org.olat.registration.RegistrationManagerTest.class,//ok + org.olat.core.commons.services.notifications.manager.NotificationsManagerTest.class, + org.olat.registration.RegistrationManagerTest.class, org.olat.course.nodes.projectbroker.ProjectBrokerManagerTest.class, org.olat.core.commons.persistence.DBTest.class, org.olat.modules.ims.cp.CPManagerTest.class, diff --git a/src/test/java/org/olat/test/OlatTestCase.java b/src/test/java/org/olat/test/OlatTestCase.java index c22d8388817aac93a9bffd07331d2588afc4919c..20c597d5da58ac2c3db5e1a489872a1e10a6b718 100644 --- a/src/test/java/org/olat/test/OlatTestCase.java +++ b/src/test/java/org/olat/test/OlatTestCase.java @@ -29,6 +29,10 @@ package org.olat.test; import java.io.IOException; import java.util.Enumeration; import java.util.Properties; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.junit.After; import org.junit.Before; @@ -137,6 +141,41 @@ public abstract class OlatTestCase extends AbstractJUnit4SpringContextTests { } + protected boolean waitForCondition(final Callable<Boolean> condition, final int timeoutInMilliseconds) { + final CountDownLatch countDown = new CountDownLatch(1); + final AtomicBoolean result = new AtomicBoolean(false); + + new Thread(){ + @Override + public void run() { + + try { + int numOfTry = (timeoutInMilliseconds / 100) + 2; + for(int i=0; i<numOfTry; i++) { + Boolean test = condition.call(); + if(test != null && test.booleanValue()) { + result.set(true); + break; + } else { + result.set(false); + } + } + } catch (Exception e) { + log.error("", e); + result.set(false); + } + countDown.countDown(); + } + }.start(); + + try { + countDown.await(timeoutInMilliseconds, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.error("", e); + } + return result.get(); + } + protected void sleep(int milliSeconds) { try { Thread.sleep(milliSeconds);