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