View Javadoc
1   /*
2    * Copyright 2020 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.data.minio;
18  
19  import io.minio.BucketExistsArgs;
20  import io.minio.GetBucketVersioningArgs;
21  import io.minio.GetPresignedObjectUrlArgs;
22  import io.minio.ListObjectsArgs;
23  import io.minio.MakeBucketArgs;
24  import io.minio.MinioClient;
25  import io.minio.ObjectWriteResponse;
26  import io.minio.PutObjectArgs;
27  import io.minio.RemoveObjectArgs;
28  import io.minio.RemoveObjectsArgs;
29  import io.minio.Result;
30  import io.minio.SetBucketVersioningArgs;
31  import io.minio.StatObjectArgs;
32  import io.minio.StatObjectResponse;
33  import io.minio.http.Method;
34  import io.minio.messages.DeleteError;
35  import io.minio.messages.DeleteObject;
36  import io.minio.messages.Item;
37  import io.minio.messages.VersioningConfiguration;
38  import io.minio.messages.VersioningConfiguration.Status;
39  import java.io.IOException;
40  import java.io.InputStream;
41  import java.time.Duration;
42  import java.time.temporal.ChronoUnit;
43  import java.util.ArrayList;
44  import java.util.Collection;
45  import java.util.Collections;
46  import java.util.List;
47  import java.util.Optional;
48  import java.util.stream.Collectors;
49  import lombok.Getter;
50  import org.bremersee.exception.ServiceException;
51  import org.bremersee.web.multipart.FileAwareMultipartFile;
52  import org.springframework.http.MediaType;
53  import org.springframework.util.StringUtils;
54  import org.springframework.web.multipart.MultipartFile;
55  
56  /**
57   * The minio repository implementation.
58   *
59   * @author Christian Bremer
60   */
61  public class MinioRepositoryImpl implements MinioRepository {
62  
63    private final MinioOperations minio;
64  
65    @Getter
66    private final String region;
67  
68    @Getter
69    private final String bucket;
70  
71    private final boolean enableVersioning;
72  
73    private final Duration presignedObjectUrlDuration;
74  
75    /**
76     * Instantiates a new minio repository.
77     *
78     * @param minioOperations the minio operations
79     * @param region the region
80     * @param bucket the bucket
81     * @param enableVersioning the enable versioning
82     * @param create the create
83     * @param presignedObjectUrlDuration the presigned object url duration
84     */
85    public MinioRepositoryImpl(
86        MinioOperations minioOperations,
87        String region,
88        String bucket,
89        boolean enableVersioning,
90        boolean create,
91        Duration presignedObjectUrlDuration) {
92  
93      this.minio = minioOperations;
94      this.region = region;
95      this.bucket = bucket;
96      this.enableVersioning = enableVersioning;
97      this.presignedObjectUrlDuration = validateDuration(presignedObjectUrlDuration);
98      if (create) {
99        createBucket();
100     }
101   }
102 
103   /**
104    * Instantiates a new minio repository.
105    *
106    * @param minioClient the minio client
107    * @param region the region
108    * @param bucket the bucket
109    * @param enableVersioning the enable versioning
110    * @param create the create
111    * @param presignedObjectUrlDuration the presigned object url duration
112    */
113   public MinioRepositoryImpl(
114       MinioClient minioClient,
115       String region,
116       String bucket,
117       boolean enableVersioning,
118       boolean create,
119       Duration presignedObjectUrlDuration) {
120 
121     this(
122         new MinioTemplate(minioClient),
123         region,
124         bucket,
125         enableVersioning,
126         create,
127         presignedObjectUrlDuration);
128   }
129 
130   private static Duration validateDuration(Duration presignedObjectUrlDuration) {
131     return Optional.ofNullable(presignedObjectUrlDuration)
132         .filter(duration -> duration.toMillis() > 1000L * 30L
133             && duration.toMillis() < 1000L * 60L * 60L * 24L * 7L)
134         .orElseGet(() -> Duration.of(1L, ChronoUnit.DAYS));
135   }
136 
137   private void createBucket() {
138     if (!minio.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {
139       minio.makeBucket(MakeBucketArgs.builder()
140           .region(region)
141           .bucket(bucket)
142           .build());
143     }
144     VersioningConfiguration versioningConfiguration = minio.getBucketVersioning(GetBucketVersioningArgs.builder()
145         .region(region)
146         .bucket(bucket)
147         .build());
148     switch (versioningConfiguration.status()) {
149       case ENABLED:
150         if (!enableVersioning) {
151           minio.setBucketVersioning(SetBucketVersioningArgs.builder()
152               .region(region)
153               .bucket(bucket)
154               .config(new VersioningConfiguration(Status.SUSPENDED, false))
155               .build());
156         }
157         break;
158       case SUSPENDED:
159       case OFF:
160         if (enableVersioning) {
161           minio.setBucketVersioning(SetBucketVersioningArgs.builder()
162               .region(region)
163               .bucket(bucket)
164               .config(new VersioningConfiguration(Status.ENABLED, false))
165               .build());
166         }
167         break;
168       default:
169     }
170   }
171 
172   @Override
173   public MinioOperations getMinioOperations() {
174     return minio;
175   }
176 
177   @Override
178   public boolean isVersioningEnabled() {
179     return enableVersioning;
180   }
181 
182   @Override
183   public Optional<ObjectWriteResponse> save(
184       MinioObjectId id,
185       MultipartFile multipartFile,
186       DeleteMode deleteMode) {
187 
188     return Optional.ofNullable(multipartFile)
189         .filter(file -> !file.isEmpty())
190         .map(file -> {
191           try (InputStream in = file.getInputStream()) {
192             ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
193                 .contentType(StringUtils.hasText(file.getContentType())
194                     ? file.getContentType()
195                     : MediaType.APPLICATION_OCTET_STREAM_VALUE)
196                 .stream(in, file.getSize(), -1)
197                 .region(region)
198                 .bucket(bucket)
199                 .object(id.getName())
200                 .build());
201             if (DeleteMode.ON_SUCCESS == deleteMode) {
202               FileAwareMultipartFile.delete(file);
203             }
204             return response;
205 
206           } catch (IOException e) {
207             throw ServiceException.internalServerError("Put object failed.", e);
208 
209           } finally {
210             if (DeleteMode.ALWAYS == deleteMode) {
211               FileAwareMultipartFile.delete(file);
212             }
213           }
214         });
215   }
216 
217   @Override
218   public boolean exists(MinioObjectId id) {
219     return minio.objectExists(StatObjectArgs.builder()
220         .region(region)
221         .bucket(bucket)
222         .object(id.getName())
223         .versionId(id.getVersionId())
224         .build());
225   }
226 
227   @Override
228   public Optional<MinioMultipartFile> findOne(MinioObjectId id) {
229     try {
230       StatObjectResponse objectStat = minio.statObject(StatObjectArgs.builder()
231           .region(region)
232           .bucket(bucket)
233           .object(id.getName())
234           .versionId(id.getVersionId())
235           .build());
236       return Optional.of(new MinioMultipartFileImpl(minio, region, objectStat));
237 
238     } catch (MinioException e) {
239       if (404 == e.status()) {
240         return Optional.empty();
241       }
242       throw e;
243     }
244   }
245 
246   @Override
247   public List<MinioMultipartFile> findAll(String prefix) {
248     Iterable<Result<Item>> results = minio.listObjects(ListObjectsArgs.builder()
249         .region(region)
250         .bucket(bucket)
251         .includeVersions(enableVersioning)
252         .recursive(true)
253         .prefix(prefix)
254         .build());
255     List<MinioMultipartFile> fileList = new ArrayList<>();
256     for (Result<Item> result : results) {
257       try {
258         Item item = result.get();
259         if (!item.isDir() && !item.isDeleteMarker()) {
260           fileList.add(new MinioMultipartFileImpl(
261               getMinioOperations(),
262               region,
263               bucket,
264               item));
265         }
266       } catch (Exception ignored) {
267         // ignored
268       }
269     }
270     return fileList;
271   }
272 
273   @Override
274   public void delete(MinioObjectId id) {
275     minio.removeObject(RemoveObjectArgs.builder()
276         .region(region)
277         .bucket(bucket)
278         .object(id.getName())
279         .versionId(id.getVersionId())
280         .build());
281   }
282 
283   @Override
284   public List<DeleteError> deleteAll(Collection<MinioObjectId> ids) {
285 
286     if (ids == null || ids.isEmpty()) {
287       return Collections.emptyList();
288     }
289     Iterable<Result<DeleteError>> errors = minio.removeObjects(RemoveObjectsArgs.builder()
290         .region(region)
291         .bucket(bucket)
292         .objects(ids.stream()
293             .map(id -> new DeleteObject(id.getName(), id.getVersionId()))
294             .collect(Collectors.toSet()))
295         .build());
296     List<DeleteError> errorList = new ArrayList<>();
297     for (Result<DeleteError> error : errors) {
298       try {
299         errorList.add(error.get());
300       } catch (Exception ignored) {
301         // ignored
302       }
303     }
304     return errorList;
305   }
306 
307   @Override
308   public String getPresignedObjectUrl(MinioObjectId id, Method method, Duration duration) {
309     Duration expiry = duration != null ? validateDuration(duration) : presignedObjectUrlDuration;
310     return minio.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
311         .expiry((int) expiry.toSeconds())
312         .method(method)
313         .region(region)
314         .bucket(bucket)
315         .object(id.getName())
316         .versionId(id.getVersionId())
317         .build());
318   }
319 
320 }