001package org.eclipse.aether.transfer;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.nio.ByteBuffer;
023import static java.util.Objects.requireNonNull;
024
025import org.eclipse.aether.RepositorySystemSession;
026
027/**
028 * An event fired to a transfer listener during an artifact/metadata transfer.
029 *
030 * @see TransferListener
031 * @see TransferEvent.Builder
032 */
033public final class TransferEvent
034{
035
036    /**
037     * The type of the event.
038     */
039    public enum EventType
040    {
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    /**
075     * The type of the request/transfer being performed.
076     */
077    public enum RequestType
078    {
079
080        /**
081         * Download artifact/metadata.
082         */
083        GET,
084
085        /**
086         * Check artifact/metadata existence only.
087         */
088        GET_EXISTENCE,
089
090        /**
091         * Upload artifact/metadata.
092         */
093        PUT,
094
095    }
096
097    private final EventType type;
098
099    private final RequestType requestType;
100
101    private final RepositorySystemSession session;
102
103    private final TransferResource resource;
104
105    private final ByteBuffer dataBuffer;
106
107    private final long transferredBytes;
108
109    private final Exception exception;
110
111    TransferEvent( Builder builder )
112    {
113        type = builder.type;
114        requestType = builder.requestType;
115        session = builder.session;
116        resource = builder.resource;
117        dataBuffer = builder.dataBuffer;
118        transferredBytes = builder.transferredBytes;
119        exception = builder.exception;
120    }
121
122    /**
123     * Gets the type of the event.
124     * 
125     * @return The type of the event, never {@code null}.
126     */
127    public EventType getType()
128    {
129        return type;
130    }
131
132    /**
133     * Gets the type of the request/transfer.
134     * 
135     * @return The type of the request/transfer, never {@code null}.
136     */
137    public RequestType getRequestType()
138    {
139        return requestType;
140    }
141
142    /**
143     * Gets the repository system session during which the event occurred.
144     * 
145     * @return The repository system session during which the event occurred, never {@code null}.
146     */
147    public RepositorySystemSession getSession()
148    {
149        return session;
150    }
151
152    /**
153     * Gets the resource that is being transferred.
154     * 
155     * @return The resource being transferred, never {@code null}.
156     */
157    public TransferResource getResource()
158    {
159        return resource;
160    }
161
162    /**
163     * Gets the total number of bytes that have been transferred since the download/upload of the resource was started.
164     * If a download has been resumed, the returned count includes the bytes that were already downloaded during the
165     * previous attempt. In other words, the ratio of transferred bytes to the content length of the resource indicates
166     * the percentage of transfer completion.
167     * 
168     * @return The total number of bytes that have been transferred since the transfer started, never negative.
169     * @see #getDataLength()
170     * @see TransferResource#getResumeOffset()
171     */
172    public long getTransferredBytes()
173    {
174        return transferredBytes;
175    }
176
177    /**
178     * Gets the byte buffer holding the transferred bytes since the last event. A listener must assume this buffer to be
179     * owned by the event source and must not change any byte in this buffer. Also, the buffer is only valid for the
180     * duration of the event callback, i.e. the next event might reuse the same buffer (with updated contents).
181     * Therefore, if the actual event processing is deferred, the byte buffer would have to be cloned to create an
182     * immutable snapshot of its contents.
183     * 
184     * @return The (read-only) byte buffer or {@code null} if not applicable to the event, i.e. if the event type is not
185     *         {@link EventType#PROGRESSED}.
186     */
187    public ByteBuffer getDataBuffer()
188    {
189        return ( dataBuffer != null ) ? dataBuffer.asReadOnlyBuffer() : null;
190    }
191
192    /**
193     * Gets the number of bytes that have been transferred since the last event.
194     * 
195     * @return The number of bytes that have been transferred since the last event, possibly zero but never negative.
196     * @see #getTransferredBytes()
197     */
198    public int getDataLength()
199    {
200        return ( dataBuffer != null ) ? dataBuffer.remaining() : 0;
201    }
202
203    /**
204     * Gets the error that occurred during the transfer.
205     * 
206     * @return The error that occurred or {@code null} if none.
207     */
208    public Exception getException()
209    {
210        return exception;
211    }
212
213    @Override
214    public String toString()
215    {
216        return getRequestType() + " " + getType() + " " + getResource();
217    }
218
219    /**
220     * A builder to create transfer events.
221     */
222    public static final class Builder
223    {
224
225        EventType type;
226
227        RequestType requestType;
228
229        final RepositorySystemSession session;
230
231        final TransferResource resource;
232
233        ByteBuffer dataBuffer;
234
235        long transferredBytes;
236
237        Exception exception;
238
239        /**
240         * Creates a new transfer event builder for the specified session and the given resource.
241         *
242         * @param session The repository system session, must not be {@code null}.
243         * @param resource The resource being transferred, must not be {@code null}.
244         */
245        public Builder( RepositorySystemSession session, TransferResource resource )
246        {
247            this.session = requireNonNull( session, "repository system session cannot be null" );
248            this.resource = requireNonNull( resource, "transfer resource cannot be null" );
249            type = EventType.INITIATED;
250            requestType = RequestType.GET;
251        }
252
253        private Builder( Builder prototype )
254        {
255            session = prototype.session;
256            resource = prototype.resource;
257            type = prototype.type;
258            requestType = prototype.requestType;
259            dataBuffer = prototype.dataBuffer;
260            transferredBytes = prototype.transferredBytes;
261            exception = prototype.exception;
262        }
263
264        /**
265         * Creates a new transfer event builder from the current values of this builder. The state of this builder
266         * remains unchanged.
267         * 
268         * @return The new event builder, never {@code null}.
269         */
270        public Builder copy()
271        {
272            return new Builder( this );
273        }
274
275        /**
276         * Sets the type of the event and resets event-specific fields. In more detail, the data buffer and the
277         * exception fields are set to {@code null}. Furthermore, the total number of transferred bytes is set to
278         * {@code 0} if the event type is {@link EventType#STARTED}.
279         *
280         * @param type The type of the event, must not be {@code null}.
281         * @return This event builder for chaining, never {@code null}.
282         */
283        public Builder resetType( EventType type )
284        {
285            this.type = requireNonNull( type, "event type cannot be null" );
286            dataBuffer = null;
287            exception = null;
288            switch ( type )
289            {
290                case INITIATED:
291                case STARTED:
292                    transferredBytes = 0L;
293                default:
294            }
295            return this;
296        }
297
298        /**
299         * Sets the type of the event. When re-using the same builder to generate a sequence of events for one transfer,
300         * {@link #resetType(TransferEvent.EventType)} might be more handy.
301         *
302         * @param type The type of the event, must not be {@code null}.
303         * @return This event builder for chaining, never {@code null}.
304         */
305        public Builder setType( EventType type )
306        {
307            this.type = requireNonNull( type, "event type cannot be null" );
308            return this;
309        }
310
311        /**
312         * Sets the type of the request/transfer.
313         *
314         * @param requestType The request/transfer type, must not be {@code null}.
315         * @return This event builder for chaining, never {@code null}.
316         */
317        public Builder setRequestType( RequestType requestType )
318        {
319            this.requestType = requireNonNull( requestType, "request type cannot be null" );
320            return this;
321        }
322
323        /**
324         * Sets the total number of bytes that have been transferred so far during the download/upload of the resource.
325         * If a download is being resumed, the count must include the bytes that were already downloaded in the previous
326         * attempt and from which the current transfer started. In this case, the event type {@link EventType#STARTED}
327         * should indicate from what byte the download resumes.
328         * 
329         * @param transferredBytes The total number of bytes that have been transferred so far during the
330         *            download/upload of the resource, must not be negative.
331         * @return This event builder for chaining, never {@code null}.
332         * @see TransferResource#setResumeOffset(long)
333         */
334        public Builder setTransferredBytes( long transferredBytes )
335        {
336            if ( transferredBytes < 0L )
337            {
338                throw new IllegalArgumentException( "number of transferred bytes cannot be negative" );
339            }
340            this.transferredBytes = transferredBytes;
341            return this;
342        }
343
344        /**
345         * Increments the total number of bytes that have been transferred so far during the download/upload.
346         *
347         * @param transferredBytes The number of bytes that have been transferred since the last event, must not be
348         *            negative.
349         * @return This event builder for chaining, never {@code null}.
350         */
351        public Builder addTransferredBytes( long transferredBytes )
352        {
353            if ( transferredBytes < 0L )
354            {
355                throw new IllegalArgumentException( "number of transferred bytes cannot be negative" );
356            }
357            this.transferredBytes += transferredBytes;
358            return this;
359        }
360
361        /**
362         * Sets the byte buffer holding the transferred bytes since the last event.
363         * 
364         * @param buffer The byte buffer holding the transferred bytes since the last event, may be {@code null} if not
365         *            applicable to the event.
366         * @param offset The starting point of valid bytes in the array.
367         * @param length The number of valid bytes, must not be negative.
368         * @return This event builder for chaining, never {@code null}.
369         */
370        public Builder setDataBuffer( byte[] buffer, int offset, int length )
371        {
372            return setDataBuffer( ( buffer != null ) ? ByteBuffer.wrap( buffer, offset, length ) : null );
373        }
374
375        /**
376         * Sets the byte buffer holding the transferred bytes since the last event.
377         * 
378         * @param dataBuffer The byte buffer holding the transferred bytes since the last event, may be {@code null} if
379         *            not applicable to the event.
380         * @return This event builder for chaining, never {@code null}.
381         */
382        public Builder setDataBuffer( ByteBuffer dataBuffer )
383        {
384            this.dataBuffer = dataBuffer;
385            return this;
386        }
387
388        /**
389         * Sets the error that occurred during the transfer.
390         * 
391         * @param exception The error that occurred during the transfer, may be {@code null} if none.
392         * @return This event builder for chaining, never {@code null}.
393         */
394        public Builder setException( Exception exception )
395        {
396            this.exception = exception;
397            return this;
398        }
399
400        /**
401         * Builds a new transfer event from the current values of this builder. The state of the builder itself remains
402         * unchanged.
403         * 
404         * @return The transfer event, never {@code null}.
405         */
406        public TransferEvent build()
407        {
408            return new TransferEvent( this );
409        }
410
411    }
412
413}