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}