| 48 | import java.util.Map; |
| 49 | |
| 50 | @Service |
| 51 | public class S3Service { |
| 52 | private static final Logger log = LoggerFactory.getLogger(S3Service.class); |
| 53 | |
| 54 | private final S3Client s3Client; |
| 55 | private final S3Presigner s3Presigner; |
| 56 | private final String bucketName; |
| 57 | private final String orgId; |
| 58 | |
| 59 | public S3Service( |
| 60 | @Value("${aws.s3.bucket-name}") String bucketName, |
| 61 | @Value("${aws.s3.region:eu-north-1}") String region, |
| 62 | @Value("${salesforce.org-id}") String orgId) { |
| 63 | this.bucketName = bucketName; |
| 64 | this.orgId = orgId; |
| 65 | |
| 66 | // Configure S3Client with timeouts to prevent hanging forever on slow/unreliable networks |
| 67 | this.s3Client = S3Client.builder() |
| 68 | .region(Region.of(region)) |
| 69 | .credentialsProvider(DefaultCredentialsProvider.create()) |
| 70 | .overrideConfiguration(ClientOverrideConfiguration.builder() |
| 71 | .apiCallTimeout(Duration.ofMinutes(5)) // Total timeout for API call (includes retries) |
| 72 | .apiCallAttemptTimeout(Duration.ofMinutes(3)) // Timeout per retry attempt |
| 73 | .retryPolicy(RetryPolicy.builder() |
| 74 | .numRetries(2) // Retry twice on failure |
| 75 | .build()) |
| 76 | .build()) |
| 77 | .build(); |
| 78 | |
| 79 | this.s3Presigner = S3Presigner.builder() |
| 80 | .region(Region.of(region)) |
| 81 | .credentialsProvider(DefaultCredentialsProvider.create()) |
| 82 | .build(); |
| 83 | |
| 84 | log.info("S3Service initialized with bucket: {} in region: {} (5min timeout, 2 retries)", bucketName, region); |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Upload a CSV file to S3 with partitioned path pattern: |
| 89 | * OrgId={x}/Period={yyyymm}/Year={y}/Month={m}/Date={d}/{ObjectName}/{file}.csv |
| 90 | * |
| 91 | * @param csvPath Path to the CSV file to upload |
| 92 | * @param objectName Salesforce object name (e.g., "Account") |
| 93 | * @param queryAll Whether this is a queryAll (deleted records) backup |
| 94 | * @return The S3 key where the file was uploaded |
| 95 | */ |
| 96 | public String uploadCsvToS3(Path csvPath, String objectName, boolean queryAll) throws IOException { |
| 97 | String period = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM")); |
| 98 | return uploadCsvToS3(csvPath, objectName, queryAll, period); |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Upload a CSV file to S3 with partitioned path pattern and explicit period: |
| 103 | * OrgId={x}/Period={period}/Year={y}/Month={m}/Date={d}/{ObjectName}/{file}.csv |
| 104 | * |
| 105 | * @param csvPath Path to the CSV file to upload |
| 106 | * @param objectName Salesforce object name (e.g., "Account") |
| 107 | * @param queryAll Whether this is a queryAll (deleted records) backup |
nothing calls this directly
no outgoing calls
no test coverage detected