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}