001/* 002 * Copyright 2018 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.odfpilotage.report; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.time.LocalDate; 025import java.time.format.DateTimeFormatter; 026import java.util.ArrayList; 027import java.util.List; 028import java.util.Map; 029import java.util.Optional; 030import java.util.zip.ZipEntry; 031import java.util.zip.ZipOutputStream; 032 033import javax.mail.MessagingException; 034 035import org.apache.avalon.framework.activity.Initializable; 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.avalon.framework.service.Serviceable; 039import org.apache.commons.io.FileUtils; 040import org.apache.commons.io.IOUtils; 041import org.apache.commons.lang.StringUtils; 042import org.apache.excalibur.source.Source; 043import org.apache.excalibur.source.SourceResolver; 044 045import org.ametys.cms.FilterNameHelper; 046import org.ametys.core.user.User; 047import org.ametys.core.user.UserIdentity; 048import org.ametys.core.user.UserManager; 049import org.ametys.core.util.I18nUtils; 050import org.ametys.core.util.mail.SendMailHelper; 051import org.ametys.odf.ODFHelper; 052import org.ametys.odf.enumeration.OdfReferenceTableHelper; 053import org.ametys.plugins.odfpilotage.helper.PilotageHelper; 054import org.ametys.plugins.odfpilotage.helper.ReportHelper; 055import org.ametys.plugins.repository.AmetysObjectResolver; 056import org.ametys.runtime.config.Config; 057import org.ametys.runtime.i18n.I18nizableText; 058import org.ametys.runtime.plugin.component.AbstractLogEnabled; 059import org.ametys.runtime.plugin.component.PluginAware; 060 061import com.google.common.collect.ImmutableList; 062 063/** 064 * The abstract class for pilotage reports. 065 */ 066public abstract class AbstractPilotageReport extends AbstractLogEnabled implements PilotageReport, Serviceable, Initializable, PluginAware 067{ 068 /** 069 * The enumerator for different pilotage report status 070 */ 071 public enum PilotageReportStatus 072 { 073 /** If the report has failed */ 074 FAIL, 075 /** If the report is a success */ 076 SUCCESS, 077 /** If there are no file in the report */ 078 NO_FILE 079 } 080 081 /** The source resolver */ 082 protected SourceResolver _sourceResolver; 083 084 /** The pilotage helper */ 085 protected PilotageHelper _pilotageHelper; 086 087 /** The ametys object resolver */ 088 protected AmetysObjectResolver _resolver; 089 090 /** The report helper */ 091 protected ReportHelper _reportHelper; 092 093 /** The ODF enumeration helper */ 094 protected OdfReferenceTableHelper _refTableHelper; 095 096 /** The ODF helper */ 097 protected ODFHelper _odfHelper; 098 099 /** The user manager */ 100 protected UserManager _userManager; 101 102 /** The I18N utils */ 103 protected I18nUtils _i18nUtils; 104 105 /** The tmp folder */ 106 protected File _tmpFolder; 107 108 /** The current date formatted to yyyy-MM-dd */ 109 protected String _currentFormattedDate; 110 111 private String _pluginName; 112 private String _mailFrom; 113 114 @Override 115 public void initialize() throws Exception 116 { 117 _tmpFolder = new File(_pilotageHelper.getTmpPilotageFolder(), getType()); 118 _mailFrom = Config.getInstance().getValue("smtp.mail.from"); 119 } 120 121 @Override 122 public void setPluginInfo(String pluginName, String featureName, String id) 123 { 124 _pluginName = pluginName; 125 } 126 127 @Override 128 public void service(ServiceManager manager) throws ServiceException 129 { 130 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 131 _pilotageHelper = (PilotageHelper) manager.lookup(PilotageHelper.ROLE); 132 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 133 _reportHelper = (ReportHelper) manager.lookup(ReportHelper.ROLE); 134 _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE); 135 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 136 _userManager = (UserManager) manager.lookup(UserManager.ROLE); 137 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 138 } 139 140 /** 141 * Launch a report generation on an orgunit. 142 * @param reportParameters The report parameters 143 * @return the name of the generated file 144 * @throws Exception if an exception occurs 145 */ 146 protected abstract String launchByOrgUnit(Map<String, String> reportParameters) throws Exception; 147 148 /** 149 * Launch a report generation on a program. 150 * @param reportParameters The report parameters 151 * @return the name of the generated file 152 * @throws Exception if an exception occurs 153 */ 154 protected abstract String launchByProgram(Map<String, String> reportParameters) throws Exception; 155 156 /** 157 * Get the name of the report 158 * @return The report name 159 */ 160 protected abstract String getType(); 161 162 /** 163 * Get the plugin name to build the pipeline. 164 * @return The plugin name 165 */ 166 protected String getPluginName() 167 { 168 return _pluginName; 169 } 170 171 /** 172 * Build the pipeline to launch the transformation. 173 * @param outputFolderName The name of the output folder name 174 * @return The pipeline for transformation 175 */ 176 protected String getPipeline(String outputFolderName) 177 { 178 return "report/" + getType() + "/" + outputFolderName; 179 } 180 181 @Override 182 public synchronized void launch(PilotageReportTarget target, Map<String, String> reportParameters, UserIdentity user) 183 { 184 getLogger().info("Début du rapport de pilotage"); 185 long time_0 = System.currentTimeMillis(); 186 187 String contextName = null; 188 try 189 { 190 FileUtils.deleteQuietly(_tmpFolder); 191 _tmpFolder.mkdir(); 192 _currentFormattedDate = LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE); 193 194 switch (target) 195 { 196 case PROGRAM: 197 contextName = launchByProgram(reportParameters); 198 break; 199 case ORGUNIT: 200 contextName = launchByOrgUnit(reportParameters); 201 break; 202 default: 203 break; 204 } 205 } 206 catch (Exception e) 207 { 208 getLogger().error("Erreur d'écriture du rapport.", e); 209 } 210 finally 211 { 212 PilotageFile pilotageFile = new PilotageFile(PilotageReportStatus.FAIL, null); 213 try 214 { 215 pilotageFile = createZipFile(_tmpFolder, contextName); 216 } 217 catch (Exception e) 218 { 219 getLogger().error("Une erreur est survenue lors de la compression des rapports.", e); 220 } 221 finally 222 { 223 sendMail(pilotageFile, user); 224 225 FileUtils.deleteQuietly(_tmpFolder); 226 _currentFormattedDate = null; 227 228 long time_1 = System.currentTimeMillis(); 229 getLogger().info("Calcul et écriture du rapport de pilotage effectué en {} ms.", time_1 - time_0); 230 } 231 } 232 } 233 234 /** 235 * Convert the report from XML to the required format 236 * @param outputFolder folder where the file will stay temporarily 237 * @param fileName the name of the file 238 * @param xmlFile the file to be converted 239 * @throws IOException if an error occurs 240 */ 241 protected void convertReport(File outputFolder, String fileName, File xmlFile) throws IOException 242 { 243 Source source = null; 244 try 245 { 246 // Transform XML to HTML-XLS 247 source = _sourceResolver.resolveURI("cocoon://_plugins/" + getPluginName() + "/" + getPipeline(outputFolder.getName()) + "/" + fileName); 248 try (InputStream is = source.getInputStream()) 249 { 250 // Delete existing file 251 File file = new File(outputFolder, fileName); 252 FileUtils.deleteQuietly(file); 253 file.createNewFile(); 254 255 // Save file 256 try (OutputStream os = new FileOutputStream(file)) 257 { 258 IOUtils.copy(is, os); 259 } 260 } 261 } 262 finally 263 { 264 FileUtils.deleteQuietly(xmlFile); 265 if (source != null) 266 { 267 _sourceResolver.release(source); 268 } 269 } 270 } 271 272 /** 273 * Compress a folder to zip format 274 * @param folderToZip the folder to be compressed 275 * @param contextName the name of the report context 276 * @return The pilotage file 277 * @throws IOException if an exception occurs 278 */ 279 protected PilotageFile createZipFile(File folderToZip, String contextName) throws IOException 280 { 281 if (StringUtils.isEmpty(contextName)) 282 { 283 return new PilotageFile(PilotageReportStatus.FAIL, null); 284 } 285 286 File zipFile = new File(_pilotageHelper.getPilotageFolder(), _buildZipName(contextName)); 287 288 FileUtils.deleteQuietly(zipFile); 289 File[] files = folderToZip.listFiles(); 290 291 if (files.length == 0) 292 { 293 getLogger().warn("Aucun fichier généré"); 294 return new PilotageFile(PilotageReportStatus.NO_FILE, null); 295 } 296 297 getLogger().info("Création de l'archive"); 298 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) 299 { 300 for (File file : files) 301 { 302 try (FileInputStream is = new FileInputStream(file)) 303 { 304 byte[] buf = new byte[1024]; 305 int len; 306 zos.putNextEntry(new ZipEntry(file.getName())); 307 while ((len = is.read(buf)) > 0) 308 { 309 zos.write(buf, 0, len); 310 } 311 } 312 finally 313 { 314 zos.closeEntry(); 315 } 316 } 317 } 318 319 return new PilotageFile(PilotageReportStatus.SUCCESS, zipFile); 320 } 321 322 /** 323 * Send a mail with the ZIP file as attachment at the end of the report generation. 324 * @param file the pilotage file 325 * @param user the recipient if he has an email 326 */ 327 protected void sendMail(PilotageFile file, UserIdentity user) 328 { 329 String recipient = Optional.ofNullable(user) 330 .map(_userManager::getUser) 331 .map(User::getEmail) 332 .filter(StringUtils::isNotEmpty) 333 .orElse(null); 334 335 if (recipient != null) 336 { 337 String subject = getMailSubject(); 338 String body = getMailBody(file.getStatus()); 339 try 340 { 341 File zipFile = file.getZipFile(); 342 List<File> attachments = new ArrayList<>(); 343 if (zipFile != null && zipFile.exists()) 344 { 345 attachments.add(zipFile); 346 } 347 348 getLogger().info("Envoi du rapport par mail"); 349 SendMailHelper.sendMail(subject, null, body, attachments, recipient, _mailFrom); 350 } 351 catch (MessagingException | IOException e) 352 { 353 getLogger().warn("Fail to send email to {}", recipient, e); 354 } 355 } 356 } 357 358 /** 359 * The mail subject. 360 * @return The subject of the mail 361 */ 362 protected String getMailSubject() 363 { 364 return _i18nUtils.translate(new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_MAIL_SUBJECT", ImmutableList.of(getReportName()))); 365 } 366 367 /** 368 * The mail body. 369 * @param status the status of the pilotage report 370 * @return The body of the mail 371 */ 372 protected String getMailBody(PilotageReportStatus status) 373 { 374 String key = "PLUGINS_ODF_PILOTAGE_MAIL_BODY_" + status.name(); 375 return _i18nUtils.translate(new I18nizableText("plugin.odf-pilotage", key, ImmutableList.of(getReportName()))); 376 } 377 378 /** 379 * The report name to add in the mail. 380 * @return The report name 381 */ 382 protected abstract String getReportName(); 383 384 /** 385 * Build the ZIP name. 386 * @param contextName The report context name 387 * @return The full ZIP name 388 */ 389 private String _buildZipName(String contextName) 390 { 391 return FilterNameHelper.filterName(getType() + "-" + contextName + "-" + _currentFormattedDate) + ".zip"; 392 } 393 394 /** 395 * Object representing a pilotage file 396 * Containing the zip file and the report status 397 */ 398 public static class PilotageFile 399 { 400 private File _zipFile; 401 private PilotageReportStatus _status; 402 403 /** 404 * The pilotage file constructor 405 * @param status the status 406 * @param zipFile the zip file, can be null or non-existent 407 */ 408 public PilotageFile(PilotageReportStatus status, File zipFile) 409 { 410 this._zipFile = zipFile; 411 this._status = status; 412 } 413 414 /** 415 * Get the pilotage report status 416 * @return the pilotage report status 417 */ 418 public PilotageReportStatus getStatus() 419 { 420 return _status; 421 } 422 423 /** 424 * Set the pilotage report status 425 * @param status the status 426 */ 427 public void setStatus(PilotageReportStatus status) 428 { 429 this._status = status; 430 } 431 432 /** 433 * Get the zip file 434 * @return the zip file 435 */ 436 public File getZipFile() 437 { 438 return _zipFile; 439 } 440 441 /** 442 * Set the zip file 443 * @param zipFile the zip file 444 */ 445 public void setZipFile(File zipFile) 446 { 447 this._zipFile = zipFile; 448 } 449 } 450}