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