001/* 002 * Copyright 2014 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.odf.export.pdf; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Map; 026 027import javax.mail.MessagingException; 028 029import org.apache.avalon.framework.configuration.Configuration; 030import org.apache.avalon.framework.configuration.ConfigurationException; 031import org.apache.avalon.framework.context.Context; 032import org.apache.avalon.framework.context.ContextException; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.cocoon.Constants; 036import org.apache.cocoon.components.source.impl.SitemapSource; 037import org.apache.cocoon.environment.ObjectModelHelper; 038import org.apache.cocoon.environment.Request; 039import org.apache.cocoon.environment.background.BackgroundEnvironment; 040import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 041import org.apache.commons.io.FileUtils; 042import org.apache.commons.lang.StringUtils; 043import org.apache.excalibur.source.SourceResolver; 044import org.apache.excalibur.source.SourceUtil; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import org.ametys.core.authentication.AuthenticateAction; 049import org.ametys.core.engine.BackgroundEngineHelper; 050import org.ametys.core.user.User; 051import org.ametys.core.util.I18nUtils; 052import org.ametys.core.util.mail.SendMailHelper; 053import org.ametys.plugins.repository.AmetysRepositoryException; 054import org.ametys.runtime.config.Config; 055import org.ametys.runtime.i18n.I18nizableText; 056import org.ametys.runtime.util.AmetysHomeHelper; 057 058/** 059 * Content consistency engine: generate consistency information for all contents. 060 * Sends a report e-mail if there are inconsistencies. 061 */ 062public class GeneratePDFEngine implements Runnable 063{ 064 /** The logger. */ 065 protected static final Logger _LOGGER = LoggerFactory.getLogger(GeneratePDFEngine.class.getName()); 066 067 /** Is the thread running ? */ 068 private static boolean _RUNNING; 069 070 /** The avalon context. */ 071 protected Context _context; 072 073 /** The cocoon environment context. */ 074 protected org.apache.cocoon.environment.Context _environmentContext; 075 076 /** The service manager. */ 077 protected ServiceManager _manager; 078 079 /** The avalon source resolver. */ 080 protected SourceResolver _sourceResolver; 081 082 /** The i18n utils. */ 083 protected I18nUtils _i18nUtils; 084 085 /** The catalog directory. */ 086 protected File _catalogRootDirectory; 087 088 /** Is the engine initialized ? */ 089 protected boolean _initialized; 090 091 /** The sender of the email */ 092 protected String _mailFrom; 093 094 /** The contextual parameters */ 095 protected Map<String, Object> _contextualParameters; 096 097 private User _issuer; 098 private String _catalog; 099 100 /** 101 * Initialize the alert engine. 102 * @param manager the avalon service manager. 103 * @param context the avalon context. 104 * @throws ContextException if an error occurs retrieving the environment context. 105 * @throws ServiceException if an error occurs retrieving a component. 106 */ 107 public void initialize(ServiceManager manager, Context context) throws ContextException, ServiceException 108 { 109 _manager = manager; 110 _context = context; 111 _environmentContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 112 113 // Lookup the needed components. 114 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 115 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 116 117 // initialize the parameters 118 _mailFrom = Config.getInstance().getValueAsString("smtp.mail.from"); 119 120 _catalogRootDirectory = new File(AmetysHomeHelper.getAmetysHomeData(), "odf/catalog"); 121 122 _initialized = true; 123 } 124 125 /** 126 * Configure the engine (called by the scheduler). 127 * @param configuration the component configuration. 128 * @throws ConfigurationException if an error occurs 129 */ 130 public void configure(Configuration configuration) throws ConfigurationException 131 { 132 // Ignore 133 } 134 135 /** 136 * Set the contextual parameters 137 * @param contextualParameters the contextual parameters 138 */ 139 public void setContextualParameters(Map<String, Object> contextualParameters) 140 { 141 _contextualParameters = contextualParameters; 142 } 143 144 /** 145 * Set the code of catalogue to generate 146 * @param code the code 147 */ 148 public void setCatalog (String code) 149 { 150 _catalog = code; 151 } 152 153 /** 154 * Set the user who launched the PDF export 155 * @param issuer the issuer 156 */ 157 public void setIssuer(User issuer) 158 { 159 _issuer = issuer; 160 } 161 162 /** 163 * Check the initialization and throw an exception if not initialized. 164 */ 165 protected void _checkInitialization() 166 { 167 if (!_initialized) 168 { 169 String message = "The synchronization component has to be initialized before being run"; 170 _LOGGER.error(message); 171 throw new IllegalStateException(message); 172 } 173 } 174 175 /** 176 * Test if the engine is running at the time. 177 * @return true if the engine is running, false otherwise. 178 */ 179 public static boolean isRunning() 180 { 181 return _RUNNING; 182 } 183 184 private static void setRunning(boolean running) 185 { 186 _RUNNING = running; 187 } 188 189 @Override 190 public void run() 191 { 192 Map<String, Object> environmentInformation = null; 193 boolean error = false; 194 195 try 196 { 197 _LOGGER.info("Preparing to generate the pdf catalog..."); 198 199 _checkInitialization(); 200 201 // Create the environment. 202 environmentInformation = BackgroundEngineHelper.createAndEnterEngineEnvironment(_manager, _environmentContext, new SLF4JLoggerAdapter(_LOGGER)); 203 BackgroundEnvironment environment = (BackgroundEnvironment) environmentInformation.get("environment"); 204 205 Request request = (Request) environment.getObjectModel().get(ObjectModelHelper.REQUEST_OBJECT); 206 207 _generatePDF(request); 208 } 209 catch (Exception e) 210 { 211 error = true; 212 _LOGGER.error("An error occurred while generating the PDF catalog.", e); 213 } 214 finally 215 { 216 _sendMail(error); 217 218 // Leave the environment. 219 if (environmentInformation != null) 220 { 221 BackgroundEngineHelper.leaveEngineEnvironment(environmentInformation); 222 } 223 // Dispose of the resources. 224 _dispose(); 225 226 if (!error) 227 { 228 _LOGGER.info("The pdf catalog has been generated."); 229 } 230 } 231 } 232 233 /** 234 * Dispose of the resources and looked-up components. 235 */ 236 protected void _dispose() 237 { 238 _sourceResolver = null; 239 _environmentContext = null; 240 _context = null; 241 _manager = null; 242 243 _initialized = false; 244 } 245 246 /** 247 * Generates the PDF file. Checks that there is no current generation process. 248 * @param request The request 249 * @throws AmetysRepositoryException if an error occurs. 250 * @throws IOException if an I/O error occurs. 251 */ 252 protected void _generatePDF(Request request) throws AmetysRepositoryException, IOException 253 { 254 if (isRunning()) 255 { 256 _LOGGER.error("Cannot start the generation, as the engine is running at the time."); 257 } 258 else 259 { 260 try 261 { 262 setRunning(true); 263 264 // Generate the report. 265 _doGeneratePDF(request); 266 } 267 finally 268 { 269 setRunning(false); 270 } 271 } 272 } 273 274 /** 275 * Generate the PDF file 276 * @param request the request 277 * @throws IOException if an I/O error occurs. 278 */ 279 protected void _doGeneratePDF(Request request) throws IOException 280 { 281 SitemapSource source = null; 282 File pdfTmpFile = null; 283 try 284 { 285 FileUtils.forceMkdir(_catalogRootDirectory); 286 287 File catalogDir = new File (_catalogRootDirectory, _catalog); 288 if (!catalogDir.exists()) 289 { 290 catalogDir.mkdir(); 291 } 292 293 File langDir = new File (catalogDir, getLanguage()); 294 if (!langDir.exists()) 295 { 296 langDir.mkdir(); 297 } 298 299 request.setAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INTERNAL_ALLOWED, true); 300 301 // Resolve the export to the appropriate pdf url. 302 source = (SitemapSource) _sourceResolver.resolveURI("cocoon://_plugins/odf/programs/" + _catalog + "/" + getLanguage() + "/catalog.pdf"); 303 304 // Save the pdf into a temporary file. 305 pdfTmpFile = new File(langDir, "catalog.tmp.pdf"); 306 307 308 try (OutputStream pdfTmpOs = new FileOutputStream(pdfTmpFile); InputStream sourceIs = source.getInputStream()) 309 { 310 SourceUtil.copy(sourceIs, pdfTmpOs); 311 } 312 313 // If all went well until now, rename the temporary file 314 File catalogFile = new File(langDir, "catalog.pdf"); 315 if (catalogFile.exists()) 316 { 317 catalogFile.delete(); 318 } 319 320 if (!pdfTmpFile.renameTo(catalogFile)) 321 { 322 throw new IOException("Fail to rename catalog.tmp.pdf to catalog.pdf"); 323 } 324 325 _sendMail(false); 326 } 327 finally 328 { 329 if (pdfTmpFile != null) 330 { 331 FileUtils.deleteQuietly(pdfTmpFile); 332 } 333 334 if (source != null) 335 { 336 _sourceResolver.release(source); 337 } 338 } 339 } 340 341 /** 342 * Get the language for export 343 * @return The language 344 */ 345 protected String getLanguage() 346 { 347 if (_contextualParameters.containsKey("lang")) 348 { 349 return (String) _contextualParameters.get("lang"); 350 } 351 return Config.getInstance().getValueAsString("odf.programs.lang"); 352 } 353 354 /** 355 * Sends an email to the user who has launched the generation of PDF 356 * @param error true if the catalog wasn't generated successfully, false otherwise. 357 */ 358 protected void _sendMail(boolean error) 359 { 360 String recipient = _issuer.getEmail(); 361 362 if (!StringUtils.isEmpty(recipient)) 363 { 364 String subject = getMailSubject(); 365 String body = error ? getFailureMailBody() : getSuccessMailBody(); 366 367 try 368 { 369 SendMailHelper.sendMail(subject, null, body, recipient, _mailFrom); 370 } 371 catch (MessagingException e) 372 { 373 if (_LOGGER.isWarnEnabled()) 374 { 375 _LOGGER.warn("Fail to send email to " + recipient, e); 376 } 377 } 378 } 379 380 } 381 382 /** 383 * Get the subject of mail 384 * @return the subject 385 */ 386 protected String getMailSubject () 387 { 388 I18nizableText subjectKey = new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_SUBJECT"); 389 return _i18nUtils.translate(subjectKey, getLanguage()); // FIXME Use user preference language 390 } 391 392 /** 393 * Get the body of mail in case of success 394 * @return the body 395 */ 396 protected String getSuccessMailBody () 397 { 398 List<String> params = new ArrayList<>(); 399 400 String downloadLink = StringUtils.removeEndIgnoreCase(Config.getInstance().getValueAsString("cms.url"), "index.html"); 401 downloadLink += (downloadLink.endsWith("/") ? "" : "/") + "plugins/odf/download/" + _catalog + "/" + getLanguage() + "/catalog.pdf"; 402 params.add(downloadLink); // {0} 403 404 I18nizableText subjectKey = new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_SUCCESS", params); 405 return _i18nUtils.translate(subjectKey, null); // FIXME Use user preference language 406 } 407 408 /** 409 * Get the body of mail in case of failure 410 * @return the body 411 */ 412 protected String getFailureMailBody () 413 { 414 I18nizableText subjectKey = new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_FAILURE"); 415 return _i18nUtils.translate(subjectKey, getLanguage()); // FIXME Use user preference language 416 } 417}