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