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