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