001/* 002 * Copyright 2019 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.io.ByteArrayOutputStream; 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.context.Context; 025import org.apache.avalon.framework.context.ContextException; 026import org.apache.avalon.framework.context.Contextualizable; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.cocoon.components.ContextHelper; 031import org.apache.cocoon.environment.Request; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.excalibur.source.Source; 034import org.apache.excalibur.source.SourceResolver; 035import org.apache.excalibur.source.SourceUtil; 036 037import org.ametys.core.group.GroupManager; 038import org.ametys.core.observation.AsyncObserver; 039import org.ametys.core.observation.Event; 040import org.ametys.core.user.UserManager; 041import org.ametys.core.util.I18nUtils; 042import org.ametys.core.util.language.UserLanguagesManager; 043import org.ametys.core.util.mail.SendMailHelper; 044import org.ametys.plugins.repository.AmetysObjectResolver; 045import org.ametys.plugins.workspaces.ObservationConstants; 046import org.ametys.plugins.workspaces.WorkspacesHelper; 047import org.ametys.plugins.workspaces.members.ProjectMemberManager; 048import org.ametys.plugins.workspaces.project.ProjectManager; 049import org.ametys.plugins.workspaces.project.notification.preferences.NotificationPreferencesHelper; 050import org.ametys.plugins.workspaces.project.objects.Project; 051import org.ametys.runtime.i18n.I18nizableText; 052import org.ametys.runtime.plugin.component.AbstractLogEnabled; 053import org.ametys.runtime.plugin.component.PluginAware; 054import org.ametys.web.WebConstants; 055import org.ametys.web.population.PopulationContextHelper; 056import org.ametys.web.renderingcontext.RenderingContext; 057import org.ametys.web.renderingcontext.RenderingContextHandler; 058import org.ametys.web.repository.site.Site; 059import org.ametys.web.repository.site.SiteManager; 060 061import jakarta.mail.MessagingException; 062 063/** 064 * Abstract observer for sending mail to members 065 */ 066public abstract class AbstractMemberMailNotifierObserver extends AbstractLogEnabled implements AsyncObserver, PluginAware, Serviceable, Contextualizable 067{ 068 /** The avalon context */ 069 protected Context _context; 070 /** The name of current plugin */ 071 protected String _pluginName; 072 /** The Ametys Object resolver */ 073 protected AmetysObjectResolver _resolver; 074 /** The i18n utils */ 075 protected I18nUtils _i18nUtils; 076 /** The project member manager */ 077 protected ProjectMemberManager _projectMemberManager; 078 /** Project manager */ 079 protected ProjectManager _projectManager; 080 /** Site manager */ 081 protected SiteManager _siteManager; 082 /** Population context helper */ 083 protected PopulationContextHelper _populationContextHelper; 084 /** Source Resolver */ 085 protected SourceResolver _srcResolver; 086 /** User manager */ 087 protected UserManager _userManager; 088 /** Group manager */ 089 protected GroupManager _groupManager; 090 /** The notification preference manager */ 091 protected NotificationPreferencesHelper _notificationPrefHelper; 092 /** The rendering context */ 093 protected RenderingContextHandler _renderingContextHandler; 094 /** The user languages manager */ 095 protected UserLanguagesManager _userLanguagesManager; 096 /** The workspaces helper */ 097 protected WorkspacesHelper _workspacesHelper; 098 099 100 @Override 101 public void setPluginInfo(String pluginName, String featureName, String id) 102 { 103 _pluginName = pluginName; 104 } 105 106 @Override 107 public void contextualize(Context context) throws ContextException 108 { 109 _context = context; 110 } 111 112 public void service(ServiceManager manager) throws ServiceException 113 { 114 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 115 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 116 _projectMemberManager = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 117 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 118 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 119 _populationContextHelper = (PopulationContextHelper) manager.lookup(org.ametys.core.user.population.PopulationContextHelper.ROLE); 120 _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 121 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 122 _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE); 123 _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 124 _userLanguagesManager = (UserLanguagesManager) manager.lookup(UserLanguagesManager.ROLE); 125 _workspacesHelper = (WorkspacesHelper) manager.lookup(WorkspacesHelper.ROLE); 126 _notificationPrefHelper = (NotificationPreferencesHelper) manager.lookup(NotificationPreferencesHelper.ROLE); 127 } 128 129 @Override 130 public int getPriority() 131 { 132 return MIN_PRIORITY; 133 } 134 135 @Override 136 public void observe(Event event, Map<String, Object> transientVars) throws Exception 137 { 138 Map<String, Object> args = event.getArguments(); 139 140 String projectId = (String) args.get(ObservationConstants.ARGS_PROJECT_ID); 141 Project project = _resolver.resolveById(projectId); 142 Site site = project.getSite(); 143 String lang = _workspacesHelper.getLang(project, _userLanguagesManager.getDefaultLanguage()); 144 // Compute subject and body 145 I18nizableText i18nSubject = getI18nSubject(event, project); 146 147 // Send mail to removed members 148 Map<String, List<String>> recipientsByLanguage = getUserToNotifyByLanguage(event, project); 149 150 for (String language : recipientsByLanguage.keySet()) 151 { 152 String userLanguage = StringUtils.defaultIfBlank(language, lang); 153 154 String subject = _i18nUtils.translate(i18nSubject, userLanguage); 155 156 String mailBody = null; 157 Source source = null; 158 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 159 try 160 { 161 // Force rendering context.FRONT to resolve URI 162 _renderingContextHandler.setRenderingContext(RenderingContext.FRONT); 163 Request request = ContextHelper.getRequest(_context); 164 request.setAttribute("forceAbsoluteUrl", true); 165 request.setAttribute("lang", userLanguage); 166 request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site); 167 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName()); 168 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, site.getSkinId()); 169 source = _srcResolver.resolveURI(getMailBodyURI(event, project), null, Map.of("event", event, "project", project)); 170 try (InputStream is = source.getInputStream()) 171 { 172 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 173 SourceUtil.copy(is, bos); 174 175 mailBody = bos.toString("UTF-8"); 176 } 177 } 178 catch (IOException e) 179 { 180 throw new RuntimeException("Failed to create mail body", e); 181 } 182 finally 183 { 184 _renderingContextHandler.setRenderingContext(currentContext); 185 186 if (source != null) 187 { 188 _srcResolver.release(source); 189 } 190 } 191 192 try 193 { 194 SendMailHelper.newMail() 195 .withSubject(subject) 196 .withHTMLBody(mailBody) 197 .withRecipients(recipientsByLanguage.get(language)) 198 .withAsync(true) 199 .withInlineCSS(false) 200 .sendMail(); 201 } 202 catch (MessagingException | IOException e) 203 { 204 getLogger().warn("Could not send a notification e-mail to " + recipientsByLanguage + " following his removal from the project " + project.getTitle(), e); 205 } 206 } 207 } 208 209 /** 210 * Get recipients' emails sorted by language 211 * @param event the event 212 * @param project the project 213 * @return the recipients' emails sorted by language 214 */ 215 protected abstract Map<String, List<String>> getUserToNotifyByLanguage(Event event, Project project); 216 217 /** 218 * Returns the URI for HTML mail body 219 * @param event the event 220 * @param project the project 221 * @return The URI for HTML mail body 222 */ 223 protected String getMailBodyURI(Event event, Project project) 224 { 225 return "cocoon://_plugins/workspaces/notification-mail-member"; 226 } 227 228 /** 229 * Get the {@link I18nizableText} for mail subject 230 * @param event the event 231 * @param project the project 232 * @return the {@link I18nizableText} for subject 233 */ 234 protected abstract I18nizableText getI18nSubject(Event event, Project project); 235}