1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.eclipse.aether.transfer;
20
21 import java.nio.ByteBuffer;
22 import java.util.Collections;
23 import java.util.Map;
24
25 import org.eclipse.aether.RepositorySystemSession;
26
27 import static java.util.Objects.requireNonNull;
28
29 /**
30 * An event fired to a transfer listener during an artifact/metadata transfer.
31 *
32 * @see TransferListener
33 * @see TransferEvent.Builder
34 */
35 public final class TransferEvent {
36
37 /**
38 * The type of the event.
39 */
40 public enum EventType {
41
42 /**
43 * The transfer has been initiated.
44 *
45 * @see TransferListener#transferInitiated(TransferEvent)
46 */
47 INITIATED,
48
49 /**
50 * The transfer has started.
51 *
52 * @see TransferListener#transferStarted(TransferEvent)
53 */
54 STARTED,
55
56 /**
57 * The transfer is progressing.
58 *
59 * @see TransferListener#transferProgressed(TransferEvent)
60 */
61 PROGRESSED,
62
63 /**
64 * The transferred data is corrupt.
65 *
66 * @see TransferListener#transferCorrupted(TransferEvent)
67 */
68 CORRUPTED,
69
70 /**
71 * The transfer has succeeded.
72 *
73 * @see TransferListener#transferSucceeded(TransferEvent)
74 */
75 SUCCEEDED,
76
77 /**
78 * The transfer has failed.
79 *
80 * @see TransferListener#transferFailed(TransferEvent)
81 */
82 FAILED
83 }
84
85 /**
86 * The type of the request/transfer being performed.
87 */
88 public enum RequestType {
89
90 /**
91 * Download artifact/metadata.
92 */
93 GET,
94
95 /**
96 * Check artifact/metadata existence only.
97 */
98 GET_EXISTENCE,
99
100 /**
101 * Upload artifact/metadata.
102 */
103 PUT,
104 }
105
106 private final EventType type;
107
108 private final RequestType requestType;
109
110 private final RepositorySystemSession session;
111
112 private final TransferResource resource;
113
114 private final ByteBuffer dataBuffer;
115
116 private final long transferredBytes;
117
118 private final Exception exception;
119
120 private final Map<TransportPropertyKey, Object> transportProperties;
121
122 TransferEvent(Builder builder) {
123 type = builder.type;
124 requestType = builder.requestType;
125 session = builder.session;
126 resource = builder.resource;
127 dataBuffer = builder.dataBuffer;
128 transferredBytes = builder.transferredBytes;
129 exception = builder.exception;
130 transportProperties = builder.transportProperties;
131 }
132
133 /**
134 * Gets the type of the event.
135 *
136 * @return The type of the event, never {@code null}.
137 */
138 public EventType getType() {
139 return type;
140 }
141
142 /**
143 * Gets the type of the request/transfer.
144 *
145 * @return The type of the request/transfer, never {@code null}.
146 */
147 public RequestType getRequestType() {
148 return requestType;
149 }
150
151 /**
152 * Gets the repository system session during which the event occurred.
153 *
154 * @return The repository system session during which the event occurred, never {@code null}.
155 */
156 public RepositorySystemSession getSession() {
157 return session;
158 }
159
160 /**
161 * Gets the resource that is being transferred.
162 *
163 * @return The resource being transferred, never {@code null}.
164 */
165 public TransferResource getResource() {
166 return resource;
167 }
168
169 /**
170 * Gets the total number of bytes that have been transferred since the download/upload of the resource was started.
171 * If a download has been resumed, the returned count includes the bytes that were already downloaded during the
172 * previous attempt. In other words, the ratio of transferred bytes to the content length of the resource indicates
173 * the percentage of transfer completion.
174 *
175 * @return The total number of bytes that have been transferred since the transfer started, never negative.
176 * @see #getDataLength()
177 * @see TransferResource#getResumeOffset()
178 */
179 public long getTransferredBytes() {
180 return transferredBytes;
181 }
182
183 /**
184 * Gets the byte buffer holding the transferred bytes since the last event. A listener must assume this buffer to be
185 * owned by the event source and must not change any byte in this buffer. Also, the buffer is only valid for the
186 * duration of the event callback, i.e. the next event might reuse the same buffer (with updated contents).
187 * Therefore, if the actual event processing is deferred, the byte buffer would have to be cloned to create an
188 * immutable snapshot of its contents.
189 *
190 * @return The (read-only) byte buffer or {@code null} if not applicable to the event, i.e. if the event type is not
191 * {@link EventType#PROGRESSED}.
192 */
193 public ByteBuffer getDataBuffer() {
194 return (dataBuffer != null) ? dataBuffer.asReadOnlyBuffer() : null;
195 }
196
197 /**
198 * Gets the number of bytes that have been transferred since the last event.
199 *
200 * @return The number of bytes that have been transferred since the last event, possibly zero but never negative.
201 * @see #getTransferredBytes()
202 */
203 public int getDataLength() {
204 return (dataBuffer != null) ? dataBuffer.remaining() : 0;
205 }
206
207 /**
208 * Gets the error that occurred during the transfer.
209 *
210 * @return The error that occurred or {@code null} if none.
211 */
212 public Exception getException() {
213 return exception;
214 }
215
216 /**
217 * Get the transport properties associated with this transfer.
218 * The keys are transporter specific and the value types are key specific.
219 * This is only potentially not empty for the following events:
220 * <ul>
221 * <li>{@link EventType#CORRUPTED}</li>
222 * <li>{@link EventType#FAILED}</li>
223 * <li>{@link EventType#SUCCEEDED}</li>
224 * </ul>
225 * @return The immutable transport properties associated with this transfer, may be empty.
226 * @since 2.0.21
227 * @see HttpTransportProperty.Key HttpTransportProperty.Key for HTTP specific keys
228 */
229 public Map<TransportPropertyKey, Object> getTransportProperties() {
230 return transportProperties;
231 }
232
233 @Override
234 public String toString() {
235 return getRequestType() + " " + getType() + " " + getResource();
236 }
237
238 /**
239 * A builder to create transfer events.
240 */
241 public static final class Builder {
242
243 EventType type;
244
245 RequestType requestType;
246
247 final RepositorySystemSession session;
248
249 final TransferResource resource;
250
251 ByteBuffer dataBuffer;
252
253 long transferredBytes;
254
255 Exception exception;
256
257 Map<TransportPropertyKey, Object> transportProperties;
258
259 /**
260 * Creates a new transfer event builder for the specified session and the given resource.
261 *
262 * @param session The repository system session, must not be {@code null}.
263 * @param resource The resource being transferred, must not be {@code null}.
264 */
265 public Builder(RepositorySystemSession session, TransferResource resource) {
266 this.session = requireNonNull(session, "repository system session cannot be null");
267 this.resource = requireNonNull(resource, "transfer resource cannot be null");
268 type = EventType.INITIATED;
269 requestType = RequestType.GET;
270 transportProperties = Collections.emptyMap();
271 }
272
273 private Builder(Builder prototype) {
274 session = prototype.session;
275 resource = prototype.resource;
276 type = prototype.type;
277 requestType = prototype.requestType;
278 dataBuffer = prototype.dataBuffer;
279 transferredBytes = prototype.transferredBytes;
280 exception = prototype.exception;
281 transportProperties = prototype.transportProperties;
282 }
283
284 /**
285 * Creates a new transfer event builder from the current values of this builder. The state of this builder
286 * remains unchanged.
287 *
288 * @return The new event builder, never {@code null}.
289 */
290 public Builder copy() {
291 return new Builder(this);
292 }
293
294 /**
295 * Sets the type of the event and resets event-specific fields. In more detail, the data buffer and the
296 * exception fields are set to {@code null}. Furthermore, the total number of transferred bytes is set to
297 * {@code 0} if the event type is {@link EventType#STARTED}.
298 *
299 * @param type The type of the event, must not be {@code null}.
300 * @return This event builder for chaining, never {@code null}.
301 */
302 public Builder resetType(EventType type) {
303 this.type = requireNonNull(type, "event type cannot be null");
304 dataBuffer = null;
305 exception = null;
306 switch (type) {
307 case INITIATED:
308 case STARTED:
309 transferredBytes = 0L;
310 default:
311 }
312 return this;
313 }
314
315 /**
316 * Sets the type of the event. When re-using the same builder to generate a sequence of events for one transfer,
317 * {@link #resetType(TransferEvent.EventType)} might be more handy.
318 *
319 * @param type The type of the event, must not be {@code null}.
320 * @return This event builder for chaining, never {@code null}.
321 */
322 public Builder setType(EventType type) {
323 this.type = requireNonNull(type, "event type cannot be null");
324 return this;
325 }
326
327 /**
328 * Sets the type of the request/transfer.
329 *
330 * @param requestType The request/transfer type, must not be {@code null}.
331 * @return This event builder for chaining, never {@code null}.
332 */
333 public Builder setRequestType(RequestType requestType) {
334 this.requestType = requireNonNull(requestType, "request type cannot be null");
335 return this;
336 }
337
338 /**
339 * Sets the total number of bytes that have been transferred so far during the download/upload of the resource.
340 * If a download is being resumed, the count must include the bytes that were already downloaded in the previous
341 * attempt and from which the current transfer started. In this case, the event type {@link EventType#STARTED}
342 * should indicate from what byte the download resumes.
343 *
344 * @param transferredBytes The total number of bytes that have been transferred so far during the
345 * download/upload of the resource, must not be negative.
346 * @return This event builder for chaining, never {@code null}.
347 * @see TransferResource#setResumeOffset(long)
348 */
349 public Builder setTransferredBytes(long transferredBytes) {
350 if (transferredBytes < 0L) {
351 throw new IllegalArgumentException("number of transferred bytes cannot be negative");
352 }
353 this.transferredBytes = transferredBytes;
354 return this;
355 }
356
357 /**
358 * Increments the total number of bytes that have been transferred so far during the download/upload.
359 *
360 * @param transferredBytes The number of bytes that have been transferred since the last event, must not be
361 * negative.
362 * @return This event builder for chaining, never {@code null}.
363 */
364 public Builder addTransferredBytes(long transferredBytes) {
365 if (transferredBytes < 0L) {
366 throw new IllegalArgumentException("number of transferred bytes cannot be negative");
367 }
368 this.transferredBytes += transferredBytes;
369 return this;
370 }
371
372 /**
373 * Sets the byte buffer holding the transferred bytes since the last event.
374 *
375 * @param buffer The byte buffer holding the transferred bytes since the last event, may be {@code null} if not
376 * applicable to the event.
377 * @param offset The starting point of valid bytes in the array.
378 * @param length The number of valid bytes, must not be negative.
379 * @return This event builder for chaining, never {@code null}.
380 */
381 public Builder setDataBuffer(byte[] buffer, int offset, int length) {
382 return setDataBuffer((buffer != null) ? ByteBuffer.wrap(buffer, offset, length) : null);
383 }
384
385 /**
386 * Sets the byte buffer holding the transferred bytes since the last event.
387 *
388 * @param dataBuffer The byte buffer holding the transferred bytes since the last event, may be {@code null} if
389 * not applicable to the event.
390 * @return This event builder for chaining, never {@code null}.
391 */
392 public Builder setDataBuffer(ByteBuffer dataBuffer) {
393 this.dataBuffer = dataBuffer;
394 return this;
395 }
396
397 /**
398 * Sets the error that occurred during the transfer.
399 *
400 * @param exception The error that occurred during the transfer, may be {@code null} if none.
401 * @return This event builder for chaining, never {@code null}.
402 */
403 public Builder setException(Exception exception) {
404 this.exception = exception;
405 return this;
406 }
407
408 /**
409 * Sets the transport properties associated with this transfer. The keys are transporter specific and the value types are key specific.
410 * @param transportProperties The transport properties used in the underlying transfer, must not be {@code null}.
411 * @return This event builder for chaining, never {@code null}.
412 */
413 public Builder setTransportProperties(Map<TransportPropertyKey, Object> transportProperties) {
414 requireNonNull(transportProperties, "transportProperties cannot be null");
415 this.transportProperties = Collections.unmodifiableMap(transportProperties);
416 return this;
417 }
418
419 /**
420 * Builds a new transfer event from the current values of this builder. The state of the builder itself remains
421 * unchanged.
422 *
423 * @return The transfer event, never {@code null}.
424 */
425 public TransferEvent build() {
426 return new TransferEvent(this);
427 }
428 }
429
430 /**
431 * Marker interface for keys used in {@link #getTransportProperties()}.
432 *
433 * @since 2.0.21
434 */
435 public interface TransportPropertyKey {}
436 }