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