1111
1212import java .io .IOException ;
1313import java .io .InputStream ;
14+ import java .net .URI ;
1415import java .security .DigestInputStream ;
1516import java .security .MessageDigest ;
1617import java .security .NoSuchAlgorithmException ;
4041import org .dspace .storage .bitstore .service .BitstreamStorageService ;
4142import org .dspace .util .FunctionalUtils ;
4243import org .springframework .beans .factory .annotation .Autowired ;
44+
45+ import jakarta .annotation .Nullable ;
46+ import jakarta .validation .constraints .NotBlank ;
47+ import jakarta .validation .constraints .NotNull ;
4348import software .amazon .awssdk .auth .credentials .AwsBasicCredentials ;
4449import software .amazon .awssdk .auth .credentials .AwsCredentialsProvider ;
4550import software .amazon .awssdk .auth .credentials .StaticCredentialsProvider ;
@@ -95,6 +100,13 @@ public class S3BitStoreService extends BaseBitStoreService {
95100
96101 private String awsAccessKey ;
97102 private String awsSecretKey ;
103+
104+ /**
105+ * Custom S3 endpoint URI which will override the Amazon defaults
106+ * if present.
107+ */
108+ private String endpoint = null ;
109+
98110 private String awsRegionName ;
99111 private boolean useRelativePath ;
100112 private double targetThroughputGbps = 10.0 ;
@@ -123,6 +135,7 @@ public class S3BitStoreService extends BaseBitStoreService {
123135 /**
124136 * Utility method for generate AmazonS3 builder
125137 *
138+ * @param endpoint optional custom s3 endpoint
126139 * @param regions wanted regions in client
127140 * @param awsCredentials credentials of the client
128141 * @param targetThroughput target throughput in Gbps
@@ -131,8 +144,9 @@ public class S3BitStoreService extends BaseBitStoreService {
131144 * @return builder with the specified parameters
132145 */
133146 protected static Supplier <S3AsyncClient > amazonClientBuilderBy (
134- Region region ,
135- AwsCredentialsProvider credentialsProvider ,
147+ @ Nullable String endpoint ,
148+ @ NotNull Region region ,
149+ @ NotNull AwsCredentialsProvider credentialsProvider ,
136150 double targetThroughput ,
137151 long minPartSize ,
138152 Integer maxConcurrency
@@ -152,6 +166,11 @@ protected static Supplier<S3AsyncClient> amazonClientBuilderBy(
152166 crtBuilder .maxConcurrency (maxConcurrency );
153167 }
154168
169+ if (StringUtils .isNotBlank (endpoint )) {
170+ crtBuilder .endpointOverride (URI .create (endpoint ));
171+ crtBuilder .forcePathStyle (true );
172+ }
173+
155174 return crtBuilder .targetThroughputInGbps (targetThroughput ).minimumPartSizeInBytes (minPartSize ).build ();
156175 };
157176 }
@@ -181,10 +200,13 @@ public boolean isEnabled() {
181200 */
182201 @ Override
183202 public void init () throws IOException {
203+
184204 if (this .isInitialized () || !this .isEnabled ()) {
185205 return ;
186206 }
187207
208+
209+
188210 try {
189211 if (StringUtils .isNotBlank (getAwsAccessKey ()) && StringUtils .isNotBlank (getAwsSecretKey ())) {
190212 log .warn ("Use local defined S3 credentials" );
@@ -202,6 +224,7 @@ public void init() throws IOException {
202224 s3AsyncClient = FunctionalUtils .getDefaultOrBuild (
203225 this .s3AsyncClient ,
204226 amazonClientBuilderBy (
227+ endpoint ,
205228 region ,
206229 StaticCredentialsProvider .create (AwsBasicCredentials .create (getAwsAccessKey (),
207230 getAwsSecretKey ())), targetThroughputGbps , minPartSizeBytes , maxConcurrency )
@@ -211,7 +234,7 @@ public void init() throws IOException {
211234 log .info ("Using a IAM role or aws environment credentials" );
212235 s3AsyncClient = FunctionalUtils .getDefaultOrBuild (
213236 this .s3AsyncClient ,
214- amazonClientBuilderBy (null , null , targetThroughputGbps , minPartSizeBytes , maxConcurrency ));
237+ amazonClientBuilderBy (endpoint , null , null , targetThroughputGbps , minPartSizeBytes , maxConcurrency ));
215238 }
216239
217240 // bucket name
@@ -301,6 +324,9 @@ public InputStream get(Bitstream bitstream) throws IOException {
301324 */
302325 @ Override
303326 public void put (Bitstream bitstream , InputStream in ) throws IOException {
327+ log .error (getEndpoint ());
328+ log .error (getAwsAccessKey ());
329+ log .error (getAwsSecretKey ());
304330 String key = getFullKey (bitstream .getInternalId ());
305331
306332 try (DigestInputStream dis = new DigestInputStream (in , MessageDigest .getInstance (CSA ))) {
@@ -478,6 +504,15 @@ public void setAwsSecretKey(String awsSecretKey) {
478504 this .awsSecretKey = awsSecretKey ;
479505 }
480506
507+ public String getEndpoint () {
508+ return endpoint ;
509+ }
510+
511+ @ Autowired
512+ public void setEndpoint (String endpoint ) {
513+ this .endpoint = endpoint ;
514+ }
515+
481516 public String getAwsRegionName () {
482517 return awsRegionName ;
483518 }
@@ -596,58 +631,6 @@ public static void main(String[] args) throws Exception {
596631 store .bucketName = DEFAULT_BUCKET_PREFIX + hostname + ".s3test" ;
597632 store .s3AsyncClient .createBucket (r -> r .bucket (store .bucketName )).join ();
598633
599- /* Broken in DSpace 6 TODO Refactor
600- // time everything, todo, switch to caliper
601- long start = Instant.now().toEpochMilli();
602- // Case 1: store a file
603- String id = store.generateId();
604- System.out.print("put() file " + assetFile + " under ID " + id + ": ");
605- FileInputStream fis = new FileInputStream(assetFile);
606- //TODO create bitstream for assetfile...
607- Map attrs = store.put(fis, id);
608- long now = Instant.now().toEpochMilli();
609- System.out.println((now - start) + " msecs");
610- start = now;
611- // examine the metadata returned
612- Iterator iter = attrs.keySet().iterator();
613- System.out.println("Metadata after put():");
614- while (iter.hasNext())
615- {
616- String key = (String)iter.next();
617- System.out.println( key + ": " + (String)attrs.get(key) );
618- }
619- // Case 2: get metadata and compare
620- System.out.print("about() file with ID " + id + ": ");
621- Map attrs2 = store.about(id, attrs);
622- now = Instant.now().toEpochMilli();
623- System.out.println((now - start) + " msecs");
624- start = now;
625- iter = attrs2.keySet().iterator();
626- System.out.println("Metadata after about():");
627- while (iter.hasNext())
628- {
629- String key = (String)iter.next();
630- System.out.println( key + ": " + (String)attrs.get(key) );
631- }
632- // Case 3: retrieve asset and compare bits
633- System.out.print("get() file with ID " + id + ": ");
634- java.io.FileOutputStream fos = new java.io.FileOutputStream(assetFile+".echo");
635- InputStream in = store.get(id);
636- Utils.bufferedCopy(in, fos);
637- fos.close();
638- in.close();
639- now = Instant.now().toEpochMilli();
640- System.out.println((now - start) + " msecs");
641- start = now;
642- // Case 4: remove asset
643- System.out.print("remove() file with ID: " + id + ": ");
644- store.remove(id);
645- now = Instant.now().toEpochMilli();
646- System.out.println((now - start) + " msecs");
647- System.out.flush();
648- // should get nothing back now - will throw exception
649- store.get(id);
650- */
651634 }
652635
653636 /**
0 commit comments