001/* 002 * Copyright 2017 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.odfsync.cdmfr; 017 018import java.io.File; 019import java.io.FileFilter; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.Comparator; 027import java.util.Date; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.LinkedHashMap; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.stream.Collectors; 035 036import org.apache.avalon.framework.configuration.Configuration; 037import org.apache.avalon.framework.configuration.ConfigurationException; 038import org.apache.avalon.framework.context.ContextException; 039import org.apache.avalon.framework.context.Contextualizable; 040import org.apache.cocoon.Constants; 041import org.apache.cocoon.ProcessingException; 042import org.apache.cocoon.environment.Context; 043import org.apache.commons.collections.SetUtils; 044import org.apache.commons.collections4.MapUtils; 045import org.apache.commons.io.comparator.LastModifiedFileComparator; 046import org.apache.commons.io.comparator.NameFileComparator; 047import org.apache.commons.io.comparator.SizeFileComparator; 048import org.slf4j.Logger; 049 050import org.ametys.cms.repository.ModifiableContent; 051import org.ametys.core.schedule.progression.ContainerProgressionTracker; 052import org.ametys.core.schedule.progression.SimpleProgressionTracker; 053import org.ametys.core.util.DateUtils; 054import org.ametys.plugins.repository.AmetysObjectIterable; 055import org.ametys.runtime.config.Config; 056import org.ametys.runtime.i18n.I18nizableText; 057 058import com.google.common.collect.Lists; 059 060/** 061 * Class for CDMFr import and synchronization 062 */ 063public class CDMFrSynchronizableContentsCollection extends AbstractCDMFrSynchronizableContentsCollection implements Contextualizable 064{ 065 /** Data source parameter : folder */ 066 protected static final String __PARAM_FOLDER = "folder"; 067 068 private static final String _CRITERIA_FILENAME = "filename"; 069 private static final String _CRITERIA_LAST_MODIFIED_AFTER = "lastModifiedAfter"; 070 private static final String _CRITERIA_LAST_MODIFIED_BEFORE = "lastModifiedBefore"; 071 private static final String _COLUMN_FILENAME = "filename"; 072 private static final String _COLUMN_LAST_MODIFIED = "lastModified"; 073 private static final String _COLUMN_LENGTH = "length"; 074 075 private static final Map<String, Comparator<File>> _NAME_TO_COMPARATOR = new HashMap<>(); 076 static 077 { 078 _NAME_TO_COMPARATOR.put(_COLUMN_FILENAME, NameFileComparator.NAME_INSENSITIVE_COMPARATOR); 079 _NAME_TO_COMPARATOR.put(_COLUMN_LAST_MODIFIED, LastModifiedFileComparator.LASTMODIFIED_COMPARATOR); 080 _NAME_TO_COMPARATOR.put(_COLUMN_LENGTH, SizeFileComparator.SIZE_COMPARATOR); 081 } 082 083 /** The Cocoon context */ 084 protected Context _cocoonContext; 085 086 /** CDM-fr folder */ 087 protected File _cdmfrFolder; 088 089 /** Default language configured for ODF */ 090 protected String _odfLang; 091 092 /** List of synchronized contents (to avoid a treatment twice or more) */ 093 protected Set<String> _updatedContents; 094 095 @Override 096 public void contextualize(org.apache.avalon.framework.context.Context context) throws ContextException 097 { 098 _cocoonContext = (Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 099 } 100 101 @Override 102 public void configure(Configuration configuration) throws ConfigurationException 103 { 104 super.configure(configuration); 105 _updatedContents = new HashSet<>(); 106 } 107 108 @SuppressWarnings("unchecked") 109 @Override 110 protected List<ModifiableContent> _internalPopulate(Logger logger, ContainerProgressionTracker progressionTracker) 111 { 112 _updatedContents.clear(); 113 114 try 115 { 116 _startHandleCDMFR(); 117 118 SimpleProgressionTracker progressionTrackerCDMfrFiles = progressionTracker.addSimpleStep("files", new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_GLOBAL_SYNCHRONIZATION_CDMFR_STEP_LABEL")); 119 120 List<ModifiableContent> contents = new ArrayList<>(); 121 122 Map<String, Object> parameters = new HashMap<>(); 123 parameters.put("validateAfterImport", validateAfterImport()); 124 parameters.put("removalSync", removalSync()); 125 parameters.put("contentPrefix", getContentPrefix()); 126 parameters.put("collectionId", getId()); 127 128 File[] cdmfrFiles = _cdmfrFolder.listFiles(new CDMFrFileFilter(null, null, null)); 129 130 progressionTrackerCDMfrFiles.setSize(cdmfrFiles.length); 131 132 for (File cdmfrFile : cdmfrFiles) 133 { 134 Map<String, Object> resultMap = _handleFile(cdmfrFile, parameters, logger); 135 if (resultMap.containsKey("importedPrograms")) 136 { 137 contents.addAll((List<ModifiableContent>) resultMap.remove("importedPrograms")); 138 } 139 parameters.putAll(resultMap); 140 progressionTrackerCDMfrFiles.increment(); 141 } 142 143 _updatedContents.addAll((Set<String>) parameters.getOrDefault("updatedContents", SetUtils.EMPTY_SET)); 144 _nbCreatedContents += (int) parameters.getOrDefault("nbCreatedContents", 0); 145 _nbSynchronizedContents += (int) parameters.getOrDefault("nbSynchronizedContents", 0); 146 _nbError += (int) parameters.getOrDefault("nbError", 0); 147 148 return contents; 149 } 150 finally 151 { 152 _endHandleCDMFR(_updatedContents); 153 } 154 } 155 156 @SuppressWarnings("unchecked") 157 @Override 158 public List<ModifiableContent> importContent(String idValue, Map<String, Object> additionalParameters, Logger logger) throws Exception 159 { 160 _updatedContents.clear(); 161 162 try 163 { 164 _startHandleCDMFR(); 165 166 File cdmfrFile = new File(_cdmfrFolder, idValue); 167 if (!cdmfrFile.exists()) 168 { 169 logger.error("The file '{}' doesn't exists in the repository '{}'.", idValue, _cdmfrFolder.getAbsolutePath()); 170 } 171 else if (!cdmfrFile.isFile()) 172 { 173 logger.error("The element '{}' is not a file.", cdmfrFile.getAbsolutePath()); 174 } 175 else 176 { 177 Map<String, Object> parameters = new HashMap<>(); 178 parameters.put("validateAfterImport", validateAfterImport()); 179 parameters.put("removalSync", removalSync()); 180 parameters.put("contentPrefix", getContentPrefix()); 181 parameters.put("collectionId", getId()); 182 183 Map<String, Object> resultMap = _handleFile(cdmfrFile, parameters, logger); 184 185 _updatedContents.addAll((Set<String>) resultMap.getOrDefault("updatedContents", SetUtils.EMPTY_SET)); 186 _nbCreatedContents += (int) resultMap.getOrDefault("nbCreatedContents", 0); 187 _nbSynchronizedContents += (int) resultMap.getOrDefault("nbSynchronizedContents", 0); 188 _nbError += (int) resultMap.getOrDefault("nbError", 0); 189 190 return (List<ModifiableContent>) resultMap.getOrDefault("importedPrograms", Collections.emptyList()); 191 } 192 } 193 finally 194 { 195 _endHandleCDMFR(_updatedContents); 196 } 197 198 return null; 199 } 200 201 /** 202 * Handle the CDM-fr file to import all the programs and its dependencies containing into it. 203 * @param cdmfrFile The CDM-fr file 204 * @param parameters Parameters used to import the file 205 * @param logger The logger 206 * @return The list of imported/synchronized programs 207 */ 208 protected Map<String, Object> _handleFile(File cdmfrFile, Map<String, Object> parameters, Logger logger) 209 { 210 String absolutePath = cdmfrFile.getAbsolutePath(); 211 212 logger.info("Processing CDM-fr file '{}'", absolutePath); 213 214 try (InputStream fis = new FileInputStream(cdmfrFile)) 215 { 216 return _importCDMFrComponent.handleInputStream(fis, parameters, this, logger); 217 } 218 catch (IOException e) 219 { 220 logger.error("An error occured while reading or closing the file {}.", absolutePath, e); 221 } 222 catch (ProcessingException e) 223 { 224 logger.error("An error occured while handling the file {}.", absolutePath, e); 225 } 226 catch (Exception e) 227 { 228 logger.error("An unknown error happens while handling the file {}.", absolutePath, e); 229 } 230 231 return new HashMap<>(); 232 } 233 234 @Override 235 public Map<String, Map<String, Object>> search(Map<String, Object> searchParameters, int offset, int limit, List<Object> sort, Logger logger) 236 { 237 File[] cdmfrFiles = internalSearch(searchParameters, logger); 238 239 // Trier 240 cdmfrFiles = _sortFiles(cdmfrFiles, sort); 241 242 // Parser uniquement les fichiers entre offset et limit 243 int i = 0; 244 Map<String, Map<String, Object>> files = new LinkedHashMap<>(); 245 for (File cdmfrFile : cdmfrFiles) 246 { 247 if (i >= offset && i < offset + limit) 248 { 249 Map<String, Object> file = new HashMap<>(); 250 file.put(SCC_UNIQUE_ID, cdmfrFile.getName()); 251 file.put(_COLUMN_FILENAME, cdmfrFile.getName()); 252 file.put(_COLUMN_LAST_MODIFIED, DateUtils.dateToString(new Date(cdmfrFile.lastModified()))); 253 file.put(_COLUMN_LENGTH, cdmfrFile.length()); 254 files.put(cdmfrFile.getName(), file); 255 } 256 else if (i >= offset + limit) 257 { 258 break; 259 } 260 i++; 261 } 262 263 return files; 264 } 265 266 @Override 267 public int getTotalCount(Map<String, Object> searchParameters, Logger logger) 268 { 269 return internalSearch(searchParameters, logger).length; 270 } 271 272 @SuppressWarnings("unchecked") 273 private File[] _sortFiles(File[] files, List<Object> sortList) 274 { 275 if (sortList != null) 276 { 277 for (Object sortValueObj : Lists.reverse(sortList)) 278 { 279 Map<String, Object> sortValue = (Map<String, Object>) sortValueObj; 280 Comparator<File> comparator = _NAME_TO_COMPARATOR.get(sortValue.get("property")); 281 Object direction = sortValue.get("direction"); 282 if (direction != null && direction.toString().equalsIgnoreCase("DESC")) 283 { 284 comparator = Collections.reverseOrder(comparator); 285 } 286 Arrays.sort(files, comparator); 287 } 288 } 289 290 return files; 291 } 292 293 /** 294 * Search values and return the result without any treatment. 295 * @param searchParameters Search parameters to restrict the search 296 * @param logger The logger 297 * @return {@link File} tab listing the available CDM-fr files corresponding to the filter. 298 */ 299 protected File[] internalSearch(Map<String, Object> searchParameters, Logger logger) 300 { 301 String filename = null; 302 Date lastModifiedAfter = null; 303 Date lastModifiedBefore = null; 304 if (searchParameters != null && !searchParameters.isEmpty()) 305 { 306 filename = MapUtils.getString(searchParameters, _CRITERIA_FILENAME); 307 lastModifiedAfter = DateUtils.parse(MapUtils.getString(searchParameters, _CRITERIA_LAST_MODIFIED_AFTER)); 308 lastModifiedBefore = DateUtils.parse(MapUtils.getString(searchParameters, _CRITERIA_LAST_MODIFIED_BEFORE)); 309 } 310 FileFilter filter = new CDMFrFileFilter(filename, lastModifiedAfter, lastModifiedBefore); 311 312 return _cdmfrFolder.listFiles(filter); 313 } 314 315 @Override 316 protected void configureDataSource(Configuration configuration) throws ConfigurationException 317 { 318 configureSpecificParameters(); 319 320 _odfLang = Config.getInstance().getValue("odf.programs.lang"); 321 } 322 323 /** 324 * Configure the specific parameters of this implementation of CDM-fr import. 325 */ 326 protected void configureSpecificParameters() 327 { 328 String cdmfrFolderPath = getParameterValues().get(__PARAM_FOLDER).toString(); 329 330 _cdmfrFolder = new File(cdmfrFolderPath); 331 if (!_cdmfrFolder.isAbsolute()) 332 { 333 // No : consider it relative to context path 334 _cdmfrFolder = new File(_cocoonContext.getRealPath("/" + cdmfrFolderPath)); 335 } 336 337 if (!_cdmfrFolder.isDirectory()) 338 { 339 throw new RuntimeException("The path '" + cdmfrFolderPath + "' defined in the SCC '" + getLabel().getLabel() + "' (" + getId() + ") is not a directory."); 340 } 341 342 if (!_cdmfrFolder.canRead()) 343 { 344 throw new RuntimeException("The folder '" + cdmfrFolderPath + "' defined in the SCC '" + getLabel().getLabel() + "' (" + getId() + ") is not readable."); 345 } 346 } 347 348 @Override 349 protected void configureSearchModel() 350 { 351 _searchModelConfiguration.displayMaskImported(false); 352 _searchModelConfiguration.addCriterion(_CRITERIA_FILENAME, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_IMPORT_CDMFR_CRITERIA_FILENAME")); 353 _searchModelConfiguration.addCriterion(_CRITERIA_LAST_MODIFIED_AFTER, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_IMPORT_CDMFR_CRITERIA_LASTMODIFIED_AFTER"), "DATETIME", "edition.date"); 354 _searchModelConfiguration.addCriterion(_CRITERIA_LAST_MODIFIED_BEFORE, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_IMPORT_CDMFR_CRITERIA_LASTMODIFIED_BEFORE"), "DATETIME", "edition.date"); 355 _searchModelConfiguration.addColumn(_COLUMN_FILENAME, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_IMPORT_CDMFR_COLUMN_FILENAME"), 350); 356 _searchModelConfiguration.addColumn(_COLUMN_LAST_MODIFIED, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_IMPORT_CDMFR_COLUMN_DATE"), 200, true, "DATETIME"); 357 _searchModelConfiguration.addColumn(_COLUMN_LENGTH, new I18nizableText("plugin.odf-sync", "PLUGINS_ODF_SYNC_IMPORT_CDMFR_COLUMN_SIZE"), 120, true, "DOUBLE", "Ext.util.Format.fileSize"); 358 } 359 360 @Override 361 public ModifiableContent getContent(String lang, String idValue, boolean forceStrictCheck) 362 { 363 return null; 364 } 365 366 @Override 367 public boolean handleRightAssignmentContext() 368 { 369 return false; 370 } 371 372 @Override 373 protected List<ModifiableContent> _getContentsToRemove(AmetysObjectIterable<ModifiableContent> contents) 374 { 375 return contents.stream() 376 .filter(content -> !_updatedContents.contains(content.getId())) 377 .collect(Collectors.toList()); 378 } 379}