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