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