001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workspaces.activities.projects; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Objects; 022import java.util.stream.Stream; 023 024import javax.jcr.RepositoryException; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028 029import org.ametys.core.group.Group; 030import org.ametys.core.group.GroupIdentity; 031import org.ametys.core.group.GroupManager; 032import org.ametys.core.observation.Event; 033import org.ametys.core.user.User; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.core.user.UserManager; 036import org.ametys.plugins.repository.AmetysObject; 037import org.ametys.plugins.repository.activities.Activity; 038import org.ametys.plugins.repository.activities.ActivityType; 039import org.ametys.plugins.repository.query.expression.Expression; 040import org.ametys.plugins.workspaces.ObservationConstants; 041import org.ametys.plugins.workspaces.activities.AbstractWorkspacesActivityType; 042import org.ametys.plugins.workspaces.members.JCRProjectMember; 043import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType; 044import org.ametys.plugins.workspaces.members.ProjectMemberManager; 045import org.ametys.plugins.workspaces.members.ProjectMemberManager.ProjectMember; 046import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper.Frequency; 047import org.ametys.plugins.workspaces.project.objects.Project; 048import org.ametys.runtime.config.Config; 049 050/** 051 * {@link ActivityType} implementation for the addition of a member 052 */ 053public class MemberAddedActivityType extends AbstractProjectsActivityType 054{ 055 /** data name for the member type */ 056 public static final String MEMBER_TYPE = "memberType"; 057 /** data name for the member identity */ 058 public static final String MEMBER = "member"; 059 060 private UserManager _userManager; 061 private GroupManager _groupManager; 062 private ProjectMemberManager _projectMemberManager; 063 064 @Override 065 public void service(ServiceManager serviceManager) throws ServiceException 066 { 067 super.service(serviceManager); 068 _projectMemberManager = (ProjectMemberManager) serviceManager.lookup(ProjectMemberManager.ROLE); 069 _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE); 070 _groupManager = (GroupManager) serviceManager.lookup(GroupManager.ROLE); 071 } 072 073 @Override 074 public boolean support(Event event) 075 { 076 return Config.getInstance().getValue("workspaces.member.added.send.notification", true, false) // Check feature enabled 077 && _isNewUser(event); 078 } 079 080 private boolean _isNewUser(Event event) 081 { 082 Map<String, Object> args = event.getArguments(); 083 Project project = getProjectFromEvent(event); 084 JCRProjectMember member = (JCRProjectMember) args.get(ObservationConstants.ARGS_MEMBER); 085 if (MemberType.USER.equals(member.getType())) 086 { 087 if (_userIsPartOfProjectGroups(member.getUser(), project)) 088 { 089 // if the user is already present via a group, we ignore the event 090 return false; 091 } 092 } 093 return true; 094 } 095 096 private boolean _userIsPartOfProjectGroups(UserIdentity identity, Project project) 097 { 098 boolean alreadyPresent = _projectMemberManager.getProjectMembers(project, false) 099 .stream() 100 // retrieve the group 101 .map(ProjectMember::getGroup) 102 .filter(Objects::nonNull) 103 // retrieve group users 104 .map(Group::getUsers) 105 // check if a group contains our user 106 .anyMatch(users -> users.contains(identity)); 107 return alreadyPresent; 108 } 109 110 @Override 111 public void setAdditionalActivityData(Activity activity, Map<String, Object> parameters) throws RepositoryException 112 { 113 super.setAdditionalActivityData(activity, parameters); 114 115 JCRProjectMember member = (JCRProjectMember) parameters.get(org.ametys.plugins.workspaces.ObservationConstants.ARGS_MEMBER); 116 member = _resolver.resolveById(member.getId()); 117 activity.setValue(MEMBER, MemberType.GROUP == member.getType() 118 ? GroupIdentity.groupIdentityToString(member.getGroup()) 119 : UserIdentity.userIdentityToString(member.getUser())); 120 activity.setValue(MEMBER_TYPE, member.getType().name().toLowerCase()); 121 } 122 123 @Override 124 public Map<String, Object> additionnalDataToJSONForClient(Activity activity) 125 { 126 // Override the MEMBER element to provide the fullName or Label instead of the identity 127 Map<String, Object> json = super.additionnalDataToJSONForClient(activity); 128 129 String memberIdentity = activity.getValue(MEMBER); 130 String type = activity.hasValue(MEMBER_TYPE) ? activity.getValue(MEMBER_TYPE) : MemberType.USER.name(); 131 if (MemberType.GROUP == MemberType.valueOf(type.toUpperCase())) 132 { 133 GroupIdentity groupIdentity = GroupIdentity.stringToGroupIdentity(memberIdentity); 134 Group group = _groupManager.getGroup(groupIdentity); 135 json.put(MEMBER, group != null ? group.getLabel() : memberIdentity); 136 } 137 else if (MemberType.USER == MemberType.valueOf(type.toUpperCase())) 138 { 139 UserIdentity userIdentity = UserIdentity.stringToUserIdentity(memberIdentity); 140 User user = _userManager.getUser(userIdentity); 141 142 json.put(MEMBER, user != null ? user.getFullName() : (userIdentity != null ? userIdentity.getLogin() : memberIdentity)); 143 } 144 json.put("identity", memberIdentity); 145 146 return json; 147 } 148 149 @Override 150 public Expression getFilterPatternExpression(String pattern) 151 { 152 return null; 153 } 154 155 @Override 156 public Map<String, Object> mergeActivities(List<Activity> activities) 157 { 158 Map<String, Object> mergedActivities = super.mergeActivities(activities); 159 160 Map<String, Map<String, Object>> members = new HashMap<>(); 161 162 for (Activity activity : activities) 163 { 164 if (activity.hasValue(MEMBER)) 165 { 166 Map<String, Object> json = activity.toJSONForClient(); 167 String identity = (String) json.get("identity"); 168 if (!members.containsKey(identity)) 169 { 170 Map<String, Object> m = new HashMap<>(); 171 m.put("name", json.get(MEMBER)); 172 m.put("type", json.get(MEMBER_TYPE)); 173 m.put("identity", identity); 174 members.put(identity, m); 175 } 176 } 177 } 178 179 mergedActivities.put("members", members.values()); 180 return mergedActivities; 181 } 182 183 @Override 184 public Project getProjectFromEvent(Event event) 185 { 186 Map<String, Object> args = event.getArguments(); 187 188 return (Project) args.get(ObservationConstants.ARGS_PROJECT); 189 } 190 191 @Override 192 public AmetysObject getTargetAmetysObject(Activity activity) 193 { 194 // we don't need this method as we override the AbstractWorkspacesActivityObserver::getUsersEmailToNotify method 195 return null; 196 } 197 198 /** 199 * Get the user to notify Retrieve the previous user in project by removing 200 * the new user or group from the members 201 */ 202 @Override 203 public List<String> getUsersEmailToNotify(Activity activity) 204 { 205 Stream<User> previousUser = null; 206 Project project = _projectManager.getProject(activity.getValue(PROJECT_NAME)); 207 208 // if the new member is a user, we just need to inform all other user 209 // we have already check that it is in fact a new member 210 MemberType memberType = MemberType.valueOf(activity.<String> getValue(MemberAddedActivityType.MEMBER_TYPE).toUpperCase()); 211 if (MemberType.USER.equals(memberType)) 212 { 213 UserIdentity userIdentity = UserIdentity.stringToUserIdentity(activity.getValue(MemberAddedActivityType.MEMBER)); 214 if (userIdentity != null) 215 { 216 User newUser = _userManager.getUser(userIdentity); 217 if (newUser != null) 218 { 219 previousUser = _projectMemberManager.getProjectMembers(project, true).stream().map(ProjectMember::getUser).filter(u -> !u.equals(newUser)); 220 } 221 } 222 } 223 else 224 { 225 GroupIdentity groupIdentity = GroupIdentity.stringToGroupIdentity(activity.getValue(MemberAddedActivityType.MEMBER)); 226 if (groupIdentity != null) 227 { 228 Group group = _groupManager.getGroup(groupIdentity); 229 if (group != null) 230 { 231 previousUser = _projectMemberManager.getProjectMembers(project, false).stream() 232 // we remove the new group from the project members. 233 .filter(member -> MemberType.USER.equals(member.getType()) || group.equals(member.getGroup())) 234 // now we can expand all the group. If a user from 235 // the new group was already present he shall 236 // receive a notification as an activity was created 237 .flatMap(member -> { 238 if (MemberType.USER.equals(member.getType())) 239 { 240 return Stream.<User> of(member.getUser()); 241 } 242 else 243 { 244 return member.getGroup().getUsers().stream().map(_userManager::getUser); 245 } 246 }); 247 } 248 } 249 } 250 if (previousUser != null) 251 { 252 return previousUser.filter(user -> _notificationPreferenceHelper.askedToBeNotified(user.getIdentity(), activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME), 253 Frequency.EACH)).map(User::getEmail).toList(); 254 } 255 else 256 { 257 // Something bad happened… 258 return List.of(); 259 } 260 } 261 262 263 @Override 264 public String getMailBodyURI(Activity activity) 265 { 266 return "cocoon://_plugins/workspaces/notification-mail-member"; 267 } 268}