package net.gepafin.tendermanagement.service.impl; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.*; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.dao.S3PathConfig; import net.gepafin.tendermanagement.entities.DocumentEntity; import net.gepafin.tendermanagement.entities.UserCompanyDelegationEntity; import net.gepafin.tendermanagement.enums.DocOtherSourceTypeEnum; import net.gepafin.tendermanagement.model.response.UploadFileOnAmazonS3Response; import net.gepafin.tendermanagement.service.AmazonS3Service; import net.gepafin.tendermanagement.util.Utils; import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException; import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Service public class AmazonS3ServiceImpl implements AmazonS3Service { private final Logger log = LoggerFactory.getLogger(AmazonS3ServiceImpl.class); @Autowired private AmazonS3 amazonS3; @Autowired private Environment environment; @Value("${aws.s3.bucket.name}") private String bucketName; @Value("${aws.s3.url}") private String s3Url; @Autowired private S3PathConfig s3ConfigBean; @Autowired private AmazonS3Client s3Client; @Value("${aws.s3.region}") private String region; @Autowired S3ReUploadMigrationService s3ReUploadMigrationService; private String upload(String fileName, String s3Folder, MultipartFile file) throws IOException { String path = s3Folder +"/"+fileName; InputStream inputStream = file.getInputStream(); ObjectMetadata objectMetadata = new ObjectMetadata(); Map metadata = new HashMap<>(); metadata.put("Content-Type", file.getContentType()); metadata.put("Content-Length", String.valueOf(file.getSize())); Optional> optionalMetaData = Optional.of(metadata); optionalMetaData.ifPresent(map -> { if (!map.isEmpty()) { map.forEach(objectMetadata::addUserMetadata); } }); if(Boolean.FALSE.equals(isTestProfileActivated())) { amazonS3.putObject(bucketName, path, inputStream, objectMetadata); } //getting actual encoded s3 file path URL amazonS3Url = amazonS3.getUrl(bucketName, path); String fileUrl = amazonS3Url.toString(); log.info("File '{}' uploaded successfully to Amazon S3 with URL: {}", fileName, fileUrl); return fileUrl; } @Override public Boolean delete(String s3Folder, String filePath) { String fileName = Utils.extractFileName(filePath); String path = s3Folder +"/"+fileName; final DeleteObjectRequest deleteObjectRequest = new DeleteObjectRequest(bucketName, path); if(Boolean.FALSE.equals(isTestProfileActivated())) { amazonS3.deleteObject(deleteObjectRequest); } log.info("File '{}' deleted successfully from Amazon S3", fileName); return true; } public Boolean isTestProfileActivated() { String[] activeProfiles = environment.getActiveProfiles(); return Arrays.stream(activeProfiles).anyMatch("test"::equals); } @Override public InputStream getFile(String s3Folder, String filePath) { try { String fileName = Utils.extractFileName(filePath); // Decode the file name to handle special characters like '+' correctly String decodedFileName = URLDecoder.decode(fileName, StandardCharsets.UTF_8.toString()); String path = s3Folder + "/" + decodedFileName; GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, path); S3Object s3Object = amazonS3.getObject(getObjectRequest); log.info("File fetched successfully from Amazon S3: {}", fileName); return s3Object.getObjectContent(); } catch (Exception e) { log.error("Error occurred while getting file from Amazon S3: {}", e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.GET_ERROR_S3)); } } @Override public UploadFileOnAmazonS3Response uploadFileOnAmazonS3(String s3Folder, MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); String originalFileName = org.springframework.util.StringUtils.cleanPath(file.getOriginalFilename()); String firstNameContain = originalFileName.substring(0, originalFileName.lastIndexOf('.')); firstNameContain = Utils.replaceSpacesWithUnderscores(firstNameContain); firstNameContain += "_" + Utils.randomKey(7); String fileName = (firstNameContain + "." + extension); try { String filepath = upload(fileName, s3Folder, file); return UploadFileOnAmazonS3Response.builder().fileName(originalFileName).filePath(filepath).build(); } catch (Exception e) { log.error("Error occurred while uploading file from Amazon S3: {}", e); throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.UPLOAD_ERROR_S3)); } } public String decodeS3Key(String key) { return URLDecoder.decode(key, StandardCharsets.UTF_8); } @Override public UploadFileOnAmazonS3Response moveFile(String fileName, String oldPath, String newPath) { try { log.info("Moving file from {} to {} in bucket {}", oldPath, newPath, bucketName); UploadFileOnAmazonS3Response response = copyFile(fileName, oldPath, newPath); s3Client.deleteObject(bucketName, decodeS3Key(cleanOldPath(oldPath))); log.info("Original file deleted successfully: {}", oldPath); return response; } catch (AmazonServiceException e) { log.error("AWS service error while moving file: {}", e.getErrorMessage(), e); throw e; } catch (SdkClientException e) { log.error("SDK client error while moving file: {}", e.getMessage(), e); throw e; } catch (Exception e) { log.error("Unexpected error while moving file: {}", e.getMessage(), e); throw e; } } public String cleanNewPath(String oldPath, String newPath) { return newPath + "/" + oldPath.substring(oldPath.lastIndexOf("/") + 1); } public String cleanOldPath(String oldPath) { return oldPath.replace(s3Url, ""); } @Override public UploadFileOnAmazonS3Response copyFile(String fileName, String oldS3Path, String newS3Path){ try { log.info("Copying file from {} to {} in bucket {}", oldS3Path, newS3Path, bucketName); oldS3Path = decodeS3Key(cleanOldPath(oldS3Path)); newS3Path = cleanNewPath(oldS3Path, newS3Path); log.info("Copying file from {} to {} in bucket {}", oldS3Path, newS3Path, bucketName); CopyObjectRequest copyRequest = new CopyObjectRequest(bucketName, oldS3Path, bucketName, newS3Path); s3Client.copyObject(copyRequest); log.info("File copied successfully from {} to {}", oldS3Path, newS3Path); URL amazonS3Url = amazonS3.getUrl(bucketName, newS3Path); String fileUrl = amazonS3Url.toString(); return UploadFileOnAmazonS3Response.builder().fileName(fileName).filePath(fileUrl).build(); } catch (AmazonServiceException e) { log.error("AWS service error while copying file: {}", e.getErrorMessage(), e); throw e; } catch (SdkClientException e) { log.error("SDK client error while copying file: {}", e.getMessage(), e); throw e; } catch (Exception e) { log.error("Unexpected error while copying file: {}", e.getMessage(), e); throw e; } } }