001/* 002 * Copyright 2021 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.schedulable; 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.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Optional; 029import java.util.Random; 030 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.cocoon.components.ContextHelper; 034import org.apache.cocoon.components.source.impl.SitemapSource; 035import org.apache.commons.io.FileUtils; 036import org.apache.commons.lang.StringUtils; 037import org.apache.excalibur.source.SourceResolver; 038import org.apache.excalibur.source.SourceUtil; 039import org.quartz.JobDataMap; 040import org.quartz.JobExecutionContext; 041 042import org.ametys.cms.schedule.AbstractSendingMailSchedulable; 043import org.ametys.odf.enumeration.OdfReferenceTableHelper; 044import org.ametys.odf.orgunit.OrgUnit; 045import org.ametys.plugins.core.schedule.Scheduler; 046import org.ametys.plugins.repository.AmetysObjectResolver; 047import org.ametys.runtime.config.Config; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.model.ElementDefinition; 050import org.ametys.runtime.util.AmetysHomeHelper; 051import org.ametys.runtime.workspace.WorkspaceMatcher; 052 053/** 054 * Schedulable to export the ODF catalog as PDF 055 */ 056public class CatalogPDFExportSchedulable extends AbstractSendingMailSchedulable 057{ 058 /** The key for the catalog */ 059 public static final String JOBDATAMAP_CATALOG_KEY = "catalog"; 060 /** The key for the lang */ 061 public static final String JOBDATAMAP_LANG_KEY = "lang"; 062 /** The key for the orgunit */ 063 public static final String JOBDATAMAP_ORGUNIT_KEY = "orgunit"; 064 /** The key for the degree */ 065 public static final String JOBDATAMAP_DEGREE_KEY = "degree"; 066 067 /** Map key where the generated filename is stored */ 068 protected static final String _CATALOG_FILENAME = "catalogFilename"; 069 070 /** The Ametys object resolver. */ 071 protected AmetysObjectResolver _resolver; 072 /** The ODF reference table helper. */ 073 protected OdfReferenceTableHelper _odfRefTableHelper; 074 /** The avalon source resolver. */ 075 protected SourceResolver _sourceResolver; 076 077 /** The catalog directory. */ 078 protected File _catalogRootDirectory; 079 080 @Override 081 public void service(ServiceManager manager) throws ServiceException 082 { 083 super.service(manager); 084 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 085 _odfRefTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE); 086 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 087 } 088 089 @Override 090 public void initialize() throws Exception 091 { 092 super.initialize(); 093 _catalogRootDirectory = new File(AmetysHomeHelper.getAmetysHomeData(), "odf/catalog"); 094 } 095 096 @Override 097 protected void _doExecute(JobExecutionContext context) throws Exception 098 { 099 SitemapSource source = null; 100 File pdfTmpFile = null; 101 try 102 { 103 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 104 String catalog = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_CATALOG_KEY); 105 String lang = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_LANG_KEY); 106 107 FileUtils.forceMkdir(_catalogRootDirectory); 108 109 File catalogDir = new File (_catalogRootDirectory, catalog); 110 if (!catalogDir.exists()) 111 { 112 catalogDir.mkdir(); 113 } 114 115 File langDir = new File (catalogDir, lang); 116 if (!langDir.exists()) 117 { 118 langDir.mkdir(); 119 } 120 121 String filenamePrefix = "catalog"; 122 123 // Resolve the export to the appropriate pdf url. 124 Map<String, String> params = new HashMap<>(); 125 126 // Org unit 127 String orgunit = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_ORGUNIT_KEY); 128 if (StringUtils.isNotEmpty(orgunit)) 129 { 130 filenamePrefix += "-" + _resolver.<OrgUnit>resolveById(orgunit).getUAICode(); 131 params.put(JOBDATAMAP_ORGUNIT_KEY, orgunit); 132 } 133 134 // Degree 135 String degree = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_DEGREE_KEY); 136 if (StringUtils.isNotEmpty(degree)) 137 { 138 filenamePrefix += "-" + _odfRefTableHelper.getItemCode(degree); 139 params.put(JOBDATAMAP_DEGREE_KEY, degree); 140 } 141 142 source = (SitemapSource) _sourceResolver.resolveURI("cocoon://_plugins/odf/programs/" + catalog + "/" + lang + "/catalog.pdf", null, params); 143 144 // Save the pdf into a temporary file. 145 String tmpFilename = filenamePrefix + "-" + new Random().nextInt() + ".tmp.pdf"; 146 pdfTmpFile = new File(langDir, tmpFilename); 147 148 try ( 149 OutputStream pdfTmpOs = new FileOutputStream(pdfTmpFile); 150 InputStream sourceIs = source.getInputStream() 151 ) 152 { 153 SourceUtil.copy(sourceIs, pdfTmpOs); 154 } 155 156 // If all went well until now, rename the temporary file 157 File catalogFile = new File(langDir, filenamePrefix + ".pdf"); 158 if (catalogFile.exists()) 159 { 160 catalogFile.delete(); 161 } 162 163 context.put(_CATALOG_FILENAME, catalogFile.getName()); 164 165 if (!pdfTmpFile.renameTo(catalogFile)) 166 { 167 throw new IOException("Fail to rename catalog.tmp.pdf to catalog.pdf"); 168 } 169 } 170 finally 171 { 172 if (pdfTmpFile != null) 173 { 174 FileUtils.deleteQuietly(pdfTmpFile); 175 } 176 177 if (source != null) 178 { 179 _sourceResolver.release(source); 180 } 181 } 182 183 } 184 185 @SuppressWarnings("unchecked") 186 @Override 187 // FIXME CMS-9980 188 public Map<String, ElementDefinition> getParameters() 189 { 190 Map<String, ElementDefinition> original = super.getParameters(); 191 192 boolean isAdminContext = Optional.ofNullable(_context) 193 .map(ContextHelper::getRequest) 194 .map(r -> r.getAttribute(WorkspaceMatcher.WORKSPACE_NAME)) 195 .map("admin"::equals) 196 .orElse(false); 197 198 // Remove the widget on orgunit and degree fields if we are in an admin context 199 if (isAdminContext) 200 { 201 // Copy to avoid the modification of the original object 202 Map<String, ElementDefinition> copy = new HashMap<>(original); 203 copy.put(JOBDATAMAP_ORGUNIT_KEY, _removeWidget(original.get(JOBDATAMAP_ORGUNIT_KEY))); 204 copy.put(JOBDATAMAP_DEGREE_KEY, _removeWidget(original.get(JOBDATAMAP_DEGREE_KEY))); 205 return copy; 206 } 207 208 return original; 209 } 210 211 private <T> ElementDefinition<T> _removeWidget(ElementDefinition<T> originalElement) 212 { 213 ElementDefinition<T> copiedElement = new ElementDefinition<>(originalElement); 214 copiedElement.setWidget(null); 215 copiedElement.setWidgetParameters(Collections.EMPTY_MAP); 216 return copiedElement; 217 } 218 219 @Override 220 protected I18nizableText _getSuccessMailSubject(JobExecutionContext context) throws Exception 221 { 222 return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_SUBJECT"); 223 } 224 225 @Override 226 protected I18nizableText _getSuccessMailBody(JobExecutionContext context) throws Exception 227 { 228 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 229 String catalog = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_CATALOG_KEY); 230 String lang = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_LANG_KEY); 231 232 String downloadLink = StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"); 233 downloadLink += (downloadLink.endsWith("/") ? "" : "/") + "plugins/odf/download/" + catalog + "/" + lang + "/" + context.get(_CATALOG_FILENAME); 234 235 List<String> params = new ArrayList<>(); 236 params.add(downloadLink); // {0} 237 238 return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_SUCCESS", params); 239 } 240 241 @Override 242 protected I18nizableText _getErrorMailSubject(JobExecutionContext context) throws Exception 243 { 244 return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_SUBJECT"); 245 } 246 247 @Override 248 protected I18nizableText _getErrorMailBody(JobExecutionContext context, Throwable throwable) throws Exception 249 { 250 return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_FAILURE"); 251 } 252}