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.cms.content.consistency; 017 018import java.io.ByteArrayInputStream; 019import java.io.ByteArrayOutputStream; 020import java.io.File; 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.commons.io.IOUtils; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.excalibur.source.Source; 035import org.apache.excalibur.source.SourceResolver; 036import org.quartz.JobExecutionContext; 037 038import org.ametys.cms.content.consistency.ContentConsistencyManager.ConsistenciesReport; 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.core.util.mail.SendMailHelper.NamedStream; 046import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable; 047import org.ametys.plugins.repository.AmetysObjectResolver; 048import org.ametys.runtime.config.Config; 049import org.ametys.runtime.i18n.I18nizableText; 050import org.ametys.runtime.servlet.RuntimeConfig; 051 052import jakarta.mail.MessagingException; 053 054/** 055 * Content consistency schedulable: generate consistency information for all contents. 056 * Sends a report e-mail if there are inconsistencies. 057 */ 058public class CheckContentConsistencySchedulable extends AbstractStaticSchedulable implements Initializable 059{ 060 /** The report e-mail will be sent to users who possess this right on the application context. */ 061 protected static final String _MAIL_RIGHT = "CMS_Rights_ReceiveConsistencyReport"; 062 063 /** The server base URL. */ 064 protected String _baseUrl; 065 066 /** The report directory. */ 067 protected File _reportDirectory; 068 069 /** The ametys object resolver. */ 070 protected AmetysObjectResolver _ametysResolver; 071 072 /** The avalon source resolver. */ 073 protected SourceResolver _sourceResolver; 074 075 /** The rights manager. */ 076 protected RightManager _rightManager; 077 078 /** The i18n utils. */ 079 protected I18nUtils _i18nUtils; 080 081 /** The content of "from" field in emails. */ 082 protected String _mailFrom; 083 084 /** The content consistency manager */ 085 protected ContentConsistencyManager _contentConsistencyManager; 086 087 @Override 088 public void service(ServiceManager manager) throws ServiceException 089 { 090 super.service(manager); 091 092 _ametysResolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 093 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 094 095 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 096 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 097 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 098 099 _contentConsistencyManager = (ContentConsistencyManager) manager.lookup(ContentConsistencyManager.ROLE); 100 } 101 102 public void initialize() throws Exception 103 { 104 _baseUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/"); 105 _mailFrom = Config.getInstance().getValue("smtp.mail.from"); 106 _reportDirectory = new File(RuntimeConfig.getInstance().getAmetysHome(), "consistency"); 107 } 108 109 @Override 110 public void execute(JobExecutionContext context) throws Exception 111 { 112 ConsistenciesReport report = _contentConsistencyManager.checkAllContents(); 113 114 List<String> errorId = report.results(); 115 List<String> unchecked = report.unchecked(); 116 117 if (!errorId.isEmpty() || !unchecked.isEmpty()) 118 { 119 _sendEmail(errorId, unchecked); 120 } 121 } 122 123 /** 124 * Send a reminder e-mail to all the users who have the right to edit. 125 * @param errorId the list of results with error 126 * @param unchecked the list of content that couldn't be checked 127 * @throws IOException if an error occurs building or sending the mail. 128 */ 129 protected void _sendEmail(List<String> errorId, List<String> unchecked) throws IOException 130 { 131 Set<UserIdentity> users = _rightManager.getAllowedUsers(_MAIL_RIGHT, "/cms").resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending")); 132 if (users.isEmpty()) 133 { 134 return; 135 } 136 Map<String, String> params = _getEmailParams(errorId, unchecked); 137 138 I18nizableText i18nSubject = _getMailSubject(params); 139 String subject = _i18nUtils.translate(i18nSubject); 140 141 I18nizableText i18nBody = _getMailBody(params); 142 143 String body = _i18nUtils.translate(i18nBody); 144 if (StringUtils.isEmpty(body)) 145 { 146 return; 147 } 148 149 Source report = null; 150 try 151 { 152 report = _getReport(); 153 _sendMails(subject, body, users, _mailFrom, report); 154 } 155 finally 156 { 157 _sourceResolver.release(report); 158 } 159 } 160 161 private Source _getReport() 162 { 163 try 164 { 165 return _sourceResolver.resolveURI("cocoon://_plugins/cms/consistency/report.csv"); 166 } 167 catch (IOException e) 168 { 169 getLogger().error("Failed to retrieve report", e); 170 return null; 171 } 172 } 173 174 /** 175 * Retrieves the mail's subject 176 * @param parameters the mail parameters. 177 * @return the mail's subject 178 */ 179 protected I18nizableText _getMailSubject(Map<String, String> parameters) 180 { 181 return new I18nizableText("plugin.cms", "PLUGINS_CMS_GLOBAL_CONTENT_CONSISTENCY_MAIL_SUBJECT"); 182 } 183 184 /** 185 * Retrieves the mail's body 186 * @param params the i18n parameters 187 * @return the mail's body 188 */ 189 protected I18nizableText _getMailBody(Map<String, String> params) 190 { 191 String failure = params.get("nbFailure"); 192 String unchecked = params.get("uncheckedList"); 193 194 String key; 195 if (StringUtils.equals(failure, "0")) 196 { 197 key = "PLUGINS_CMS_GLOBAL_CONTENT_CONSISTENCY_MAIL_BODY_NO_FAILURE"; 198 } 199 else if (StringUtils.equals(failure, "1")) 200 { 201 key = "PLUGINS_CMS_GLOBAL_CONTENT_CONSISTENCY_MAIL_BODY_FAILURE"; 202 } 203 else 204 { 205 key = "PLUGINS_CMS_GLOBAL_CONTENT_CONSISTENCY_MAIL_BODY_FAILURES"; 206 } 207 208 if (!StringUtils.isEmpty(unchecked)) 209 { 210 key += "_UNCHECKED"; 211 } 212 213 return new I18nizableText("plugin.cms", key, List.of(params.get("url"), failure, unchecked)); 214 } 215 216 /** 217 * Get the report e-mail parameters. 218 * @param unchecked the list of unchecked content id 219 * @param errorResults the list of error results 220 * @return the e-mail parameters. 221 */ 222 protected Map<String, String> _getEmailParams(List<String> errorResults, List<String> unchecked) 223 { 224 Map<String, String> params = new HashMap<>(); 225 226 StringBuilder url = new StringBuilder(_baseUrl); 227 url.append("/index.html?uitool=uitool-global-consistency"); 228 229 params.put("url", url.toString()); 230 params.put("nbFailure", Integer.toString(errorResults.size())); 231 params.put("uncheckedList", StringUtils.join(unchecked, ", ")); 232 233 return params; 234 } 235 236 /** 237 * Send the alert emails. 238 * @param subject the e-mail subject. 239 * @param body the e-mail body. 240 * @param users users to send the mail to. 241 * @param from the address sending the e-mail. 242 * @param attachement the 243 */ 244 protected void _sendMails(String subject, String body, Set<UserIdentity> users, String from, Source attachement) 245 { 246 InputStream attachmentCopy = null; 247 // Get the attachement and store it in a reusable inputStream. 248 // If the attachment size exceed 1M we won't attach it 249 try (InputStream is = attachement.getInputStream()) 250 { 251 // Override the byteArray to avoid duplication of buffer during copy 252 ByteArrayOutputStream tmp = new ByteArrayOutputStream() 253 { 254 @Override 255 public synchronized byte[] toByteArray() 256 { 257 return buf; 258 } 259 }; 260 IOUtils.copyLarge(is, tmp, 0, 1_000_000); 261 if (is.read() == -1) 262 { 263 // We could read all the stream so the size is acceptable and we keep the attachment 264 attachmentCopy = new ByteArrayInputStream(tmp.toByteArray()); 265 } 266 267 } 268 catch (IOException e) 269 { 270 getLogger().error("Failed to retrieve consistency report", e); 271 } 272 273 for (UserIdentity userIdentity : users) 274 { 275 User user = _userManager.getUser(userIdentity.getPopulationId(), userIdentity.getLogin()); 276 277 if (user != null && StringUtils.isNotBlank(user.getEmail())) 278 { 279 String mail = user.getEmail(); 280 281 Collection<NamedStream> attachments = null; 282 if (attachmentCopy != null) 283 { 284 try 285 { 286 attachmentCopy.reset(); 287 } 288 catch (IOException e) 289 { 290 // in case reset is not implemented. 291 } 292 attachments = List.of(new NamedStream(attachmentCopy, "report.csv", "text/csv")); 293 } 294 295 try 296 { 297 SendMailHelper.newMail() 298 .withSubject(subject) 299 .withTextBody(body) 300 .withSender(from) 301 .withRecipient(mail) 302 .withAttachmentsAsStream(attachments) 303 .sendMail(); 304 } 305 catch (MessagingException | IOException e) 306 { 307 if (getLogger().isWarnEnabled()) 308 { 309 getLogger().warn("Could not send an alert e-mail to " + mail, e); 310 } 311 } 312 } 313 } 314 } 315}