001/* 002 * Copyright 2022 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.excalibur.source.Source; 033import org.apache.excalibur.source.SourceResolver; 034import org.apache.excalibur.source.SourceUtil; 035 036import org.ametys.core.observation.AsyncObserver; 037import org.ametys.core.observation.Event; 038import org.ametys.core.util.I18nUtils; 039import org.ametys.core.util.mail.SendMailHelper; 040import org.ametys.plugins.repository.AmetysObjectResolver; 041import org.ametys.plugins.repository.ObservationConstants; 042import org.ametys.plugins.repository.activities.Activity; 043import org.ametys.plugins.repository.activities.ActivityType; 044import org.ametys.plugins.workspaces.activities.AbstractWorkspacesActivityType; 045import org.ametys.plugins.workspaces.project.ProjectManager; 046import org.ametys.plugins.workspaces.project.objects.Project; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.runtime.plugin.component.AbstractLogEnabled; 049import org.ametys.runtime.plugin.component.PluginAware; 050import org.ametys.web.WebConstants; 051import org.ametys.web.renderingcontext.RenderingContext; 052import org.ametys.web.renderingcontext.RenderingContextHandler; 053import org.ametys.web.repository.site.Site; 054import org.ametys.web.repository.sitemap.Sitemap; 055 056import jakarta.mail.MessagingException; 057 058/** 059 * Send mail notification to user when a new activity is created 060 */ 061public class NotifyActivityObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable, PluginAware, Contextualizable 062{ 063 /** the name used to store the activity in the context parameter when generating the notification */ 064 public static final String ACTIVITY_CONTEXT_PARAM = "activity"; 065 066 private AmetysObjectResolver _resolver; 067 private ProjectManager _projectManager; 068 private RenderingContextHandler _renderingContextHandler; 069 private SourceResolver _srcResolver; 070 private I18nUtils _i18nUtils; 071 072 private Context _context; 073 074 private String _pluginName; 075 076 public void service(ServiceManager manager) throws ServiceException 077 { 078 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 079 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 080 _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 081 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 082 _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 083 } 084 085 public void setPluginInfo(String pluginName, String featureName, String id) 086 { 087 _pluginName = pluginName; 088 } 089 090 public void contextualize(Context context) throws ContextException 091 { 092 _context = context; 093 } 094 095 public boolean supports(Event event) 096 { 097 return ObservationConstants.EVENT_ACTIVITY_CREATED.equals(event.getId()); 098 } 099 100 public int getPriority(Event event) 101 { 102 return MAX_PRIORITY; 103 } 104 105 public void observe(Event event, Map<String, Object> transientVars) throws Exception 106 { 107 Map<String, Object> arguments = event.getArguments(); 108 String activityId = (String) arguments.get(ObservationConstants.ARGS_ACTIVITY_ID); 109 Activity activity = _resolver.resolveById(activityId); 110 ActivityType activityType = activity.getActivityType(); 111 if (activityType instanceof AbstractWorkspacesActivityType workspacesActivityType) 112 { 113 List<String> recipients = workspacesActivityType.getUsersEmailToNotify(activity); 114 if (!recipients.isEmpty()) 115 { 116 notify(activity, workspacesActivityType, recipients); 117 } 118 } 119 } 120 121 /** 122 * Notify email by mail 123 * @param activity the activity 124 * @param activityType the activityType of the activity 125 * @param recipients The users to notify 126 */ 127 protected void notify(Activity activity, AbstractWorkspacesActivityType activityType, List<String> recipients) 128 { 129 String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME); 130 Project project = _projectManager.getProject(projectName); 131 // we don't need to retrieve the project through the activity as we already have retrieved it for the creation 132 Site site = project.getSite(); 133 // retrieve the lang through the activity to match summary implementation 134 String lang = site.getSitemaps().stream().findFirst().map(Sitemap::getName).orElse(null); 135 // Subject 136 String subject = getSubject(activity, activityType, lang); 137 138 // Body 139 String mailBody = getMailBody(activity, activityType, site, lang); 140 141 try 142 { 143 SendMailHelper.newMail() 144 .withRecipients(recipients) 145 .withSubject(subject) 146 .withHTMLBody(mailBody) 147 .withAsync(true) 148 .withInlineCSS(false) 149 150 .sendMail(); 151 } 152 catch (MessagingException | IOException e) 153 { 154 getLogger().warn("Could not send a notification e-mail to {}", recipients, e); 155 } 156 } 157 158 private String getSubject(Activity activity, AbstractWorkspacesActivityType activityType, String lang) 159 { 160 return _i18nUtils.translate(new I18nizableText("plugin." + _pluginName, activityType.getSubjectI18nKey(activity), activityType.getSubjectI18nParams(activity)), lang); 161 } 162 163 private String getMailBody(Activity activity, AbstractWorkspacesActivityType activityType, Site site, String lang) 164 { 165 String mailBody; 166 Source source = null; 167 RenderingContext currentContext = _renderingContextHandler.getRenderingContext(); 168 169 try 170 { 171 // Force rendering context.FRONT to resolve URI 172 _renderingContextHandler.setRenderingContext(RenderingContext.FRONT); 173 174 Request request = ContextHelper.getRequest(_context); 175 request.setAttribute("forceAbsoluteUrl", true); 176 request.setAttribute("forceBase64Encoding", true); 177 178 request.setAttribute("lang", lang); 179 request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site); 180 request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName()); 181 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, site.getSkinId()); 182 183 source = _srcResolver.resolveURI(activityType.getMailBodyURI(activity), null, Map.of(ACTIVITY_CONTEXT_PARAM, activity)); 184 185 try (InputStream is = source.getInputStream()) 186 { 187 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 188 SourceUtil.copy(is, bos); 189 190 mailBody = bos.toString("UTF-8"); 191 } 192 } 193 catch (IOException e) 194 { 195 throw new RuntimeException("Failed to create mail body", e); 196 } 197 finally 198 { 199 _renderingContextHandler.setRenderingContext(currentContext); 200 201 if (source != null) 202 { 203 _srcResolver.release(source); 204 } 205 } 206 return mailBody; 207 } 208}