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