001/* 002 * Copyright 2013 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.skincommons; 017 018import java.io.IOException; 019import java.nio.file.Files; 020import java.nio.file.Path; 021import java.util.Collections; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.Iterator; 025import java.util.Map; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang.StringUtils; 032 033import org.ametys.core.ui.Callable; 034import org.ametys.core.user.CurrentUserProvider; 035import org.ametys.core.user.User; 036import org.ametys.core.user.UserIdentity; 037import org.ametys.core.user.UserManager; 038import org.ametys.core.util.DateUtils; 039import org.ametys.core.util.path.PathUtils; 040import org.ametys.runtime.config.Config; 041import org.ametys.web.repository.page.Page; 042import org.ametys.web.repository.site.Site; 043import org.ametys.web.repository.site.SiteManager; 044import org.ametys.web.skin.Skin; 045import org.ametys.web.skin.SkinsManager; 046 047/** 048 * DAO for skin edition. 049 */ 050public class CommonSkinDAO implements Serviceable, Component 051{ 052 private static final String __TEMP_MODE = "temp"; 053 private static final String __WORK_MODE = "work"; 054 055 private SiteManager _siteManager; 056 private SkinEditionHelper _skinHelper; 057 private SkinLockManager _lockManager; 058 private CurrentUserProvider _userProvider; 059 private UserManager _userManager; 060 private SkinsManager _skinManager; 061 062 @Override 063 public void service(ServiceManager smanager) throws ServiceException 064 { 065 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 066 _skinManager = (SkinsManager) smanager.lookup(SkinsManager.ROLE); 067 _skinHelper = (SkinEditionHelper) smanager.lookup(SkinEditionHelper.ROLE); 068 _lockManager = (SkinLockManager) smanager.lookup(SkinLockManager.ROLE); 069 _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 070 _userManager = (UserManager) smanager.lookup(UserManager.ROLE); 071 } 072 073 /** 074 * Get the URI to preview a site 075 * @param siteName the site name 076 * @param lang the site langage 077 * @return The uri 078 */ 079 @Callable 080 public String getPreviewURI(String siteName, String lang) 081 { 082 Site site = _siteManager.getSite(siteName); 083 084 String siteLangage = !StringUtils.isEmpty(lang) ? lang : site.getSitemaps().iterator().next().getName(); 085 086 if (site.getSitemap(siteLangage).hasChild("index")) 087 { 088 return siteLangage + "/index.html"; 089 } 090 else 091 { 092 Iterator<? extends Page> it = site.getSitemap(siteLangage).getChildrenPages().iterator(); 093 094 if (it.hasNext()) 095 { 096 String path = it.next().getPathInSitemap(); 097 return siteLangage + "/" + path + ".html"; 098 } 099 } 100 101 return null; 102 } 103 104 /** 105 * Check if there is unsaved or uncomitted changes 106 * @param skinName The skin name 107 * @return The result 108 * @throws IOException if an error occurs while manipulating files 109 */ 110 @Callable 111 public Map<String, Object> checkUnsaveModifications (String skinName) throws IOException 112 { 113 Map<String, Object> result = new HashMap<>(); 114 115 Path tempDir = _skinHelper.getTempDirectory(skinName); 116 Path workDir = _skinHelper.getWorkDirectory(skinName); 117 Path skinDir = _skinHelper.getSkinDirectory(skinName); 118 119 if (!_lockManager.canWrite(tempDir)) 120 { 121 // Unecessary to check unsave modifications, the user has no right anymore 122 return result; 123 } 124 125 long lastModifiedLock = _lockManager.lastModified(tempDir).getTime(); 126 if (lastModifiedLock <= Files.getLastModifiedTime(workDir).toMillis()) 127 { 128 if (Files.getLastModifiedTime(workDir).toMillis() > Files.getLastModifiedTime(skinDir).toMillis()) 129 { 130 result.put("hasUncommitChanges", true); 131 } 132 else 133 { 134 PathUtils.deleteDirectory(workDir); 135 } 136 } 137 else if (lastModifiedLock >= Files.getLastModifiedTime(skinDir).toMillis()) 138 { 139 // The modifications were not saved 140 result.put("hasUnsaveChanges", true); 141 } 142 143 return result; 144 } 145 146 /** 147 * Save the current skin into the skin work folder 148 * @param skinName The name of the skin 149 * @param quit True if the temp directory can be removed 150 * @return The name of the skin 151 * @throws IOException if an error occurs while manipulating files 152 */ 153 @Callable 154 public Map<String, Object> saveChanges(String skinName, boolean quit) throws IOException 155 { 156 Map<String, Object> lockInfos = _checkLock(skinName); 157 if (!lockInfos.isEmpty()) 158 { 159 return lockInfos; 160 } 161 162 Path tempDir = _skinHelper.getTempDirectory(skinName); 163 Path workDir = _skinHelper.getWorkDirectory(skinName); 164 165 if (Files.exists(workDir)) 166 { 167 // Delete work directory 168 _skinHelper.deleteQuicklyDirectory(workDir); 169 } 170 171 if (quit) 172 { 173 // Move to work directory 174 PathUtils.moveDirectory(tempDir, workDir); 175 176 // Remove lock 177 PathUtils.deleteQuietly(workDir.resolve(".lock")); 178 } 179 else 180 { 181 // Do a copy in work directory 182 PathUtils.copyDirectory(tempDir, workDir, file -> !file.getFileName().toString().equals(".lock"), false); 183 } 184 185 Map<String, Object> result = new HashMap<>(); 186 result.put("skinName", skinName); 187 return result; 188 } 189 190 /** 191 * Commit the changes made to the skin 192 * @param skinName the name of the skin 193 * @param quit True to remove the temporary directory 194 * @return A map with information 195 * @throws Exception if an error occurs when committing the skin changes 196 */ 197 @Callable 198 public Map<String, Object> commitChanges(String skinName, boolean quit) throws Exception 199 { 200 Skin skin = _skinManager.getSkin(skinName); 201 if (!skin.isModifiable()) 202 { 203 throw new IllegalStateException("The skin '" + skinName + "' is not modifiable and thus cannot be modified."); 204 } 205 206 Map<String, Object> lockInfos = _checkLock(skinName); 207 if (!lockInfos.isEmpty()) 208 { 209 return lockInfos; 210 } 211 212 Path skinDir = _skinHelper.getSkinDirectory(skinName); 213 214 // Do a backup (move skin directory to backup directory) 215 Path backupDir = _skinHelper.createBackupFile(skinName); 216 217 // Move temporary version to current skin 218 Path tempDir = _skinHelper.getTempDirectory(skinName); 219 PathUtils.moveDirectory(tempDir, skinDir); 220 221 Path workDir = _skinHelper.getWorkDirectory(skinName); 222 if (quit) 223 { 224 // Delete work version 225 _skinHelper.deleteQuicklyDirectory(workDir); 226 } 227 else 228 { 229 // Do a copy in work directory 230 PathUtils.copyDirectory(skinDir, workDir, file -> !file.getFileName().toString().equals(".lock"), true); 231 232 // Do a copy in temp directory 233 PathUtils.copyDirectory(skinDir, tempDir, true); 234 } 235 236 // Delete lock file 237 PathUtils.deleteQuietly(skinDir.resolve(".lock")); 238 239 // Invalidate caches 240 _skinHelper.invalidateCaches(skinName); 241 _skinHelper.invalidateSkinCatalogues(skinName); 242 243 // Remove old backup (keep only the 5 last backup) 244 _skinHelper.deleteOldBackup(skinName, 5); 245 246 Map<String, Object> result = new HashMap<>(); 247 248 result.put("backupFilename", backupDir.getFileName().toString()); 249 250 String mailSysAdmin = Config.getInstance().getValue("smtp.mail.sysadminto"); 251 if (!mailSysAdmin.isEmpty()) 252 { 253 result.put("adminEmail", mailSysAdmin); 254 } 255 256 return result; 257 } 258 259 /** 260 * Cancel the current modification to the skin 261 * @param skinName The name of the skin 262 * @param workVersion True it is the work version, false for the temp version 263 * @param toolId the id of the tool 264 * @return True if some changes were canceled 265 * @throws IOException if an error occurs while manipulating files 266 */ 267 @Callable 268 public Map<String, Object> cancelChanges(String skinName, boolean workVersion, String toolId) throws IOException 269 { 270 Map<String, Object> lockInfos = _checkLock(skinName); 271 if (!lockInfos.isEmpty()) 272 { 273 return lockInfos; 274 } 275 276 String modelBeforeCancel = _skinHelper.getTempModel(skinName); 277 278 Path tempDir = _skinHelper.getTempDirectory(skinName); 279 if (Files.exists(tempDir)) 280 { 281 // Delete current temp version 282 _skinHelper.deleteQuicklyDirectory(tempDir); 283 } 284 285 Path workDir = _skinHelper.getWorkDirectory(skinName); 286 if (workVersion && Files.exists(workDir)) 287 { 288 // Back from the last saved work version 289 PathUtils.copyDirectory(workDir, tempDir); 290 } 291 else 292 { 293 if (Files.exists(workDir)) 294 { 295 // Delete work version 296 _skinHelper.deleteQuicklyDirectory(workDir); 297 } 298 299 // Back from current skin 300 Path skinDir = _skinHelper.getSkinDirectory(skinName); 301 PathUtils.copyDirectory(skinDir, tempDir); 302 } 303 304 String modelAfterCancel = _skinHelper.getTempModel(skinName); 305 306 _lockManager.updateLockFile(tempDir, !toolId.isEmpty() ? toolId : "uitool-skineditor"); 307 308 // Invalidate i18n. 309 _skinHelper.invalidateTempSkinCatalogues(skinName); 310 311 Map<String, Object> result = new HashMap<>(); 312 result.put("hasChanges", modelAfterCancel == null || !modelAfterCancel.equals(modelBeforeCancel)); 313 return result; 314 } 315 316 private Map<String, Object> _checkLock (String skinName) throws IOException 317 { 318 Path tempDir = _skinHelper.getTempDirectory(skinName); 319 320 if (!_lockManager.canWrite(tempDir)) 321 { 322 Map<String, Object> result = new HashMap<>(); 323 324 UserIdentity lockOwner = _lockManager.getLockOwner(tempDir); 325 User user = _userManager.getUser(lockOwner.getPopulationId(), lockOwner.getLogin()); 326 327 result.put("isLocked", true); 328 result.put("lockOwner", user != null ? user.getFullName() + " (" + lockOwner + ")" : lockOwner); 329 result.put("success", false); 330 331 return result; 332 } 333 334 // Not lock 335 return Collections.EMPTY_MAP; 336 } 337 338 /** 339 * Get the model for the skin 340 * @param siteName the site name. Can be null if skinName is not null. 341 * @param skinName the skin name. Can be null if siteName is not null. 342 * @param mode the edition mode. Can be null. 343 * @return the model's name or null if there is no model for this skin 344 */ 345 @Callable 346 public String getSkinModel(String siteName, String skinName, String mode) 347 { 348 String skinId = _getSkinName (siteName, skinName); 349 350 if (__TEMP_MODE.equals(mode)) 351 { 352 return _skinHelper.getTempModel(skinId); 353 } 354 else if (__WORK_MODE.equals(mode)) 355 { 356 return _skinHelper.getWorkModel(skinId); 357 } 358 else 359 { 360 return _skinHelper.getSkinModel(skinId); 361 } 362 } 363 364 private String _getSkinName (String siteName, String skinName) 365 { 366 if (StringUtils.isEmpty(skinName) && StringUtils.isNotEmpty(siteName)) 367 { 368 Site site = _siteManager.getSite(siteName); 369 return site.getSkinId(); 370 } 371 372 return skinName; 373 } 374 375 /** 376 * Get lock informations on a skin 377 * @param siteName the site name. Can be null if skinName is not null. 378 * @param skinName the skin name. Can be null if siteName is not null. 379 * @return Informations about the lock 380 * @throws IOException if an error occurs 381 */ 382 @Callable 383 public Map<String, Object> getLock(String siteName, String skinName) throws IOException 384 { 385 Map<String, Object> result = new HashMap<>(); 386 387 String skinId = _getSkinName(siteName, skinName); 388 389 Path tempDir = _skinHelper.getTempDirectory(skinId); 390 if (Files.exists(tempDir) && _lockManager.isLocked(tempDir)) 391 { 392 UserIdentity lockOwner = _lockManager.getLockOwner(tempDir); 393 User user = _userManager.getUser(lockOwner.getPopulationId(), lockOwner.getLogin()); 394 395 result.put("isLocked", !_userProvider.getUser().equals(lockOwner)); 396 result.put("lockOwner", user != null ? user.getFullName() + " (" + lockOwner + ")" : lockOwner); 397 result.put("lastModified", DateUtils.dateToString(_lockManager.lastModified(tempDir))); 398 result.put("toolId", _lockManager.getLockTool(tempDir)); 399 } 400 else 401 { 402 result.put("isLocked", false); 403 } 404 405 Path workDir = _skinHelper.getWorkDirectory(skinId); 406 if (Files.exists(workDir)) 407 { 408 result.put("lastSave", DateUtils.dateToString(new Date(Files.getLastModifiedTime(workDir).toMillis()))); 409 } 410 411 return result; 412 } 413 414 /** 415 * Revert changes and back to last work version or to current skin 416 * @param skinName The skin name 417 * @param workVersion true to get back the work version 418 * @return The skin name in case of success or lock infos if the skin is locked. 419 * @throws IOException if an error occurs 420 */ 421 @Callable 422 public Map<String, Object> clearModifications (String skinName, boolean workVersion) throws IOException 423 { 424 Map<String, Object> lockInfos = _checkLock(skinName); 425 if (!lockInfos.isEmpty()) 426 { 427 return lockInfos; 428 } 429 430 Path tempDir = _skinHelper.getTempDirectory(skinName); 431 if (Files.exists(tempDir)) 432 { 433 // Delete current temp version 434 _skinHelper.deleteQuicklyDirectory(tempDir); 435 } 436 437 if (workVersion) 438 { 439 Path workDir = _skinHelper.getWorkDirectory(skinName); 440 if (Files.exists(workDir)) 441 { 442 // Delete current work version 443 _skinHelper.deleteQuicklyDirectory(workDir); 444 } 445 } 446 447 Map<String, Object> result = new HashMap<>(); 448 result.put("skinName", skinName); 449 return result; 450 } 451}