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.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025import java.util.function.Function;
026import java.util.stream.Collectors;
027
028import javax.jcr.Repository;
029
030import org.apache.avalon.framework.context.Context;
031import org.apache.avalon.framework.context.ContextException;
032import org.apache.avalon.framework.context.Contextualizable;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.cocoon.components.ContextHelper;
037import org.apache.cocoon.environment.Request;
038import org.apache.commons.lang3.ArrayUtils;
039
040import org.ametys.cms.repository.Content;
041import org.ametys.core.observation.AsyncObserver;
042import org.ametys.core.observation.Event;
043import org.ametys.core.user.User;
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 workspaceActivityType)
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, Set<String>> langAndTokens = new HashMap<>();
150            for (User user : users)
151            {
152                Map<String, Set<String>> tokensForUser = _userPreferencesHelper.getUserImpactedTokens(user.getIdentity(), project, event.getId());
153                for (Entry<String, Set<String>> tokensByLang : tokensForUser.entrySet())
154                {
155                    Set<String> tokens = langAndTokens.computeIfAbsent(tokensByLang.getKey(), __ -> new HashSet<>());
156                    tokens.addAll(tokensByLang.getValue());
157                }
158            }
159            
160            // Get the activities infos for each languages
161            Map<String, Map<String, Object>> translatedNotificationContent = langAndTokens.keySet().stream()
162                    .distinct()
163                    .collect(Collectors.toMap(Function.identity(), lang -> _feedHelper.getActivityInfos(activity2json, projectJson, lang)));
164            
165            for (Entry<String, Set<String>> entry : langAndTokens.entrySet())
166            {
167                String lang = entry.getKey();
168                Set<String> tokens = entry.getValue();
169                Map<String, Object> notificationData = translatedNotificationContent.get(lang);
170                
171                try
172                {
173                    @SuppressWarnings("unchecked")
174                    Map<String, Object> category = (Map<String, Object>) ((Map<String, Object>) notificationData.get("project")).get("category");
175                    String categoryLabel = ((org.ametys.runtime.i18n.I18nizableText) category.get("title")).getLabel();
176                    category.put("title", categoryLabel);
177                }
178                catch (Exception e)
179                {
180                    // Nothing, this is just a hack do pass the notification framework jsonifier
181                }
182                
183                String title = project.getTitle();
184                String message = (String) notificationData.get("short-description");
185                _pushNotificationManager.pushNotifications(title, message, tokens, notificationData);
186            }
187        }
188    }
189    
190    /**
191     * Get the event linked to this event
192     * @param event the event to read
193     * @return the project linked to this event
194     */
195    protected Project getProject(Event event)
196    {
197        Project project = null;
198        Map<String, Object> args = event.getArguments();
199
200        if (args.containsKey("projectName"))
201        {
202            String projectName = (String) args.get("projectName");
203            project = _projectManager.getProject(projectName);
204        }
205        else if (args.containsKey(org.ametys.plugins.workspaces.calendars.ObservationConstants.ARGS_CALENDAR))
206        {
207            Calendar calendar = (Calendar) args.get(org.ametys.plugins.workspaces.calendars.ObservationConstants.ARGS_CALENDAR);
208            project = getProject(calendar);
209        }
210        else if (args.containsKey(org.ametys.plugins.workspaces.ObservationConstants.ARGS_TASK))
211        {
212            Task task = (Task) args.get(org.ametys.plugins.workspaces.ObservationConstants.ARGS_TASK);
213            project = getProject(task);
214        }
215        else if (args.containsKey(ObservationConstants.ARGS_THREAD))
216        {
217            JCRThread thread = (JCRThread) args.get(ObservationConstants.ARGS_THREAD);
218            project = getProject(thread);
219        }
220        else if (args.containsKey(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT))
221        {
222            project = (Project) args.get(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT);
223        }
224        else if (args.containsKey(org.ametys.cms.ObservationConstants.ARGS_CONTENT))
225        {
226            Content content = (Content) args.get(org.ametys.cms.ObservationConstants.ARGS_CONTENT);
227            project = getProject(content);
228            
229            if (project == null)
230            {
231                String[] cTypes = content.getTypes();
232                if (ArrayUtils.contains(cTypes, WorkspacesConstants.PROJECT_ARTICLE_CONTENT_TYPE) && content instanceof WebContent)
233                {
234                    Request request = ContextHelper.getRequest(_context);
235                    String siteName = (String) request.getAttribute("siteName");
236                    List<String> projects = _projectManager.getProjectsForSite(siteName);
237                    if (projects.size() > 0)
238                    {
239                        project = _projectManager.getProject(projects.get(0));
240                    }
241                }
242            }
243        }
244        else if (args.containsKey(ObservationConstants.ARGS_PARENT_ID))
245        {
246            String parentId = (String) args.get(ObservationConstants.ARGS_PARENT_ID);
247            AmetysObject ao = _resolver.resolveById(parentId);
248            
249            project = getProject(ao);
250        }
251        
252        return project;
253    }
254    
255    /**
256     * Get the parent project
257     * @param ao The ametys object
258     * @return The parent project or <code>null</code> if not found
259     */
260    protected Project getProject(AmetysObject ao)
261    {
262        
263        AmetysObject parentAO = _resolver.resolveById(ao.getId());
264        while (parentAO != null)
265        {
266            if (parentAO instanceof Project)
267            {
268                return (Project) parentAO;
269            } 
270            parentAO = parentAO.getParent();
271        }
272        
273        return null;
274    }
275}