4747import org .springframework .beans .factory .annotation .Autowired ;
4848import software .amazon .awssdk .auth .credentials .AwsBasicCredentials ;
4949import software .amazon .awssdk .auth .credentials .AwsCredentialsProvider ;
50+ import software .amazon .awssdk .auth .credentials .AwsSessionCredentials ;
5051import software .amazon .awssdk .auth .credentials .StaticCredentialsProvider ;
5152import software .amazon .awssdk .awscore .exception .AwsServiceException ;
5253import software .amazon .awssdk .core .async .AsyncRequestBody ;
7172 */
7273
7374public class S3BitStoreService extends BaseBitStoreService {
75+ protected static final String DEFAULT_BUCKET_PREFIX = "dspace-asset-" ;
7476 public static final long DEFAULT_EXPIRATION = Duration .ofMinutes (2 ).toSeconds ();
77+ // Prefix indicating a registered bitstream
78+ protected final String REGISTERED_FLAG = "-R" ;
79+ /**
80+ * log4j log
81+ */
82+ private static final Logger log = LogManager .getLogger (S3BitStoreService .class );
83+
7584 public static final String TEMP_PREFIX = "s3-virtual-path" ;
7685 public static final String TEMP_SUFFIX = "temp" ;
77- protected static final String DEFAULT_BUCKET_PREFIX = "dspace-asset-" ;
78- // These settings control the way an identifier is hashed into
79- // directory and file names
80- //
81- // With digitsPerLevel 2 and directoryLevels 3, an identifier
82- // like 12345678901234567890 turns into the relative name
83- // /12/34/56/12345678901234567890.
84- //
85- // You should not change these settings if you have data in the
86- // asset store, as the BitstreamStorageManager will be unable
87- // to find your existing data.
88- protected static final int digitsPerLevel = 2 ;
89- protected static final int directoryLevels = 3 ;
86+
9087 /**
9188 * Checksum algorithm
9289 */
9390 static final String CSA = "MD5" ;
94- /**
95- * log4j log
96- */
97- private static final Logger log = LogManager .getLogger (S3BitStoreService .class );
98- private static final ConfigurationService configurationService
99- = DSpaceServicesFactory .getInstance ().getConfigurationService ();
100- // Prefix indicating a registered bitstream
101- protected final String REGISTERED_FLAG = "-R" ;
91+
10292 private boolean enabled = false ;
10393 /**
10494 * Override AWS endpoint if not null
@@ -113,8 +103,7 @@ public class S3BitStoreService extends BaseBitStoreService {
113103 private long minPartSizeBytes = 8 * 1024 * 1024L ;
114104 private ChecksumAlgorithm s3ChecksumAlgorithm = ChecksumAlgorithm .CRC32 ;
115105 private Integer maxConcurrency = null ;
116- private Integer maxConnections ;
117- private Integer connectionTimeout ;
106+
118107 /**
119108 * container for all the assets
120109 */
@@ -128,6 +117,9 @@ public class S3BitStoreService extends BaseBitStoreService {
128117 */
129118 private S3AsyncClient s3AsyncClient = null ;
130119
120+ private static final ConfigurationService configurationService
121+ = DSpaceServicesFactory .getInstance ().getConfigurationService ();
122+
131123 public S3BitStoreService () {
132124 }
133125
@@ -140,24 +132,46 @@ protected S3BitStoreService(S3AsyncClient s3AsyncClient) {
140132 this .s3AsyncClient = s3AsyncClient ;
141133 }
142134
135+ /**
136+ * Creates an AWS credentials provider that supports both basic credentials and session tokens.
137+ *
138+ * @param accessKey AWS access key
139+ * @param secretKey AWS secret key
140+ * @param sessionToken AWS session token (optional)
141+ * @return StaticCredentialsProvider with appropriate credentials
142+ */
143+ protected static StaticCredentialsProvider createCredentialsProvider (
144+ String accessKey , String secretKey , String sessionToken
145+ ) {
146+ if (StringUtils .isNotBlank (sessionToken )) {
147+ return StaticCredentialsProvider .create (
148+ AwsSessionCredentials .create (accessKey , secretKey , sessionToken )
149+ );
150+ } else {
151+ return StaticCredentialsProvider .create (
152+ AwsBasicCredentials .create (accessKey , secretKey )
153+ );
154+ }
155+ }
156+
143157 /**
144158 * Utility method for generate AmazonS3 builder
145159 *
146- * @param regions wanted regions in client
147- * @param awsCredentials credentials of the client
148- * @param endpoint custom AWS endpoint
160+ * @param region wanted regions in client
161+ * @param credentialsProvider credentials of the client
162+ * @param endpoint custom AWS endpoint
149163 * @param targetThroughput target throughput in Gbps
150- * @param minPartSize minimum part size in bytes
151- * @param maxConcurrency maximum number of concurrent requests
164+ * @param minPartSize minimum part size in bytes
165+ * @param maxConcurrency maximum number of concurrent requests
152166 * @return builder with the specified parameters
153167 */
154168 protected static Supplier <S3AsyncClient > amazonClientBuilderBy (
155- Region region ,
156- AwsCredentialsProvider credentialsProvider ,
157- String endpoint ,
158- double targetThroughput ,
159- long minPartSize ,
160- Integer maxConcurrency
169+ Region region ,
170+ AwsCredentialsProvider credentialsProvider ,
171+ String endpoint ,
172+ double targetThroughput ,
173+ long minPartSize ,
174+ Integer maxConcurrency
161175 ) {
162176 return () -> {
163177 S3CrtAsyncClientBuilder crtBuilder = S3AsyncClient .crtBuilder ();
@@ -242,10 +256,6 @@ public boolean isEnabled() {
242256 return this .enabled ;
243257 }
244258
245- public void setEnabled (boolean enabled ) {
246- this .enabled = enabled ;
247- }
248-
249259 /**
250260 * Initialize the asset store
251261 * S3 Requires:
@@ -272,24 +282,25 @@ public void init() throws IOException {
272282 }
273283 }
274284
275- StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider
276- .create (AwsBasicCredentials .create (getAwsAccessKey (), getAwsSecretKey ()));
285+ StaticCredentialsProvider credentialsProvider = createCredentialsProvider (
286+ getAwsAccessKey (), getAwsSecretKey (), getAwsSessionToken ());
287+
277288 // init client
278289 s3AsyncClient = FunctionalUtils .getDefaultOrBuild (
279- this .s3AsyncClient ,
280- amazonClientBuilderBy (
281- region ,
282- credentialsProvider , endpoint , targetThroughputGbps ,
283- minPartSizeBytes , maxConcurrency )
284- );
290+ this .s3AsyncClient ,
291+ amazonClientBuilderBy (
292+ region ,
293+ credentialsProvider , endpoint , targetThroughputGbps ,
294+ minPartSizeBytes , maxConcurrency )
295+ );
285296
286297 log .warn ("S3 Region set to: " + region .id ());
287298 } else {
288299 log .info ("Using a IAM role or aws environment credentials" );
289300 s3AsyncClient = FunctionalUtils .getDefaultOrBuild (
290- this .s3AsyncClient ,
291- amazonClientBuilderBy (null , null , endpoint , targetThroughputGbps ,
292- minPartSizeBytes , maxConcurrency ));
301+ this .s3AsyncClient ,
302+ amazonClientBuilderBy (null , null , endpoint , targetThroughputGbps ,
303+ minPartSizeBytes , maxConcurrency ));
293304 }
294305
295306 // bucket name
@@ -359,7 +370,7 @@ public InputStream get(Bitstream bitstream) throws IOException {
359370
360371 try {
361372 return s3AsyncClient .getObject (r -> r .bucket (bucketName ).key (objectKey ),
362- AsyncResponseTransformer .toBlockingInputStream ()).join ();
373+ AsyncResponseTransformer .toBlockingInputStream ()).join ();
363374 } catch (CompletionException e ) {
364375 throw new IOException (e .getCause ());
365376 }
@@ -384,11 +395,11 @@ public void put(Bitstream bitstream, InputStream in) throws IOException {
384395 try (DigestInputStream dis = new DigestInputStream (in , MessageDigest .getInstance (CSA ))) {
385396 AsyncRequestBody body = AsyncRequestBody .fromInputStream (dis , null , executor );
386397
387- s3AsyncClient .putObject (b -> b .bucket (bucketName ).key (key ).checksumAlgorithm (s3ChecksumAlgorithm ),
388- body ).join ();
398+ s3AsyncClient .putObject (b -> b .bucket (bucketName ).key (key ).checksumAlgorithm (s3ChecksumAlgorithm ),
399+ body ).join ();
389400
390401 bitstream .setSizeBytes (s3AsyncClient .headObject (r -> r .bucket (bucketName ).key (key ))
391- .join ().contentLength ());
402+ .join ().contentLength ());
392403
393404 // we cannot use the S3 ETAG here as it could be not a MD5 in case of multipart upload (large files) or if
394405 // the bucket is encrypted
@@ -476,7 +487,7 @@ public void remove(Bitstream bitstream) throws IOException {
476487 String key = getFullKey (bitstream .getInternalId ());
477488 try {
478489 s3AsyncClient .deleteObject (r -> r .bucket (bucketName ).key (key )).join ();
479- } catch (CompletionException e ) {
490+ } catch (CompletionException e ) {
480491 log .error ("remove(" + key + ")" , e .getCause ());
481492 throw new IOException (e .getCause ());
482493 }
@@ -503,7 +514,7 @@ public String getFullKey(String id) {
503514
504515 if (log .isDebugEnabled ()) {
505516 log .debug ("S3 filepath for " + id + " is "
506- + bufFilename .toString ());
517+ + bufFilename .toString ());
507518 }
508519
509520 return bufFilename .toString ();
@@ -513,15 +524,15 @@ public String getFullKey(String id) {
513524 * there are 2 cases:
514525 * - conventional bitstream, conventional storage
515526 * - registered bitstream, conventional storage
516- * conventional bitstream: dspace ingested, dspace random name/path
517- * registered bitstream: registered to dspace, any name/path
527+ * conventional bitstream: dspace ingested, dspace random name/path
528+ * registered bitstream: registered to dspace, any name/path
518529 *
519530 * @param sInternalId
520531 * @return Computed Relative path
521532 */
522533 public String getRelativePath (String sInternalId ) {
523534 BitstreamStorageService bitstreamStorageService = StorageServiceFactory .getInstance ()
524- .getBitstreamStorageService ();
535+ .getBitstreamStorageService ();
525536
526537 String sIntermediatePath = StringUtils .EMPTY ;
527538 if (bitstreamStorageService .isRegisteredBitstream (sInternalId )) {
@@ -534,6 +545,10 @@ public String getRelativePath(String sInternalId) {
534545 return sIntermediatePath + sInternalId ;
535546 }
536547
548+ public void setEnabled (boolean enabled ) {
549+ this .enabled = enabled ;
550+ }
551+
537552 public String getAwsAccessKey () {
538553 return awsAccessKey ;
539554 }
@@ -625,22 +640,6 @@ public void setEndpoint(String endpoint) {
625640 this .endpoint = endpoint ;
626641 }
627642
628- public Integer getMaxConnections () {
629- return maxConnections ;
630- }
631-
632- public void setMaxConnections (Integer maxConnections ) {
633- this .maxConnections = maxConnections ;
634- }
635-
636- public Integer getConnectionTimeout () {
637- return connectionTimeout ;
638- }
639-
640- public void setConnectionTimeout (Integer connectionTimeout ) {
641- this .connectionTimeout = connectionTimeout ;
642- }
643-
644643 public String getAwsSessionToken () {
645644 return awsSessionToken ;
646645 }
0 commit comments