1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.transport.minio;
20
21 import java.io.InputStream;
22 import java.net.URI;
23 import java.net.URISyntaxException;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.StandardCopyOption;
27 import java.util.HashMap;
28 import java.util.Map;
29
30 import io.minio.GetObjectArgs;
31 import io.minio.MinioClient;
32 import io.minio.StatObjectArgs;
33 import io.minio.UploadObjectArgs;
34 import io.minio.credentials.Provider;
35 import io.minio.credentials.StaticProvider;
36 import io.minio.errors.ErrorResponseException;
37 import org.eclipse.aether.RepositorySystemSession;
38 import org.eclipse.aether.repository.AuthenticationContext;
39 import org.eclipse.aether.repository.RemoteRepository;
40 import org.eclipse.aether.spi.connector.transport.AbstractTransporter;
41 import org.eclipse.aether.spi.connector.transport.GetTask;
42 import org.eclipse.aether.spi.connector.transport.PeekTask;
43 import org.eclipse.aether.spi.connector.transport.PutTask;
44 import org.eclipse.aether.spi.connector.transport.Transporter;
45 import org.eclipse.aether.spi.io.PathProcessor;
46 import org.eclipse.aether.transfer.NoTransporterException;
47 import org.eclipse.aether.util.connector.transport.http.HttpTransporterUtils;
48
49 import static java.util.Objects.requireNonNull;
50
51
52
53
54
55
56 final class MinioTransporter extends AbstractTransporter implements Transporter {
57 private final URI baseUri;
58
59 private final Map<String, String> headers;
60
61 private final MinioClient client;
62
63 private final ObjectNameMapper objectNameMapper;
64
65 private final PathProcessor pathProcessor;
66
67 MinioTransporter(
68 RepositorySystemSession session,
69 RemoteRepository repository,
70 ObjectNameMapperFactory objectNameMapperFactory,
71 PathProcessor pathProcessor)
72 throws NoTransporterException {
73 try {
74 URI uri = new URI(repository.getUrl()).parseServerAuthority();
75 if (uri.isOpaque()) {
76 throw new URISyntaxException(repository.getUrl(), "URL must not be opaque");
77 }
78 if (uri.getRawFragment() != null || uri.getRawQuery() != null) {
79 throw new URISyntaxException(repository.getUrl(), "URL must not have fragment or query");
80 }
81 String path = uri.getPath();
82 if (path == null) {
83 path = "/";
84 }
85 if (!path.startsWith("/")) {
86 path = "/" + path;
87 }
88 if (!path.endsWith("/")) {
89 path = path + "/";
90 }
91 this.baseUri = URI.create(uri.getScheme() + "://" + uri.getRawAuthority() + path);
92 } catch (URISyntaxException e) {
93 throw new NoTransporterException(repository, e.getMessage(), e);
94 }
95
96 HashMap<String, String> headers = new HashMap<>();
97 Map<String, String> configuredHeaders = HttpTransporterUtils.getHttpHeaders(session, repository);
98 if (configuredHeaders != null) {
99 headers.putAll(configuredHeaders);
100 }
101 this.headers = headers;
102
103 String username = null;
104 String password = null;
105 try (AuthenticationContext repoAuthContext = AuthenticationContext.forRepository(session, repository)) {
106 if (repoAuthContext != null) {
107 username = repoAuthContext.get(AuthenticationContext.USERNAME);
108 password = repoAuthContext.get(AuthenticationContext.PASSWORD);
109 }
110 }
111 if (username == null || password == null) {
112 throw new IllegalStateException(
113 "Minio transport: No accessKey and/or secretKey provided for repository " + repository.getId());
114 }
115
116 Provider credentialsProvider = new StaticProvider(username, password, null);
117 this.client = MinioClient.builder()
118 .endpoint(repository.getUrl())
119 .credentialsProvider(credentialsProvider)
120 .build();
121 this.objectNameMapper = objectNameMapperFactory.create(session, repository, client, headers);
122 this.pathProcessor = requireNonNull(pathProcessor);
123 }
124
125 @Override
126 public int classify(Throwable error) {
127 if (error instanceof ErrorResponseException) {
128 String errorCode = ((ErrorResponseException) error).errorResponse().code();
129 if ("NoSuchKey".equals(errorCode) || "NoSuchBucket".equals(errorCode)) {
130 return ERROR_NOT_FOUND;
131 }
132 }
133 return ERROR_OTHER;
134 }
135
136 @Override
137 protected void implPeek(PeekTask task) throws Exception {
138 ObjectName objectName =
139 objectNameMapper.name(baseUri.relativize(task.getLocation()).getPath());
140 StatObjectArgs.Builder builder = StatObjectArgs.builder()
141 .bucket(objectName.getBucket())
142 .object(objectName.getName())
143 .extraHeaders(headers);
144 client.statObject(builder.build());
145 }
146
147 @Override
148 protected void implGet(GetTask task) throws Exception {
149 ObjectName objectName =
150 objectNameMapper.name(baseUri.relativize(task.getLocation()).getPath());
151 try (InputStream stream = client.getObject(GetObjectArgs.builder()
152 .bucket(objectName.getBucket())
153 .object(objectName.getName())
154 .extraHeaders(headers)
155 .build())) {
156 final Path dataFile = task.getDataPath();
157 if (dataFile == null) {
158 utilGet(task, stream, true, -1, false);
159 } else {
160 try (PathProcessor.CollocatedTempFile tempFile = pathProcessor.newTempFile(dataFile)) {
161 task.setDataPath(tempFile.getPath(), false);
162 utilGet(task, stream, true, -1, false);
163 tempFile.move();
164 } finally {
165 task.setDataPath(dataFile);
166 }
167 }
168 }
169 }
170
171 @Override
172 protected void implPut(PutTask task) throws Exception {
173 ObjectName objectName =
174 objectNameMapper.name(baseUri.relativize(task.getLocation()).getPath());
175 task.getListener().transportStarted(0, task.getDataLength());
176 final Path dataFile = task.getDataPath();
177 if (dataFile == null) {
178 try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
179 Files.copy(task.newInputStream(), tempFile.getPath(), StandardCopyOption.REPLACE_EXISTING);
180 client.uploadObject(UploadObjectArgs.builder()
181 .bucket(objectName.getBucket())
182 .object(objectName.getName())
183 .filename(tempFile.getPath().toString())
184 .build());
185 }
186 } else {
187 client.uploadObject(UploadObjectArgs.builder()
188 .bucket(objectName.getBucket())
189 .object(objectName.getName())
190 .filename(dataFile.toString())
191 .build());
192 }
193 }
194
195 @Override
196 protected void implClose() {
197 try {
198 client.close();
199 } catch (Exception e) {
200 throw new RuntimeException(e);
201 }
202 }
203 }