001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.transfer;
020
021import java.nio.ByteBuffer;
022import java.util.Collections;
023import java.util.Map;
024
025import org.eclipse.aether.RepositorySystemSession;
026
027import static java.util.Objects.requireNonNull;
028
029/**
030 * An event fired to a transfer listener during an artifact/metadata transfer.
031 *
032 * @see TransferListener
033 * @see TransferEvent.Builder
034 */
035public final class TransferEvent {
036
037    /**
038     * The type of the event.
039     */
040    public enum EventType {
041
042        /**
043         * The transfer has been initiated.
044         *
045         * @see TransferListener#transferInitiated(TransferEvent)
046         */
047        INITIATED,
048
049        /**
050         * The transfer has started.
051         *
052         * @see TransferListener#transferStarted(TransferEvent)
053         */
054        STARTED,
055
056        /**
057         * The transfer is progressing.
058         *
059         * @see TransferListener#transferProgressed(TransferEvent)
060         */
061        PROGRESSED,
062
063        /**
064         * The transferred data is corrupt.
065         *
066         * @see TransferListener#transferCorrupted(TransferEvent)
067         */
068        CORRUPTED,
069
070        /**
071         * The transfer has succeeded.
072         *
073         * @see TransferListener#transferSucceeded(TransferEvent)
074         */
075        SUCCEEDED,
076
077        /**
078         * The transfer has failed.
079         *
080         * @see TransferListener#transferFailed(TransferEvent)
081         */
082        FAILED
083    }
084
085    /**
086     * The type of the request/transfer being performed.
087     */
088    public enum RequestType {
089
090        /**
091         * Download artifact/metadata.
092         */
093        GET,
094
095        /**
096         * Check artifact/metadata existence only.
097         */
098        GET_EXISTENCE,
099
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}