001/* 002 * Copyright 2020 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.mobileapp.observer; 017 018import java.util.Collection; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.Map.Entry; 023import java.util.Set; 024import java.util.function.Function; 025import java.util.stream.Collectors; 026 027import javax.jcr.Repository; 028 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.components.ContextHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.commons.lang3.ArrayUtils; 038 039import org.ametys.cms.repository.Content; 040import org.ametys.core.observation.AsyncObserver; 041import org.ametys.core.observation.Event; 042import org.ametys.core.user.User; 043import org.ametys.core.user.UserIdentity; 044import org.ametys.core.user.UserManager; 045import org.ametys.core.user.population.UserPopulationDAO; 046import org.ametys.plugins.explorer.ObservationConstants; 047import org.ametys.plugins.explorer.threads.jcr.JCRThread; 048import org.ametys.plugins.mobileapp.FeedHelper; 049import org.ametys.plugins.mobileapp.PushNotificationManager; 050import org.ametys.plugins.mobileapp.UserPreferencesHelper; 051import org.ametys.plugins.repository.AmetysObject; 052import org.ametys.plugins.repository.AmetysObjectResolver; 053import org.ametys.plugins.repository.activities.Activity; 054import org.ametys.plugins.repository.activities.ActivityType; 055import org.ametys.plugins.repository.activities.ActivityTypeExtensionPoint; 056import org.ametys.plugins.repository.provider.AbstractRepository; 057import org.ametys.plugins.workspaces.WorkspacesConstants; 058import org.ametys.plugins.workspaces.activities.AbstractWorkspacesActivityType; 059import org.ametys.plugins.workspaces.calendars.Calendar; 060import org.ametys.plugins.workspaces.project.ProjectManager; 061import org.ametys.plugins.workspaces.project.objects.Project; 062import org.ametys.plugins.workspaces.tasks.Task; 063import org.ametys.runtime.plugin.component.AbstractLogEnabled; 064import org.ametys.web.repository.content.WebContent; 065 066/** 067 * On validation, test each query to notify impacted users 068 */ 069public class ProjectActivityObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable, Contextualizable 070{ 071 /** Event type extension point */ 072 protected ActivityTypeExtensionPoint _activityTypeExtensionPoint; 073 074 /** Feed helper */ 075 protected FeedHelper _feedHelper; 076 077 /** User Preferences Helper */ 078 protected UserPreferencesHelper _userPreferencesHelper; 079 080 /** Push Notification Manager */ 081 protected PushNotificationManager _pushNotificationManager; 082 083 /** The user manager */ 084 protected UserManager _userManager; 085 086 /** The user population DAO */ 087 protected UserPopulationDAO _userPopulationDAO; 088 089 /** Project Manager */ 090 protected ProjectManager _projectManager; 091 092 /** Context */ 093 protected Context _context; 094 095 /** Ametys Object Resolver */ 096 protected AmetysObjectResolver _resolver; 097 098 /** The repository */ 099 protected Repository _repository; 100 101 @Override 102 public void service(ServiceManager manager) throws ServiceException 103 { 104 _activityTypeExtensionPoint = (ActivityTypeExtensionPoint) manager.lookup(ActivityTypeExtensionPoint.ROLE); 105 _feedHelper = (FeedHelper) manager.lookup(FeedHelper.ROLE); 106 _userPreferencesHelper = (UserPreferencesHelper) manager.lookup(UserPreferencesHelper.ROLE); 107 _pushNotificationManager = (PushNotificationManager) manager.lookup(PushNotificationManager.ROLE); 108 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 109 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 110 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 111 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 112 _repository = (Repository) manager.lookup(AbstractRepository.ROLE); 113 } 114 115 public void contextualize(Context context) throws ContextException 116 { 117 _context = context; 118 } 119 120 public boolean supports(Event event) 121 { 122 return org.ametys.plugins.repository.ObservationConstants.EVENT_ACTIVITY_CREATED.contains(event.getId()); 123 } 124 125 public int getPriority(Event event) 126 { 127 return MAX_PRIORITY; 128 } 129 130 public void observe(Event event, Map<String, Object> transientVars) throws Exception 131 { 132 String activityId = (String) event.getArguments().get(org.ametys.plugins.repository.ObservationConstants.ARGS_ACTIVITY_ID); 133 Activity activity = _resolver.resolveById(activityId); 134 135 ActivityType activityType = activity.getActivityType(); 136 if (activityType instanceof AbstractWorkspacesActivityType) 137 { 138 Project project = _projectManager.getProject(activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME)); 139 getLogger().info("Listing push notification to send for activity on project '{}'", project.getId()); 140 141 Map<String, Object> activity2json = activity.toJSONForClient(); 142 143 Map<String, Object> projectJson = _feedHelper.projectToMap(project); 144 145 List<String> userPopulationsIds = _userPopulationDAO.getUserPopulationsIds(); 146 Collection<User> users = _userManager.getUsersByPopulationIds(userPopulationsIds); 147 148 // Get all map of language => tokens for all users, and generate a new big map language => tokens 149 Map<String, Map<UserIdentity, Set<String>>> langAndTokens = new HashMap<>(); 150 for (User user : users) 151 { 152 UserIdentity userIdentity = user.getIdentity(); 153 Map<String, Set<String>> tokensForUser = _userPreferencesHelper.getUserImpactedTokens(userIdentity, project, activity.getEventType()); 154 for (Entry<String, Set<String>> tokensByLang : tokensForUser.entrySet()) 155 { 156 Map<UserIdentity, Set<String>> tokens = langAndTokens.computeIfAbsent(tokensByLang.getKey(), __ -> new HashMap<>()); 157 tokens.put(userIdentity, tokensByLang.getValue()); 158 } 159 } 160 161 if (getLogger().isDebugEnabled()) 162 { 163 getLogger().debug("Push tokens " + langAndTokens.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue().size()).collect(Collectors.joining(", "))); 164 } 165 166 // Get the activities infos for each languages 167 Map<String, Map<String, Object>> translatedNotificationContent = langAndTokens.keySet().stream() 168 .distinct() 169 .collect(Collectors.toMap(Function.identity(), lang -> _feedHelper.getActivityInfos(activity2json, projectJson, lang))); 170 171 for (Entry<String, Map<UserIdentity, Set<String>>> entry : langAndTokens.entrySet()) 172 { 173 String lang = entry.getKey(); 174 Map<UserIdentity, Set<String>> tokens = entry.getValue(); 175 Map<String, Object> notificationData = translatedNotificationContent.get(lang); 176 177 try 178 { 179 @SuppressWarnings("unchecked") 180 Map<String, Object> category = (Map<String, Object>) ((Map<String, Object>) notificationData.get("project")).get("category"); 181 String categoryLabel = ((org.ametys.runtime.i18n.I18nizableText) category.get("title")).getLabel(); 182 category.put("title", categoryLabel); 183 } 184 catch (Exception e) 185 { 186 // Nothing, this is just a hack do pass the notification framework jsonifier 187 } 188 189 String title = project.getTitle(); 190 String message = (String) notificationData.get("short-description"); 191 _pushNotificationManager.pushNotifications(title, message, tokens, null); 192 } 193 } 194 } 195 196 /** 197 * Get the event linked to this event 198 * @param event the event to read 199 * @return the project linked to this event 200 */ 201 protected Project getProject(Event event) 202 { 203 Project project = null; 204 Map<String, Object> args = event.getArguments(); 205 206 if (args.containsKey("projectName")) 207 { 208 String projectName = (String) args.get("projectName"); 209 project = _projectManager.getProject(projectName); 210 } 211 else if (args.containsKey(org.ametys.plugins.workspaces.calendars.ObservationConstants.ARGS_CALENDAR)) 212 { 213 Calendar calendar = (Calendar) args.get(org.ametys.plugins.workspaces.calendars.ObservationConstants.ARGS_CALENDAR); 214 project = getProject(calendar); 215 } 216 else if (args.containsKey(org.ametys.plugins.workspaces.ObservationConstants.ARGS_TASK)) 217 { 218 Task task = (Task) args.get(org.ametys.plugins.workspaces.ObservationConstants.ARGS_TASK); 219 project = getProject(task); 220 } 221 else if (args.containsKey(ObservationConstants.ARGS_THREAD)) 222 { 223 JCRThread thread = (JCRThread) args.get(ObservationConstants.ARGS_THREAD); 224 project = getProject(thread); 225 } 226 else if (args.containsKey(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT)) 227 { 228 project = (Project) args.get(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT); 229 } 230 else if (args.containsKey(org.ametys.cms.ObservationConstants.ARGS_CONTENT)) 231 { 232 Content content = (Content) args.get(org.ametys.cms.ObservationConstants.ARGS_CONTENT); 233 project = getProject(content); 234 235 if (project == null) 236 { 237 String[] cTypes = content.getTypes(); 238 if (ArrayUtils.contains(cTypes, WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE) && content instanceof WebContent) 239 { 240 Request request = ContextHelper.getRequest(_context); 241 String siteName = (String) request.getAttribute("siteName"); 242 List<String> projects = _projectManager.getProjectsForSite(siteName); 243 if (projects.size() > 0) 244 { 245 project = _projectManager.getProject(projects.get(0)); 246 } 247 } 248 } 249 } 250 else if (args.containsKey(ObservationConstants.ARGS_PARENT_ID)) 251 { 252 String parentId = (String) args.get(ObservationConstants.ARGS_PARENT_ID); 253 AmetysObject ao = _resolver.resolveById(parentId); 254 255 project = getProject(ao); 256 } 257 258 return project; 259 } 260 261 /** 262 * Get the parent project 263 * @param ao The ametys object 264 * @return The parent project or <code>null</code> if not found 265 */ 266 protected Project getProject(AmetysObject ao) 267 { 268 269 AmetysObject parentAO = _resolver.resolveById(ao.getId()); 270 while (parentAO != null) 271 { 272 if (parentAO instanceof Project) 273 { 274 return (Project) parentAO; 275 } 276 parentAO = parentAO.getParent(); 277 } 278 279 return null; 280 } 281}