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         * @see TransferListener#transferInitiated(TransferEvent)
044         */
045        INITIATED,
046
047        /**
048         * @see TransferListener#transferStarted(TransferEvent)
049         */
050        STARTED,
051
052        /**
053         * @see TransferListener#transferProgressed(TransferEvent)
054         */
055        PROGRESSED,
056
057        /**
058         * @see TransferListener#transferCorrupted(TransferEvent)
059         */
060        CORRUPTED,
061
062        /**
063         * @see TransferListener#transferSucceeded(TransferEvent)
064         */
065        SUCCEEDED,
066
067        /**
068         * @see TransferListener#transferFailed(TransferEvent)
069         */
070        FAILED
071    }
072
073    /**
074     * The type of the request/transfer being performed.
075     */
076    public enum RequestType {
077
078        /**
079         * Download artifact/metadata.
080         */
081        GET,
082
083        /**
084         * Check artifact/metadata existence only.
085         */
086        GET_EXISTENCE,
087
088        /**
089         * Upload artifact/metadata.
090         */
091        PUT,
092    }
093
094    private final EventType type;
095
096    private final RequestType requestType;
097
098    private final RepositorySystemSession session;
099
100    private final TransferResource resource;
101
102    private final ByteBuffer dataBuffer;
103
104    private final long transferredBytes;
105
106    private final Exception exception;
107
108    private final Map<TransportPropertyKey, Object> transportProperties;
109
110    TransferEvent(Builder builder) {
111        type = builder.type;
112        requestType = builder.requestType;
113        session = builder.session;
114        resource = builder.resource;
115        dataBuffer = builder.dataBuffer;
116        transferredBytes = builder.transferredBytes;
117        exception = builder.exception;
118        transportProperties = builder.transportProperties;
119    }
120
121    /**
122     * Gets the type of the event.
123     *
124     * @return The type of the event, never {@code null}.
125     */
126    public EventType getType() {
127        return type;
128    }
129
130    /**
131     * Gets the type of the request/transfer.
132     *
133     * @return The type of the request/transfer, never {@code null}.
134     */
135    public RequestType getRequestType() {
136        return requestType;
137    }
138
139    /**
140     * Gets the repository system session during which the event occurred.
141     *
142     * @return The repository system session during which the event occurred, never {@code null}.
143     */
144    public RepositorySystemSession getSession() {
145        return session;
146    }
147
148    /**
149     * Gets the resource that is being transferred.
150     *
151     * @return The resource being transferred, never {@code null}.
152     */
153    public TransferResource getResource() {
154        return resource;
155    }
156
157    /**
158     * Gets the total number of bytes that have been transferred since the download/upload of the resource was started.
159     * If a download has been resumed, the returned count includes the bytes that were already downloaded during the
160     * previous attempt. In other words, the ratio of transferred bytes to the content length of the resource indicates
161     * the percentage of transfer completion.
162     *
163     * @return The total number of bytes that have been transferred since the transfer started, never negative.
164     * @see #getDataLength()
165     * @see TransferResource#getResumeOffset()
166     */
167    public long getTransferredBytes() {
168        return transferredBytes;
169    }
170
171    /**
172     * Gets the byte buffer holding the transferred bytes since the last event. A listener must assume this buffer to be
173     * owned by the event source and must not change any byte in this buffer. Also, the buffer is only valid for the
174     * duration of the event callback, i.e. the next event might reuse the same buffer (with updated contents).
175     * Therefore, if the actual event processing is deferred, the byte buffer would have to be cloned to create an
176     * immutable snapshot of its contents.
177     *
178     * @return The (read-only) byte buffer or {@code null} if not applicable to the event, i.e. if the event type is not
179     *         {@link EventType#PROGRESSED}.
180     */
181    public ByteBuffer getDataBuffer() {
182        return (dataBuffer != null) ? dataBuffer.asReadOnlyBuffer() : null;
183    }
184
185    /**
186     * Gets the number of bytes that have been transferred since the last event.
187     *
188     * @return The number of bytes that have been transferred since the last event, possibly zero but never negative.
189     * @see #getTransferredBytes()
190     */
191    public int getDataLength() {
192        return (dataBuffer != null) ? dataBuffer.remaining() : 0;
193    }
194
195    /**
196     * Gets the error that occurred during the transfer.
197     *
198     * @return The error that occurred or {@code null} if none.
199     */
200    public Exception getException() {
201        return exception;
202    }
203
204    /**
205     * Get the transport properties associated with this transfer.
206     * The keys are transporter specific and the value types are key specific.
207     * This is only potentially not empty for the following events:
208     * <ul>
209     * <li>{@link EventType#CORRUPTED}</li>
210     * <li>{@link EventType#FAILED}</li>
211     * <li>{@link EventType#SUCCEEDED}</li>
212     * </ul>
213     * @return The immutable transport properties associated with this transfer, may be empty.
214     * @since 2.0.21
215     * @see HttpTransportProperty.Key HttpTransportProperty.Key for HTTP specific keys
216     */
217    public Map<TransportPropertyKey, Object> getTransportProperties() {
218        return transportProperties;
219    }
220
221    @Override
222    public String toString() {
223        return getRequestType() + " " + getType() + " " + getResource();
224    }
225
226    /**
227     * A builder to create transfer events.
228     */
229    public static final class Builder {
230
231        EventType type;
232
233        RequestType requestType;
234
235        final RepositorySystemSession session;
236
237        final TransferResource resource;
238
239        ByteBuffer dataBuffer;
240
241        long transferredBytes;
242
243        Exception exception;
244
245        Map<TransportPropertyKey, Object> transportProperties;
246
247        /**
248         * Creates a new transfer event builder for the specified session and the given resource.
249         *
250         * @param session The repository system session, must not be {@code null}.
251         * @param resource The resource being transferred, must not be {@code null}.
252         */
253        public Builder(RepositorySystemSession session, TransferResource resource) {
254            this.session = requireNonNull(session, "repository system session cannot be null");
255            this.resource = requireNonNull(resource, "transfer resource cannot be null");
256            type = EventType.INITIATED;
257            requestType = RequestType.GET;
258            transportProperties = Collections.emptyMap();
259        }
260
261        private Builder(Builder prototype) {
262            session = prototype.session;
263            resource = prototype.resource;
264            type = prototype.type;
265            requestType = prototype.requestType;
266            dataBuffer = prototype.dataBuffer;
267            transferredBytes = prototype.transferredBytes;
268            exception = prototype.exception;
269            transportProperties = prototype.transportProperties;
270        }
271
272        /**
273         * Creates a new transfer event builder from the current values of this builder. The state of this builder
274         * remains unchanged.
275         *
276         * @return The new event builder, never {@code null}.
277         */
278        public Builder copy() {
279            return new Builder(this);
280        }
281
282        /**
283         * Sets the type of the event and resets event-specific fields. In more detail, the data buffer and the
284         * exception fields are set to {@code null}. Furthermore, the total number of transferred bytes is set to
285         * {@code 0} if the event type is {@link EventType#STARTED}.
286         *
287         * @param type The type of the event, must not be {@code null}.
288         * @return This event builder for chaining, never {@code null}.
289         */
290        public Builder resetType(EventType type) {
291            this.type = requireNonNull(type, "event type cannot be null");
292            dataBuffer = null;
293            exception = null;
294            switch (type) {
295                case INITIATED:
296                case STARTED:
297                    transferredBytes = 0L;
298                default:
299            }
300            return this;
301        }
302
303        /**
304         * Sets the type of the event. When re-using the same builder to generate a sequence of events for one transfer,
305         * {@link #resetType(TransferEvent.EventType)} might be more handy.
306         *
307         * @param type The type of the event, must not be {@code null}.
308         * @return This event builder for chaining, never {@code null}.
309         */
310        public Builder setType(EventType type) {
311            this.type = requireNonNull(type, "event type cannot be null");
312            return this;
313        }
314
315        /**
316         * Sets the type of the request/transfer.
317         *
318         * @param requestType The request/transfer type, must not be {@code null}.
319         * @return This event builder for chaining, never {@code null}.
320         */
321        public Builder setRequestType(RequestType requestType) {
322            this.requestType = requireNonNull(requestType, "request type cannot be null");
323            return this;
324        }
325
326        /**
327         * Sets the total number of bytes that have been transferred so far during the download/upload of the resource.
328         * If a download is being resumed, the count must include the bytes that were already downloaded in the previous
329         * attempt and from which the current transfer started. In this case, the event type {@link EventType#STARTED}
330         * should indicate from what byte the download resumes.
331         *
332         * @param transferredBytes The total number of bytes that have been transferred so far during the
333         *            download/upload of the resource, must not be negative.
334         * @return This event builder for chaining, never {@code null}.
335         * @see TransferResource#setResumeOffset(long)
336         */
337        public Builder setTransferredBytes(long transferredBytes) {
338            if (transferredBytes < 0L) {
339                throw new IllegalArgumentException("number of transferred bytes cannot be negative");
340            }
341            this.transferredBytes = transferredBytes;
342            return this;
343        }
344
345        /**
346         * Increments the total number of bytes that have been transferred so far during the download/upload.
347         *
348         * @param transferredBytes The number of bytes that have been transferred since the last event, must not be
349         *            negative.
350         * @return This event builder for chaining, never {@code null}.
351         */
352        public Builder addTransferredBytes(long transferredBytes) {
353            if (transferredBytes < 0L) {
354                throw new IllegalArgumentException("number of transferred bytes cannot be negative");
355            }
356            this.transferredBytes += transferredBytes;
357            return this;
358        }
359
360        /**
361         * Sets the byte buffer holding the transferred bytes since the last event.
362         *
363         * @param buffer The byte buffer holding the transferred bytes since the last event, may be {@code null} if not
364         *            applicable to the event.
365         * @param offset The starting point of valid bytes in the array.
366         * @param length The number of valid bytes, must not be negative.
367         * @return This event builder for chaining, never {@code null}.
368         */
369        public Builder setDataBuffer(byte[] buffer, int offset, int length) {
370            return setDataBuffer((buffer != null) ? ByteBuffer.wrap(buffer, offset, length) : null);
371        }
372
373        /**
374         * Sets the byte buffer holding the transferred bytes since the last event.
375         *
376         * @param dataBuffer The byte buffer holding the transferred bytes since the last event, may be {@code null} if
377         *            not applicable to the event.
378         * @return This event builder for chaining, never {@code null}.
379         */
380        public Builder setDataBuffer(ByteBuffer dataBuffer) {
381            this.dataBuffer = dataBuffer;
382            return this;
383        }
384
385        /**
386         * Sets the error that occurred during the transfer.
387         *
388         * @param exception The error that occurred during the transfer, may be {@code null} if none.
389         * @return This event builder for chaining, never {@code null}.
390         */
391        public Builder setException(Exception exception) {
392            this.exception = exception;
393            return this;
394        }
395
396        /**
397         * Sets the transport properties associated with this transfer. The keys are transporter specific and the value types are key specific.
398         * @param transportProperties The transport properties used in the underlying transfer, must not be {@code null}.
399         * @return This event builder for chaining, never {@code null}.
400         */
401        public Builder setTransportProperties(Map<TransportPropertyKey, Object> transportProperties) {
402            requireNonNull(transportProperties, "transportProperties cannot be null");
403            this.transportProperties = Collections.unmodifiableMap(transportProperties);
404            return this;
405        }
406
407        /**
408         * Builds a new transfer event from the current values of this builder. The state of the builder itself remains
409         * unchanged.
410         *
411         * @return The transfer event, never {@code null}.
412         */
413        public TransferEvent build() {
414            return new TransferEvent(this);
415        }
416    }
417
418    public interface TransportPropertyKey {}
419}