001/*
002 *  Copyright 2019 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.extraction.edition;
017
018import java.io.File;
019import java.io.IOException;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.commons.lang3.StringUtils;
028import org.apache.commons.lang3.Strings;
029import org.apache.excalibur.source.SourceResolver;
030import org.apache.excalibur.source.TraversableSource;
031import org.apache.excalibur.source.impl.FileSource;
032
033import org.ametys.core.file.FileHelper;
034import org.ametys.core.ui.Callable;
035import org.ametys.core.ui.StaticClientSideElement;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.core.util.URIUtils;
038import org.ametys.plugins.extraction.ExtractionConstants;
039import org.ametys.plugins.extraction.execution.Extraction;
040import org.ametys.plugins.extraction.execution.Extraction.ExtractionProfile;
041import org.ametys.plugins.extraction.execution.ExtractionDAO;
042import org.ametys.plugins.extraction.execution.ExtractionDefinitionReader;
043import org.ametys.plugins.extraction.rights.ExtractionAccessController;
044import org.ametys.runtime.authentication.AccessDeniedException;
045import org.ametys.runtime.util.AmetysHomeHelper;
046
047/**
048 * Component for operations on extraction files
049 */
050public class ExtractionFilesClientSideElement extends StaticClientSideElement
051{
052    private FileHelper _fileHelper;
053    private SourceResolver _srcResolver;
054    private ExtractionDAO _extractionDAO;
055    private ExtractionDefinitionReader _definitionReader;
056    
057    @Override
058    public void service(ServiceManager serviceManager) throws ServiceException
059    {
060        super.service(serviceManager);
061        _fileHelper = (FileHelper) serviceManager.lookup(FileHelper.ROLE);
062        _srcResolver = (org.apache.excalibur.source.SourceResolver) serviceManager.lookup(org.apache.excalibur.source.SourceResolver.ROLE);        
063        _extractionDAO = (ExtractionDAO) serviceManager.lookup(ExtractionDAO.ROLE);
064        _definitionReader = (ExtractionDefinitionReader) serviceManager.lookup(ExtractionDefinitionReader.ROLE);
065    }
066
067    /**
068     * Gets parameters files and folders contained in the given path.
069     * @param path the relative file's path from parameters files root directory
070     * @param profileId The extraction profile used to get the files
071     * @return the list of parameters files and folders
072     * @throws IOException If an error occurred while listing files
073     */
074    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
075    public List<Map<String, Object>> getDefinitionFiles(String path, String profileId) throws IOException
076    {
077        ExtractionProfile profile = StringUtils.isNotEmpty(profileId) ? ExtractionProfile.valueOf(profileId.toUpperCase()) : ExtractionProfile.READ_ACCESS;
078        UserIdentity currentUser = _currentUserProvider.getUser();
079        
080        TraversableSource rootDir = (TraversableSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR);
081        TraversableSource currentDir = (TraversableSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR + (path.length() > 0 ? "/" + path : ""));
082
083        if (!currentDir.exists() || !currentDir.isCollection())
084        {
085            throw new IOException("The source folder '" + currentDir.getURI() + "' does not exist or is not a folder.");
086        }
087        
088        List<Map<String, Object>> nodes = new ArrayList<>();
089        
090        for (TraversableSource child : (Collection<TraversableSource>) currentDir.getChildren())
091        {
092            if (!_isIgnoredSource(child))
093            {
094                if (child.isCollection())
095                {
096                    if (_checkContainerAccess(profile, currentUser, child))
097                    {
098                        nodes.add(_extractionContainer2JsonObject(child, rootDir));
099                    }
100                }
101                else if (_checkExtractionAccess(profile, currentUser, child))
102                {
103                    try
104                    {
105                        Extraction extraction = _definitionReader.readExtractionDefinitionFile(((FileSource) child).getFile());
106                        nodes.add(_extraction2JsonObject(extraction, child, rootDir));
107                    }
108                    catch (Exception e)
109                    {
110                        throw new IOException("Failed read extraction definition file at uri " + child.getURI(), e);
111                    }
112                }
113            }
114        }
115        return nodes;
116    }
117    
118    /**
119     * Gets the result files of extractions
120     * @param path the relative file's path from the results root directory
121     * @return the results files
122     * @throws IOException If an error occurred while listing files
123     */
124    @Callable (rights = ExtractionConstants.EXECUTE_EXTRACTION_RIGHT_ID)
125    public List<Map<String, Object>> getResultFiles(String path) throws IOException
126    {
127        String rootUri = new File(AmetysHomeHelper.getAmetysHomeData(), ExtractionConstants.RESULT_EXTRACTION_DIR_NAME).toURI().toString();
128        
129        List<Map<String, Object>> files = _fileHelper.getFiles(rootUri, path, List.of());
130
131        files.stream().forEach(file -> file.put("downloadUrl", URIUtils.encodePath((String) file.get("path"))));
132
133        return files;
134    }
135    
136    
137    private boolean _checkContainerAccess(ExtractionProfile profile, UserIdentity user, TraversableSource src)
138    {
139        switch (profile)
140        {
141            case WRITE_ACCESS:
142                return _extractionDAO.canWrite(user, src) || _extractionDAO.hasAnyWritableDescendant(user, src, true);
143            case RIGHT_ACCESS:
144                return _extractionDAO.canAssignRights(user, src) || _extractionDAO.hasAnyAssignableDescendant(user, src);
145            case READ_ACCESS:
146            default:
147                return _extractionDAO.canRead(user, src) || _extractionDAO.hasAnyReadableDescendant(user, src);
148        }
149    }
150    
151    private boolean _checkExtractionAccess(ExtractionProfile profile, UserIdentity user, TraversableSource src)
152    {
153        switch (profile)
154        {
155            case WRITE_ACCESS:
156                return _extractionDAO.canWrite(user, src);
157            case RIGHT_ACCESS:
158                return _extractionDAO.canAssignRights(user, src);
159            case READ_ACCESS:
160            default:
161                return _extractionDAO.canRead(user, src);
162        }
163    }
164    
165    private boolean _isIgnoredSource(TraversableSource source)
166    {
167        return !source.isCollection() && !source.getName().endsWith(".xml");
168    }
169    
170    private Map<String, Object> _extraction2JsonObject(Extraction extraction, TraversableSource file, TraversableSource root)
171    {
172        Map<String, Object> jsonObject = _fileHelper.getFileProperties(file, root);
173        jsonObject.putAll(_extractionDAO.getExtractionProperties(extraction, root, file, true));
174        return jsonObject;
175    }
176    
177    private Map<String, Object> _extractionContainer2JsonObject(TraversableSource folder, TraversableSource root)
178    {
179        return _extractionDAO.getExtractionContainerProperties(root, folder, true);
180    }
181    
182    /**
183     * Add a new folder
184     * @param parentRelPath the relative parent file's path from parameters files root directory
185     * @param name The name of folder to create
186     * @return a map containing the name of the created folder, its path and the path of its parent
187     * @throws IOException If an error occurred while creating folder
188     */
189    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
190    public Map<String, Object> addFolder(String parentRelPath, String name) throws IOException 
191    {
192        String nonNullParentRelPath = StringUtils.defaultString(parentRelPath);
193        
194        FileSource rootDir = (FileSource) _srcResolver.resolveURI(ExtractionConstants.DEFINITIONS_DIR);
195        if (!rootDir.exists())
196        {
197            rootDir.getFile().mkdirs();
198        }
199        
200        String parentURI = ExtractionConstants.DEFINITIONS_DIR + (StringUtils.isNotEmpty(nonNullParentRelPath) ? "/" + nonNullParentRelPath : "");
201        FileSource parentFolder = (FileSource) _srcResolver.resolveURI(parentURI);
202        
203        if (!_extractionDAO.canWrite(_currentUserProvider.getUser(), parentFolder))
204        {
205            throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to access extraction feature without convenient right");
206        }
207        
208        Map<String, Object> result = _fileHelper.addFolder(parentURI, name, true);
209        
210        if (result.containsKey("uri"))
211        {
212            String folderUri = (String) result.get("uri");
213            // Get only the part after the root folder to get the relative path 
214            String path = StringUtils.substringAfter(folderUri, rootDir.getURI());
215            result.put("path", ExtractionDAO.trimLastFileSeparator(path));
216            result.put("parentPath", ExtractionDAO.trimLastFileSeparator(nonNullParentRelPath));
217        }
218
219        return result;
220    }
221    
222    /**
223    * Remove a folder or a file
224    * @param relPath the relative file's path from parameters files root directory
225    * @return the result map
226    * @throws IOException If an error occurs while removing the folder
227    */
228    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
229    public Map<String, Object> deleteFile(String relPath) throws IOException
230    {
231        String fileUri = ExtractionConstants.DEFINITIONS_DIR + (relPath.length() > 0 ? "/" + relPath : "");
232        FileSource folderToDelete = (FileSource) _srcResolver.resolveURI(fileUri);
233        
234        if (!_extractionDAO.canDelete(_currentUserProvider.getUser(), folderToDelete))
235        {
236            throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to access extraction feature without convenient right");
237        }
238        
239        String context = ExtractionAccessController.ROOT_CONTEXT + "/" + relPath;
240        _extractionDAO.deleteRightsRecursively(context, folderToDelete);
241        return _fileHelper.deleteFile(fileUri);
242    }
243    
244    /**
245    * Rename a file or a folder 
246    * @param relPath the relative file's path from parameters files root directory
247    * @param name the new name of the file/folder
248    * @return the result map
249    * @throws IOException if an error occurs while renaming the file/folder
250    */
251    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION)
252    public Map<String, Object> renameFile(String relPath, String name) throws IOException 
253    {
254        String fileUri = ExtractionConstants.DEFINITIONS_DIR + relPath;
255        FileSource file = (FileSource) _srcResolver.resolveURI(fileUri);
256        
257        if (!_extractionDAO.canRename(_currentUserProvider.getUser(), file))
258        {
259            throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to access extraction feature without convenient right");
260        }
261        
262        String relativeParentPath = Strings.CS.removeEnd(relPath, file.getName());
263        String relativeNewFilePath = relativeParentPath + name;
264        return _extractionDAO.moveOrRenameExtractionDefinitionFile(relPath, relativeNewFilePath);
265    }
266}