MinioRepositoryImpl.java
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.data.minio;
import io.minio.BucketExistsArgs;
import io.minio.GetBucketVersioningArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.SetBucketVersioningArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import io.minio.messages.VersioningConfiguration;
import io.minio.messages.VersioningConfiguration.Status;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import org.bremersee.exception.ServiceException;
import org.bremersee.web.multipart.FileAwareMultipartFile;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* The minio repository implementation.
*
* @author Christian Bremer
*/
public class MinioRepositoryImpl implements MinioRepository {
private final MinioOperations minio;
@Getter
private final String region;
@Getter
private final String bucket;
private final boolean enableVersioning;
private final Duration presignedObjectUrlDuration;
/**
* Instantiates a new minio repository.
*
* @param minioOperations the minio operations
* @param region the region
* @param bucket the bucket
* @param enableVersioning the enable versioning
* @param create the create
* @param presignedObjectUrlDuration the presigned object url duration
*/
public MinioRepositoryImpl(
MinioOperations minioOperations,
String region,
String bucket,
boolean enableVersioning,
boolean create,
Duration presignedObjectUrlDuration) {
this.minio = minioOperations;
this.region = region;
this.bucket = bucket;
this.enableVersioning = enableVersioning;
this.presignedObjectUrlDuration = validateDuration(presignedObjectUrlDuration);
if (create) {
createBucket();
}
}
/**
* Instantiates a new minio repository.
*
* @param minioClient the minio client
* @param region the region
* @param bucket the bucket
* @param enableVersioning the enable versioning
* @param create the create
* @param presignedObjectUrlDuration the presigned object url duration
*/
public MinioRepositoryImpl(
MinioClient minioClient,
String region,
String bucket,
boolean enableVersioning,
boolean create,
Duration presignedObjectUrlDuration) {
this(
new MinioTemplate(minioClient),
region,
bucket,
enableVersioning,
create,
presignedObjectUrlDuration);
}
private static Duration validateDuration(Duration presignedObjectUrlDuration) {
return Optional.ofNullable(presignedObjectUrlDuration)
.filter(duration -> duration.toMillis() > 1000L * 30L
&& duration.toMillis() < 1000L * 60L * 60L * 24L * 7L)
.orElseGet(() -> Duration.of(1L, ChronoUnit.DAYS));
}
private void createBucket() {
if (!minio.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {
minio.makeBucket(MakeBucketArgs.builder()
.region(region)
.bucket(bucket)
.build());
}
VersioningConfiguration versioningConfiguration = minio.getBucketVersioning(GetBucketVersioningArgs.builder()
.region(region)
.bucket(bucket)
.build());
switch (versioningConfiguration.status()) {
case ENABLED:
if (!enableVersioning) {
minio.setBucketVersioning(SetBucketVersioningArgs.builder()
.region(region)
.bucket(bucket)
.config(new VersioningConfiguration(Status.SUSPENDED, false))
.build());
}
break;
case SUSPENDED:
case OFF:
if (enableVersioning) {
minio.setBucketVersioning(SetBucketVersioningArgs.builder()
.region(region)
.bucket(bucket)
.config(new VersioningConfiguration(Status.ENABLED, false))
.build());
}
break;
default:
}
}
@Override
public MinioOperations getMinioOperations() {
return minio;
}
@Override
public boolean isVersioningEnabled() {
return enableVersioning;
}
@Override
public Optional<ObjectWriteResponse> save(
MinioObjectId id,
MultipartFile multipartFile,
DeleteMode deleteMode) {
return Optional.ofNullable(multipartFile)
.filter(file -> !file.isEmpty())
.map(file -> {
try (InputStream in = file.getInputStream()) {
ObjectWriteResponse response = minio.putObject(PutObjectArgs.builder()
.contentType(StringUtils.hasText(file.getContentType())
? file.getContentType()
: MediaType.APPLICATION_OCTET_STREAM_VALUE)
.stream(in, file.getSize(), -1)
.region(region)
.bucket(bucket)
.object(id.getName())
.build());
if (DeleteMode.ON_SUCCESS == deleteMode) {
FileAwareMultipartFile.delete(file);
}
return response;
} catch (IOException e) {
throw ServiceException.internalServerError("Put object failed.", e);
} finally {
if (DeleteMode.ALWAYS == deleteMode) {
FileAwareMultipartFile.delete(file);
}
}
});
}
@Override
public boolean exists(MinioObjectId id) {
return minio.objectExists(StatObjectArgs.builder()
.region(region)
.bucket(bucket)
.object(id.getName())
.versionId(id.getVersionId())
.build());
}
@Override
public Optional<MinioMultipartFile> findOne(MinioObjectId id) {
try {
StatObjectResponse objectStat = minio.statObject(StatObjectArgs.builder()
.region(region)
.bucket(bucket)
.object(id.getName())
.versionId(id.getVersionId())
.build());
return Optional.of(new MinioMultipartFileImpl(minio, region, objectStat));
} catch (MinioException e) {
if (404 == e.status()) {
return Optional.empty();
}
throw e;
}
}
@Override
public List<MinioMultipartFile> findAll(String prefix) {
Iterable<Result<Item>> results = minio.listObjects(ListObjectsArgs.builder()
.region(region)
.bucket(bucket)
.includeVersions(enableVersioning)
.recursive(true)
.prefix(prefix)
.build());
List<MinioMultipartFile> fileList = new ArrayList<>();
for (Result<Item> result : results) {
try {
Item item = result.get();
if (!item.isDir() && !item.isDeleteMarker()) {
fileList.add(new MinioMultipartFileImpl(
getMinioOperations(),
region,
bucket,
item));
}
} catch (Exception ignored) {
// ignored
}
}
return fileList;
}
@Override
public void delete(MinioObjectId id) {
minio.removeObject(RemoveObjectArgs.builder()
.region(region)
.bucket(bucket)
.object(id.getName())
.versionId(id.getVersionId())
.build());
}
@Override
public List<DeleteError> deleteAll(Collection<MinioObjectId> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
Iterable<Result<DeleteError>> errors = minio.removeObjects(RemoveObjectsArgs.builder()
.region(region)
.bucket(bucket)
.objects(ids.stream()
.map(id -> new DeleteObject(id.getName(), id.getVersionId()))
.collect(Collectors.toSet()))
.build());
List<DeleteError> errorList = new ArrayList<>();
for (Result<DeleteError> error : errors) {
try {
errorList.add(error.get());
} catch (Exception ignored) {
// ignored
}
}
return errorList;
}
@Override
public String getPresignedObjectUrl(MinioObjectId id, Method method, Duration duration) {
Duration expiry = duration != null ? validateDuration(duration) : presignedObjectUrlDuration;
return minio.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.expiry((int) expiry.toSeconds())
.method(method)
.region(region)
.bucket(bucket)
.object(id.getName())
.versionId(id.getVersionId())
.build());
}
}