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