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}