001/*
002 *  Copyright 2010 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.skineditor.resources;
017
018import java.io.IOException;
019import java.nio.file.Path;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.component.Component;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027import org.apache.cocoon.servlet.multipart.Part;
028import org.apache.excalibur.source.SourceResolver;
029import org.apache.excalibur.source.impl.FileSource;
030
031import org.ametys.core.file.FileHelper;
032import org.ametys.core.right.RightManager;
033import org.ametys.core.right.RightManager.RightResult;
034import org.ametys.core.ui.Callable;
035import org.ametys.core.user.CurrentUserProvider;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.plugins.skincommons.SkinEditionHelper;
038import org.ametys.plugins.skincommons.SkinLockManager;
039import org.ametys.plugins.skineditor.skin.SkinDAO;
040import org.ametys.web.skin.Skin;
041import org.ametys.web.skin.SkinsManager;
042
043/**
044 * DAO for files and folders inside a skin directory
045 */
046public class SkinResourceDAO implements Serviceable, Component
047{
048    /** Constant for skin editor tool id */
049    public static final String ROLE = SkinResourceDAO.class.getName();
050    /** Constant for skin editor tool id */
051    public static final String SKIN_EDITOR_TOOL_ID = "uitool-skineditor";
052    
053    private static final List<String> __IGNORED_SOURCE = List.of(".svn", ".cvs", ".lock", "CVS", ".gitignore");
054    
055    /** The lock manager */
056    protected SkinLockManager _lockManager;
057    /** The skin edition helper */
058    protected SkinEditionHelper _skinHelper;
059    /** The file helper */
060    protected FileHelper _fileHelper;
061    /** The source resolver */
062    protected SourceResolver _srcResolver;
063    /** The current user provider */
064    protected CurrentUserProvider _currentUserProvider;
065    /** The rights manager */
066    protected RightManager _rightManager;
067    /** The skins manager */
068    protected SkinsManager _skinManager;
069
070    @Override
071    public void service(ServiceManager manager) throws ServiceException
072    {
073        _skinHelper = (SkinEditionHelper) manager.lookup(SkinEditionHelper.ROLE);
074        _skinManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
075        _fileHelper = (FileHelper) manager.lookup(FileHelper.ROLE);
076        _lockManager = (SkinLockManager) manager.lookup(SkinLockManager.ROLE);
077        _srcResolver = (org.apache.excalibur.source.SourceResolver) manager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);
078        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
079        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
080    }
081    
082    /**
083     * Gets skin's files and folders contained in the given path.
084     * @param skinName The skin name
085     * @param path the relative file's path from temporary skin root directory
086     * @return the list of skin's files and folders
087     * @throws IOException If an error occurred while listing files
088     */
089    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
090    public List<Map<String, Object>> getFiles(String skinName, String path) throws IOException
091    {
092        _checkUserRight(skinName);
093        
094        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
095        
096        List<Map<String, Object>> files = _fileHelper.getFiles(tempDirURI, path, __IGNORED_SOURCE);
097        
098        files.stream().forEach(file -> file.put("skinName", skinName));
099        
100        return files;
101    }
102    
103    /**
104     * Saves the text in file
105     * @param skinName The name of the skin containing the resource
106     * @param relPath the relative path of file under the skin directory
107     * @param text the file content to save
108     * @return The result map
109     * @throws IOException If an error occurred while saving
110     */
111    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
112    public Map<String, Object> save(String skinName, String relPath, String text) throws IOException
113    {
114        _checkUserRight(skinName);
115        
116        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
117        
118        String fileURI = tempDirURI + (relPath.length() > 0 ? "/" + relPath : "");
119        
120        Map<String, Object> result = _fileHelper.saveFile(fileURI, text);
121        
122        if (result.containsKey("isI18n"))
123        {
124            _skinHelper.invalidateTempSkinCatalogues(skinName);
125        }
126        
127        Path tempDir = _skinHelper.getTempDirectory(skinName);
128        // Update lock
129        _lockManager.updateLockFile(tempDir, SKIN_EDITOR_TOOL_ID);
130        
131        return result;
132    }
133    
134    /**
135     * checks if the resource already exists in the parent
136     * @param skinName The name of the skin containing the resource
137     * @param parentRelPath The parent path
138     * @param fileName the file name to check
139     * @return true if the file exists
140     * @throws IOException if something goes wrong while trying to retrieve a file
141     */
142    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
143    public boolean checkSourceExists(String skinName, String parentRelPath, String fileName) throws IOException
144    {
145        _checkUserRight(skinName);
146        
147        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
148        String parentFileURI = tempDirURI + (parentRelPath.length() > 0 ? "/" + parentRelPath : "");
149        
150        return _fileHelper.hasChild(parentFileURI, fileName);
151    }
152    
153    /**
154     * Copy a file or directory
155     * @param skinName the name of the current skin
156     * @param srcPath the path of the source file or directory
157     * @param parentTargetPath the new path for the source file or directory
158     * @return a map of data
159     * @throws IOException if something went wrong during the source copy processing
160     */
161    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
162    public Map<String, Object> copySource(String skinName, String srcPath, String parentTargetPath) throws IOException
163    {
164        _checkUserRight(skinName);
165        
166        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
167        
168        String srcFileURI = tempDirURI + (srcPath.length() > 0 ? "/" + srcPath : "");
169        String parentTargetURI = tempDirURI + (parentTargetPath.length() > 0 ? "/" + parentTargetPath : "");
170        
171        Map<String, Object> result = _fileHelper.copySource(srcFileURI, parentTargetURI);
172        
173        // Update lock
174        FileSource rootDir = (FileSource) _srcResolver.resolveURI(tempDirURI);
175        _lockManager.updateLockFile(rootDir.getFile().toPath(), SKIN_EDITOR_TOOL_ID);
176        
177        if (result.containsKey("uri"))
178        {
179            String folderUri = (String) result.get("uri");
180            String path = folderUri.substring(rootDir.getURI().length());
181            result.put("path", path.endsWith("/") ? path.substring(0, path.length() - 1) : path);
182            result.put("skinName", skinName);
183        }
184        
185        return result;
186    }
187    
188    /**
189     * Create a folder
190     * @param skinName the name of the current skin
191     * @param parentRelPath the path of the parent containing the folder
192     * @param originalName the name of the new folder
193     * @param renameIfExists if true, will generate a valid name if "originalName" already exists.
194     * @return a map of data
195     * @throws IOException if an error occurs while manipulating files
196     */
197    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
198    public Map addFolder(String skinName, String parentRelPath, String originalName, boolean renameIfExists) throws IOException
199    {
200        _checkUserRight(skinName);
201        
202        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
203        String parentURI = tempDirURI + (parentRelPath.length() > 0 ? "/" + parentRelPath : "");
204        
205        Map<String, Object> result = _fileHelper.addFolder(parentURI, originalName, renameIfExists);
206        
207        // Update lock
208        FileSource rootDir = (FileSource) _srcResolver.resolveURI(tempDirURI);
209        _lockManager.updateLockFile(rootDir.getFile().toPath(), SKIN_EDITOR_TOOL_ID);
210       
211        if (result.containsKey("uri"))
212        {
213            String folderUri = (String) result.get("uri");
214            String path = folderUri.substring(rootDir.getURI().length());
215            result.put("path", path.endsWith("/") ? path.substring(0, path.length() - 1) : path);
216            result.put("parentPath", parentRelPath.endsWith("/") ? parentRelPath.substring(0, parentRelPath.length() - 1) : parentRelPath);
217            result.put("skinName", skinName);
218        }
219        
220        return result;
221    }
222    
223    /**
224     * Add or update a file into a skin
225     * @param skinName the name of the skin
226     * @param part The file multipart to upload
227     * @param toPath The path of parent directory
228     * @param mode The insertion mode: 'add-rename' or 'update' or null.
229     * @param unzip true to unzip .zip file
230     * @return the result map
231     * @throws IOException If an error occurred manipulating the file
232     */
233    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
234    public Map addOrUpdateFile(String skinName, Part part, String toPath, String mode, boolean unzip) throws IOException
235    {
236        _checkUserRight(skinName);
237        
238        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
239        FileSource tempDir = (FileSource) _srcResolver.resolveURI(tempDirURI);
240        
241        String parentURI = tempDirURI + (toPath.length() > 0 ? "/" + toPath : "");
242        FileSource parentDir = (FileSource) _srcResolver.resolveURI(parentURI);
243        
244        Map<String, Object> result = _fileHelper.addOrUpdateFile(part, parentDir, mode, unzip);
245        
246        // Update lock
247        _lockManager.updateLockFile(tempDir.getFile().toPath(), SKIN_EDITOR_TOOL_ID);
248        
249        if (result.containsKey("uri"))
250        {
251            String fileUri = (String) result.get("uri");
252            String filePath = fileUri.substring(tempDir.getURI().length());
253            result.put("path", filePath.endsWith("/") ? filePath.substring(0, filePath.length() - 1) : filePath);
254            String parentPath = parentDir.getURI().substring(tempDir.getURI().length());
255            result.put("parentPath", parentPath.endsWith("/") ? parentPath.substring(0, parentPath.length() - 1) : parentPath);
256        }
257        else if (result.containsKey("unzip"))
258        {
259            String parentPath = parentDir.getURI().substring(tempDir.getURI().length());
260            result.put("parentPath", parentPath.endsWith("/") ? parentPath.substring(0, parentPath.length() - 1) : parentPath);
261        }
262        
263        return result;
264    }
265
266
267    /**
268     * Delete a file or a directory
269     * @param skinName the name of the current skin
270     * @param relPath the path of the file or directory
271     * @return a map of data
272     * @throws IOException if an error occurs while manipulating files
273     */
274    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
275    public Map deleteFile(String skinName, String relPath) throws IOException
276    {
277        _checkUserRight(skinName);
278        
279        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
280        String fileURI = tempDirURI + (relPath.length() > 0 ? "/" + relPath : "");
281        
282        Map result = _fileHelper.deleteFile(fileURI);
283        
284        // Update lock
285        Path tempDir = _skinHelper.getTempDirectory(skinName);
286        _lockManager.updateLockFile(tempDir, SKIN_EDITOR_TOOL_ID);
287        
288        return result;
289    }
290    
291    /**
292     * Move a file or a directory
293     * @param skinName the name of current skin
294     * @param srcPath the path of the file or directory
295     * @param targetPath the targeted path
296     * @return a map of data
297     * @throws IOException if something goes wrong during the source moving process
298     */
299    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
300    public Map<String, Object> moveSource(String skinName, String srcPath, String targetPath) throws IOException
301    {
302        _checkUserRight(skinName);
303        
304        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
305        
306        String srcFileURI = tempDirURI + (srcPath.length() > 0 ? "/" + srcPath : "");
307        String parentTargetURI = tempDirURI + (targetPath.length() > 0 ? "/" + targetPath : "");
308        
309        Map<String, Object> result = _fileHelper.moveSource(srcFileURI, parentTargetURI);
310        
311        // Update lock
312        FileSource rootDir = (FileSource) _srcResolver.resolveURI(tempDirURI);
313        _lockManager.updateLockFile(rootDir.getFile().toPath(), SKIN_EDITOR_TOOL_ID);
314
315        if (result.containsKey("uri"))
316        {
317            String folderUri = (String) result.get("uri");
318            String path = folderUri.substring(rootDir.getURI().length());
319            result.put("path", path.endsWith("/") ? path.substring(0, path.length() - 1) : path);
320            result.put("skinName", skinName);
321        }
322        
323        return result;
324    }
325    
326    /**
327     * Rename a file or a directory
328     * @param skinName the current skin name
329     * @param relPath the path of the file
330     * @param name the new name
331     * @return a map of data
332     * @throws IOException if something goes wrong when renaming the source
333     */
334    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
335    public Map renameSource(String skinName, String relPath, String name) throws IOException
336    {
337        _checkUserRight(skinName);
338        
339        String tempDirURI = _skinHelper.getTempDirectoryURI(skinName);
340        String fileURI = tempDirURI + (relPath.length() > 0 ? "/" + relPath : "");
341        
342        Map<String, Object> result = _fileHelper.renameFile(fileURI, name);
343        
344        // Update lock
345        FileSource rootDir = null;
346        try
347        {
348            rootDir = (FileSource) _srcResolver.resolveURI(tempDirURI);
349            _lockManager.updateLockFile(rootDir.getFile().toPath(), SKIN_EDITOR_TOOL_ID);
350        }
351        finally
352        {
353            _srcResolver.release(rootDir);
354        }
355        
356        if (result.containsKey("uri"))
357        {
358            String newURI = (String) result.get("uri");
359            String path = newURI.substring(rootDir.getURI().length());
360            result.put("path", path);
361            result.put("name", name);
362            result.put("skinName", skinName);
363        }
364        
365        return result;
366    }
367    
368    private void _checkUserRight(String skinName) throws IllegalStateException
369    {
370        UserIdentity user = _currentUserProvider.getUser();
371        
372        Skin skin = _skinManager.getSkin(skinName);
373        if (skin == null)
374        {
375            throw new IllegalStateException("User '" + user + "' tried to modify an unknown skin '" + skinName + "'");
376        }
377        
378        if (!skin.isModifiable())
379        {
380            throw new IllegalStateException("User '" + user + "' tried to modify an unmodifiable skin '" + skinName + "'");
381        }
382        
383        if (!(_rightManager.hasRight(user, SkinDAO.EDIT_SKINS_RIGHT_ID, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW || _rightManager.hasRight(user, SkinDAO.EDIT_CURRENT_SKIN_RIGHT_ID, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW && skinName.equals(_skinManager.getSkinNameFromRequest())))
384        {
385            throw new IllegalStateException("User '" + user + "' tried to modify skin '" + skinName + "' without convenient right.");
386        }
387    }
388}