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}