001/* 002 * Copyright 2012 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.tool; 017 018import java.io.File; 019import java.io.FileFilter; 020import java.io.IOException; 021import java.io.InputStream; 022import java.nio.charset.StandardCharsets; 023import java.time.ZonedDateTime; 024import java.time.format.DateTimeFormatter; 025import java.util.Arrays; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.HashMap; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Map; 032import java.util.zip.ZipEntry; 033import java.util.zip.ZipFile; 034 035import org.apache.avalon.framework.parameters.Parameters; 036import org.apache.avalon.framework.service.ServiceException; 037import org.apache.avalon.framework.service.ServiceManager; 038import org.apache.cocoon.ProcessingException; 039import org.apache.cocoon.acting.ServiceableAction; 040import org.apache.cocoon.environment.ObjectModelHelper; 041import org.apache.cocoon.environment.Redirector; 042import org.apache.cocoon.environment.Request; 043import org.apache.cocoon.environment.SourceResolver; 044import org.apache.commons.collections4.MapUtils; 045import org.apache.commons.io.IOUtils; 046import org.apache.commons.io.comparator.LastModifiedFileComparator; 047import org.apache.commons.io.comparator.NameFileComparator; 048import org.apache.commons.io.comparator.SizeFileComparator; 049import org.apache.commons.lang.StringUtils; 050 051import org.ametys.cms.content.ContentHelper; 052import org.ametys.cms.languages.Language; 053import org.ametys.cms.languages.LanguagesManager; 054import org.ametys.cms.repository.Content; 055import org.ametys.core.cocoon.JSonReader; 056import org.ametys.core.util.DateUtils; 057import org.ametys.core.util.JSONUtils; 058import org.ametys.core.util.ServerCommHelper; 059import org.ametys.odf.catalog.Catalog; 060import org.ametys.odf.catalog.CatalogsManager; 061import org.ametys.odf.program.Program; 062import org.ametys.plugins.odfpilotage.helper.PilotageHelper; 063import org.ametys.plugins.odfpilotage.manager.PilotageLogFileManager; 064import org.ametys.plugins.odfpilotage.report.AbstractPilotageReport; 065import org.ametys.plugins.odfpilotage.report.PilotageReport; 066import org.ametys.plugins.odfpilotage.report.PilotageReport.PilotageReportTarget; 067import org.ametys.plugins.odfpilotage.report.ReportExtensionPoint; 068import org.ametys.plugins.odfpilotage.schedulable.AbstractReportSchedulable; 069import org.ametys.plugins.odfpilotage.schedulable.OrgUnitReportSchedulable; 070import org.ametys.plugins.odfpilotage.schedulable.ProgramReportSchedulable; 071import org.ametys.plugins.repository.AmetysObjectResolver; 072import org.ametys.plugins.repository.UnknownAmetysObjectException; 073 074import com.google.common.collect.Lists; 075import com.google.common.io.PatternFilenameFilter; 076 077/** 078 * SAX the last 30 log files 079 * 080 */ 081public class ListReportsAction extends ServiceableAction 082{ 083 private static final String _CRITERIA_FILENAME = "filename"; 084 private static final String _CRITERIA_LAST_MODIFIED_AFTER = "lastModifiedAfter"; 085 private static final String _CRITERIA_LAST_MODIFIED_BEFORE = "lastModifiedBefore"; 086 087 private static final String _COLUMN_FILENAME = "reportfile"; 088 private static final String _COLUMN_LAST_MODIFIED = "lastModified"; 089 private static final String _COLUMN_LENGTH = "length"; 090 private static final String _COLUMN_TYPE = "type"; 091 private static final String _COLUMN_OUTPUT_FORMAT = "outputFormat"; 092 private static final String _COLUMN_CATALOG = "catalog"; 093 private static final String _COLUMN_LANG = "lang"; 094 private static final String _COLUMN_TARGET = "target"; 095 private static final String _COLUMN_CONTEXT = "context"; 096 097 private static final Map<String, Comparator<File>> _NAME_TO_COMPARATOR = new HashMap<>(); 098 static 099 { 100 _NAME_TO_COMPARATOR.put(_COLUMN_FILENAME, NameFileComparator.NAME_INSENSITIVE_COMPARATOR); 101 _NAME_TO_COMPARATOR.put(_COLUMN_LAST_MODIFIED, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR); 102 _NAME_TO_COMPARATOR.put(_COLUMN_LENGTH, SizeFileComparator.SIZE_COMPARATOR); 103 } 104 105 /** ServerComm Helper */ 106 protected ServerCommHelper _serverCommHelper; 107 /** JSON Utils */ 108 protected JSONUtils _jsonUtils; 109 /** Pilotage log file manager */ 110 protected PilotageLogFileManager _pilotageLogFileManager; 111 /** Pilotage helper */ 112 protected PilotageHelper _pilotageHelper; 113 /** Ametys object resolver */ 114 protected AmetysObjectResolver _resolver; 115 /** The report extension point */ 116 protected ReportExtensionPoint _reportEP; 117 /** The language manager */ 118 protected LanguagesManager _languageManager; 119 /** The catalog manager */ 120 protected CatalogsManager _catalogManager; 121 /** The content helper */ 122 protected ContentHelper _contentHelper; 123 124 @Override 125 public void service(ServiceManager smanager) throws ServiceException 126 { 127 super.service(smanager); 128 _serverCommHelper = (ServerCommHelper) smanager.lookup(ServerCommHelper.ROLE); 129 _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE); 130 _pilotageLogFileManager = (PilotageLogFileManager) smanager.lookup(PilotageLogFileManager.ROLE); 131 _pilotageHelper = (PilotageHelper) smanager.lookup(PilotageHelper.ROLE); 132 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 133 _reportEP = (ReportExtensionPoint) smanager.lookup(ReportExtensionPoint.ROLE); 134 _languageManager = (LanguagesManager) smanager.lookup(LanguagesManager.ROLE); 135 _catalogManager = (CatalogsManager) smanager.lookup(CatalogsManager.ROLE); 136 _contentHelper = (ContentHelper) smanager.lookup(ContentHelper.ROLE); 137 } 138 139 @SuppressWarnings("unchecked") 140 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 141 { 142 // Get JS parameters 143 Map<String, Object> jsParameters = _serverCommHelper.getJsParameters(); 144 145 // Search 146 Map<String, Object> searchParams = (Map<String, Object>) jsParameters.get("values"); 147 File[] reportFiles = _getReportFiles(searchParams); 148 Integer offset = _getIntValue(jsParameters, "start", 0); 149 Integer limit = _getIntValue(jsParameters, "limit", Integer.MAX_VALUE); 150 List<Map<String, Object>> results = _getReportResults(reportFiles, offset, limit, _getSortList(jsParameters.get("sort"))); 151 152 // Construct the JSON object 153 Map<String, Object> objectToRead = new HashMap<>(); 154 objectToRead.put("items", results); 155 objectToRead.put("total", reportFiles.length); 156 157 // Add JSON to the request to be parsed 158 Request request = ObjectModelHelper.getRequest(objectModel); 159 request.setAttribute(JSonReader.OBJECT_TO_READ, objectToRead); 160 161 return EMPTY_MAP; 162 } 163 164 private int _getIntValue(Map<String, Object> values, String key, int defaultValue) 165 { 166 if (values.containsKey(key)) 167 { 168 return Integer.valueOf(values.get(key).toString()).intValue(); 169 } 170 171 return defaultValue; 172 } 173 174 private File[] _getReportFiles(Map<String, Object> parameters) 175 { 176 String filename = null; 177 ZonedDateTime lastModifiedAfter = null; 178 ZonedDateTime lastModifiedBefore = null; 179 if (parameters != null && !parameters.isEmpty()) 180 { 181 filename = MapUtils.getString(parameters, _CRITERIA_FILENAME); 182 lastModifiedAfter = _getZonedDateTimeFromParameters(parameters, _CRITERIA_LAST_MODIFIED_AFTER); 183 lastModifiedBefore = _getZonedDateTimeFromParameters(parameters, _CRITERIA_LAST_MODIFIED_BEFORE); 184 } 185 FileFilter filter = new PilotageFileFilter(filename, lastModifiedAfter, lastModifiedBefore); 186 187 return _pilotageHelper.getPilotageFolder().listFiles(filter); 188 } 189 190 private ZonedDateTime _getZonedDateTimeFromParameters(Map<String, Object> parameters, String parameterName) 191 { 192 String dateAsString = MapUtils.getString(parameters, parameterName); 193 return StringUtils.isNotEmpty(dateAsString) ? ZonedDateTime.parse(dateAsString, DateTimeFormatter.ISO_DATE_TIME) : null; 194 } 195 196 private List<Map<String, Object>> _getReportResults(File[] reportFiles, Integer offset, Integer limit, List<Object> sort) throws ProcessingException 197 { 198 List<Map<String, Object>> reports = new LinkedList<>(); 199 200 int count = 0; 201 202 for (File reportFile : _sortFiles(reportFiles, sort)) 203 { 204 if (count >= offset && count < offset + limit) 205 { 206 String filename = reportFile.getName(); 207 208 Map<String, Object> report = new HashMap<>(); 209 report.put(_COLUMN_FILENAME, filename); 210 report.put(_COLUMN_LENGTH, String.valueOf(reportFile.length())); 211 report.put(_COLUMN_LAST_MODIFIED, DateUtils.epochMilliToString(reportFile.lastModified())); 212 213 // Parse manifest.json (if it exists) 214 report.put("properties", _parseManifest(reportFile)); 215 216 File[] logFiles = _getLogsFiles(filename); 217 218 if (logFiles != null && logFiles.length > 0) 219 { 220 if (logFiles.length == 1) 221 { 222 File logFile = logFiles[0]; 223 report.put("logfile", logFile.getName()); 224 } 225 else 226 { 227 throw new ProcessingException("Found more than one logfile with name '" + logFiles[0].getName() + ".log'."); 228 } 229 } 230 231 reports.add(report); 232 } 233 else if (count >= offset + limit) 234 { 235 break; 236 } 237 count++; 238 } 239 240 return reports; 241 } 242 243 private Map<String, Object> _parseManifest(File reportFile) 244 { 245 try (ZipFile zipFile = new ZipFile(reportFile);) 246 { 247 ZipEntry zipEntry = zipFile.getEntry(AbstractPilotageReport.MANIFEST_FILENAME); 248 if (zipEntry != null) 249 { 250 try (InputStream is = zipFile.getInputStream(zipEntry);) 251 { 252 String manifestContent = IOUtils.toString(is, StandardCharsets.UTF_8); 253 Map<String, Object> manifestMap = _jsonUtils.convertJsonToMap(manifestContent); 254 255 Map<String, Object> infos = new HashMap<>(); 256 infos.put(_COLUMN_TYPE, _getReportTypeInfos((String) manifestMap.get("type"))); 257 258 infos.put(_COLUMN_OUTPUT_FORMAT, manifestMap.get(AbstractReportSchedulable.JOBDATAMAP_OUTPUT_FORMAT_KEY)); 259 String target = manifestMap.get("target").toString().toUpperCase(); 260 infos.put(_COLUMN_TARGET, target); 261 if (target.equals(PilotageReportTarget.ORGUNIT.name())) 262 { 263 String contextId = (String) manifestMap.get(OrgUnitReportSchedulable.JOBDATAMAP_ORGUNIT_KEY); 264 if (StringUtils.isNotEmpty(contextId)) 265 { 266 infos.put(_COLUMN_CONTEXT, _getContentInfos((String) manifestMap.get(OrgUnitReportSchedulable.JOBDATAMAP_ORGUNIT_KEY))); 267 } 268 infos.put(_COLUMN_CATALOG, _getCatalogInfos((String) manifestMap.get(OrgUnitReportSchedulable.JOBDATAMAP_CATALOG_KEY))); 269 infos.put(_COLUMN_LANG, _getLanguageInfos((String) manifestMap.get(OrgUnitReportSchedulable.JOBDATAMAP_LANG_KEY))); 270 } 271 else if (target.equals(PilotageReportTarget.PROGRAM.name())) 272 { 273 String programId = manifestMap.get(ProgramReportSchedulable.JOBDATAMAP_PROGRAM_KEY).toString(); 274 infos.put(_COLUMN_CONTEXT, _getContentInfos(programId)); 275 try 276 { 277 Program program = _resolver.resolveById(programId); 278 infos.put(_COLUMN_CATALOG, _getCatalogInfos(program.getCatalog())); 279 infos.put(_COLUMN_LANG, _getLanguageInfos(program.getLanguage())); 280 } 281 catch (UnknownAmetysObjectException e) 282 { 283 getLogger().warn("The content '" + programId + "' has probably been deleted."); 284 } 285 } 286 return infos; 287 } 288 } 289 } 290 catch (IOException e) 291 { 292 getLogger().error("An error occured while parsing the manifest.json file of " + reportFile.getName(), e); 293 } 294 295 return Collections.EMPTY_MAP; 296 } 297 298 private Map<String, Object> _getReportTypeInfos(String typeId) 299 { 300 Map<String, Object> infos = new HashMap<>(); 301 infos.put("value", typeId); 302 303 PilotageReport report = _reportEP.getExtension(typeId); 304 if (report != null) 305 { 306 infos.put("label", report.getLabel()); 307 } 308 309 return infos; 310 } 311 312 private Map<String, Object> _getContentInfos(String contentId) 313 { 314 Map<String, Object> infos = new HashMap<>(); 315 316 infos.put("id", contentId); 317 318 try 319 { 320 Content content = _resolver.resolveById(contentId); 321 infos.put("title", content.getTitle()); 322 infos.put("isSimple", _contentHelper.isSimple(content)); 323 } 324 catch (UnknownAmetysObjectException e) 325 { 326 // Nothing 327 } 328 329 return infos; 330 } 331 332 private Map<String, Object> _getCatalogInfos(String name) 333 { 334 Map<String, Object> infos = new HashMap<>(); 335 infos.put("value", name); 336 337 Catalog catalog = _catalogManager.getCatalog(name); 338 if (catalog != null) 339 { 340 infos.put("label", catalog.getTitle()); 341 } 342 return infos; 343 344 } 345 346 private Map<String, Object> _getLanguageInfos(String lang) 347 { 348 Map<String, Object> infos = new HashMap<>(); 349 infos.put("code", lang); 350 351 if (lang != null) 352 { 353 Language language = _languageManager.getLanguage(lang); 354 if (language != null) 355 { 356 infos.put("icon", language.getSmallIcon()); 357 infos.put("label", language.getLabel()); 358 } 359 } 360 361 return infos; 362 } 363 /** 364 * Get the logs files 365 * @param filename Name of the file to retrieve a log 366 * @return the logs files 367 */ 368 private File[] _getLogsFiles(String filename) 369 { 370 File logDir = _pilotageLogFileManager.getLogsDirectory(); 371 372 int filenameLength = filename.lastIndexOf("."); 373 String logFileName = filename.substring(0, filename.indexOf("-")) + filename.substring(filenameLength - 11, filenameLength) + ".log"; 374 return logDir.listFiles(new PatternFilenameFilter(logFileName)); 375 } 376 377 private List<Object> _getSortList(Object sortValues) 378 { 379 if (sortValues != null) 380 { 381 return _jsonUtils.convertJsonToList(sortValues.toString()); 382 } 383 384 return null; 385 } 386 387 @SuppressWarnings("unchecked") 388 private File[] _sortFiles(File[] files, List<Object> sortList) 389 { 390 if (sortList != null) 391 { 392 for (Object sortValueObj : Lists.reverse(sortList)) 393 { 394 Map<String, Object> sortValue = (Map<String, Object>) sortValueObj; 395 Comparator<File> comparator = _NAME_TO_COMPARATOR.get(sortValue.get("property")); 396 Object direction = sortValue.get("direction"); 397 if (direction != null && direction.toString().equalsIgnoreCase("DESC")) 398 { 399 comparator = Collections.reverseOrder(comparator); 400 } 401 Arrays.sort(files, comparator); 402 } 403 } 404 405 return files; 406 } 407}