001/*
002 *  Copyright 2010 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.project.notification;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Locale;
021import java.util.Map;
022import java.util.Set;
023
024import javax.mail.MessagingException;
025
026import org.apache.avalon.framework.context.Context;
027import org.apache.avalon.framework.context.ContextException;
028import org.apache.avalon.framework.context.Contextualizable;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.cocoon.components.ContextHelper;
033import org.apache.commons.collections.CollectionUtils;
034import org.apache.commons.lang.StringUtils;
035
036import org.ametys.cms.transformation.xslt.ResolveURIComponent;
037import org.ametys.core.observation.Event;
038import org.ametys.core.observation.Observer;
039import org.ametys.core.right.RightManager;
040import org.ametys.core.user.User;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.user.UserManager;
043import org.ametys.core.util.I18nUtils;
044import org.ametys.core.util.mail.SendMailHelper;
045import org.ametys.plugins.explorer.ObservationConstants;
046import org.ametys.plugins.repository.AmetysObject;
047import org.ametys.plugins.repository.AmetysObjectIterator;
048import org.ametys.plugins.repository.AmetysObjectResolver;
049import org.ametys.plugins.workspaces.project.ProjectManager;
050import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
051import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
052import org.ametys.plugins.workspaces.project.objects.Project;
053import org.ametys.runtime.config.Config;
054import org.ametys.runtime.i18n.I18nizableText;
055import org.ametys.runtime.plugin.component.AbstractLogEnabled;
056import org.ametys.runtime.plugin.component.PluginAware;
057import org.ametys.web.renderingcontext.RenderingContext;
058import org.ametys.web.renderingcontext.RenderingContextHandler;
059import org.ametys.web.repository.page.Page;
060
061import com.google.common.collect.Iterables;
062
063/**
064 * {@link Observer} for observing events on resources project
065 */
066public abstract class AbstractSendNotificationObserver extends AbstractLogEnabled implements Observer, Serviceable, Contextualizable, PluginAware
067{
068    /** The Ametys Object Resolver*/
069    protected AmetysObjectResolver _resolver;
070    /** The avalon context */
071    protected Context _context;
072    /** The users manager */
073    protected UserManager _userManager;
074    /** The rights manager */
075    protected RightManager _rightManager;
076    /** The i18n utils */
077    protected I18nUtils _i18nUtils;
078    /** The project manager */
079    protected ProjectManager _projectManager;
080    /** The rendering context handler */
081    protected RenderingContextHandler _renderingContextHandler;
082    /** The name of current plugin */
083    protected String _pluginName;
084    /** The workspace module managers EP */
085    protected WorkspaceModuleExtensionPoint _moduleManagerEP;
086    
087    @Override
088    public void service(ServiceManager manager) throws ServiceException
089    {
090        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
091        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
092        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
093        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
094        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
095        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
096        _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
097    }
098    
099    @Override
100    public void contextualize(Context context) throws ContextException
101    {
102        _context = context;
103    }
104
105    public void setPluginInfo(String pluginName, String featureName, String id)
106    {
107        _pluginName = pluginName;
108    }
109    
110    @Override
111    public int getPriority(Event event)
112    {
113        return Observer.MAX_PRIORITY;
114    }
115    
116    @Override
117    public void observe(Event event, Map<String, Object> transientVars)
118    {
119        UserIdentity userIdentity = event.getIssuer();
120        User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
121        
122        Project project = getProject(event);
123        
124        if (project != null)
125        {
126            notifyEvent(project, event.getId(), event.getArguments(), user);
127        }
128    }
129    
130    /**
131     * Notify email by mail
132     * @param project The project
133     * @param eventId The id of event
134     * @param eventParams The event's arguments
135     * @param issuer The issuer
136     */
137    protected abstract void notifyEvent (Project project, String eventId, Map<String, Object> eventParams, User issuer);
138    
139    /**
140     * Get the project from event
141     * @param event The event
142     * @return the project or null if not found
143     */
144    protected Project getProject (Event event)
145    {
146        Map<String, Object> args = event.getArguments();
147        
148        String targetId = (String) args.get(ObservationConstants.ARGS_ID);
149        String parentID = (String) args.get(ObservationConstants.ARGS_PARENT_ID);
150        
151        AmetysObject object = null;
152        if (parentID != null)
153        {
154            object = _resolver.resolveById(parentID);
155        }
156        else
157        {
158            object = _resolver.resolveById(targetId);
159        }
160        
161        AmetysObject parent = object.getParent();
162        
163        while (parent != null)
164        {
165            if (parent instanceof Project)
166            {
167                return (Project) parent;
168            }
169            
170            parent = parent.getParent();
171        }
172        
173        return null;
174    }
175    
176    /**
177     * Get the common mail parameters
178     * @param project The parent project
179     * @param issuer The user responsible of the action
180     * @param explorerNodeId the id of the explorer node. Can be null.
181     * @return an array of strings used in every mail
182     */
183    protected List<String> getMailCommonParams(Project project, User issuer, String explorerNodeId)
184    {
185        List<String> mailBodyParams = new ArrayList<>();
186        // {0} project title
187        mailBodyParams.add(project.getTitle());
188        // {1} module url
189        mailBodyParams.add(getUrl(project, explorerNodeId));
190        // {2} user full name
191        String login = issuer.getIdentity().getLogin();
192        String populationId = issuer.getIdentity().getPopulationId();
193        mailBodyParams.add(_userManager.getUser(populationId, login).getFullName());
194        // {3} user email
195        mailBodyParams.add(issuer.getEmail());
196
197        return mailBodyParams;
198    }
199    
200    /**
201     * Get the default language to resolve module's page
202     * @return The default language
203     */
204    protected String getDefaultLanguage()
205    {
206        Map objectModel = ContextHelper.getObjectModel(_context);
207        Locale locale = org.apache.cocoon.i18n.I18nUtils.findLocale(objectModel, "locale", null, Locale.getDefault(), true);
208        return locale.getLanguage();
209    }
210    
211    /**
212     * Get the module's page
213     * @param project The project
214     * @param moduleId The module
215     * @return The page or <code>null</code> if not found
216     */
217    protected Page getModulePage(Project project, String moduleId)
218    {
219        AmetysObjectIterator<Page> pages = null;
220        
221        WorkspaceModule module = _moduleManagerEP.getModule(moduleId);
222        if (module != null && _projectManager.isModuleActivated(project, moduleId))
223        {
224            pages = module.getModulePages(project, null).iterator();
225        }
226        
227        String defaultLanguage = getDefaultLanguage();
228        Page firstPage = null;
229        
230        if (pages != null && pages.getSize() > 0)
231        {
232            while (pages.hasNext())
233            {
234                Page page = pages.next();
235                firstPage = page;
236                if (page.getSitemapName().equals(defaultLanguage))
237                {
238                    return page;
239                }
240            }
241        }
242        
243        return firstPage;
244    }
245    
246    /**
247     * Get the absolute full url of module'page
248     * @param project The project
249     * @param moduleId The module. Can not be null
250     * @param objectId The id of concerned object. Can be null.
251     * @return The 
252     */
253    protected String getModuleUrl(Project project, String moduleId, String objectId)
254    {
255        Page modulePage = getModulePage(project, moduleId);
256        if (modulePage != null)
257        {
258            RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
259            
260            try
261            {
262                StringBuilder sb = new StringBuilder();
263                
264                _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
265                
266                sb.append(ResolveURIComponent.resolve("page", modulePage.getId(), false, true));
267                
268                if (objectId != null)
269                {
270                    sb.append("#").append(objectId);
271                }
272                
273                return sb.toString();
274            }
275            finally 
276            {
277                _renderingContextHandler.setRenderingContext(currentContext);
278            }
279        }
280        else
281        {
282            return getProjectUrl(project);
283        }
284    }
285    
286    /**
287     * Get the absolute url of project
288     * @param project The project
289     * @return the project's url
290     */
291    protected String getProjectUrl(Project project)
292    {
293        return Iterables.getFirst(_projectManager.getProjectUrls(project), StringUtils.EMPTY);
294    }
295    
296    
297    /**
298     * Get the URL of project to insert in email body
299     * @param project The project
300     * @param objectId The id of concerned object
301     * @return The full URL
302     */
303    protected abstract String getUrl(Project project, String objectId);
304    
305    /**
306     * Sent an email
307     * @param eventId The id of event
308     * @param recipients The recipients of the mail 
309     * @param mailBodyi18nKey i18n key for the body
310     * @param mailSubjecti18nKey i18n key for the subject
311     * @param mailBodyParams parameters for the body
312     * @param mailSubjectParams parameters for the subject
313     */
314    protected void sendMail(String eventId, List<UserIdentity> recipients, String mailBodyi18nKey, String mailSubjecti18nKey, List<String> mailBodyParams, List<String> mailSubjectParams)
315    {
316        I18nizableText i18nSubject = new I18nizableText("plugin." + _pluginName, mailSubjecti18nKey, mailSubjectParams);
317        String subject = _i18nUtils.translate(i18nSubject);
318            
319        I18nizableText i18nBody = new I18nizableText("plugin." + _pluginName, mailBodyi18nKey, mailBodyParams);
320        String body = _i18nUtils.translate(i18nBody);
321            
322        String sender = Config.getInstance().getValue("smtp.mail.from");
323        
324        for (UserIdentity userIdentity : recipients)
325        {
326            User recipient = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin());
327            if (recipient != null)
328            {
329                String email = recipient.getEmail();
330                if (StringUtils.isNotEmpty(email))
331                {
332                    try
333                    {
334                        SendMailHelper.sendMail(subject, null, body, email, sender, true);
335                    }
336                    catch (MessagingException e)
337                    {
338                        getLogger().warn("Could not send a notification e-mail to " + email, e);
339                    }
340                }
341            }
342        }
343    }
344    
345    /**
346     * Get the users allowed to be notified
347     * @param eventId The id of event
348     * @param object The object on which to test rights
349     * @return The allowed users
350     */
351    protected List<UserIdentity> getUsersToNotify(String eventId, AmetysObject object)
352    {
353        boolean returnAll = Config.getInstance().getValue("runtime.mail.massive.sending");
354        Set<UserIdentity> readAccessUsers = _rightManager.getReadAccessAllowedUsers(object).resolveAllowedUsers(returnAll); 
355        Set<UserIdentity> mailAllowedUsers = _rightManager.getAllowedUsers(getRightIdForNotify(), object).resolveAllowedUsers(returnAll); 
356        
357        return (List<UserIdentity>) CollectionUtils.retainAll(readAccessUsers, mailAllowedUsers);
358    }
359    
360    /**
361     * Get the right to check allowed users to notify by mail
362     * @return the right id to check
363     */
364    protected abstract String getRightIdForNotify();
365    
366    /**
367     * format the path without the root path
368     * @param rootPath The root path to remove
369     * @param path The absolute path
370     * @return the local path
371     */
372    protected String _getPath (String rootPath, String path)
373    {
374        int index = path.indexOf(rootPath);
375        return path.substring(index + rootPath.length());
376    }
377}