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.io.File;
022import java.nio.file.Path;
023import java.time.Clock;
024import java.time.Instant;
025
026import org.eclipse.aether.RequestTrace;
027
028/**
029 * Describes a resource being uploaded or downloaded by the repository system.
030 */
031public final class TransferResource {
032
033    private static Clock clock = Clock.systemUTC();
034
035    private final String repositoryId;
036
037    private final String repositoryUrl;
038
039    private final String resourceName;
040
041    private final Object resource;
042
043    private final Path path;
044
045    private final Instant startTime;
046
047    private final RequestTrace trace;
048
049    private volatile long contentLength = -1L;
050
051    private volatile long resumeOffset;
052
053    /**
054     * Gets the clock used to determine the start time of transfers.
055     *
056     * @return The clock currently in use, never {@code null}.
057     */
058    public static Clock getClock() {
059        return clock;
060    }
061
062    /**
063     * Sets the clock used to determine the start time of transfers. Intended for testing purposes.
064     *
065     * @param clock The clock to use, must not be {@code null}.
066     */
067    public static void setClock(Clock clock) {
068        TransferResource.clock = clock;
069    }
070
071    /**
072     * Creates a new transfer resource with the specified properties.
073     *
074     * @param repositoryId The ID of the repository used to transfer the resource, may be {@code null} or
075     *                     empty if unknown.
076     * @param repositoryUrl The base URL of the repository, may be {@code null} or empty if unknown. If not empty, a
077     *            trailing slash will automatically be added if missing.
078     * @param resourceName The relative path to the resource within the repository, may be {@code null}. A leading slash
079     *            (if any) will be automatically removed.
080     * @param file The source/target file involved in the transfer, may be {@code null}.
081     * @param trace The trace information, may be {@code null}.
082     *
083     * @since 1.1.0
084     * @deprecated Use {@link TransferResource(String, String, String, Path, Object, RequestTrace)} instead.
085     */
086    @Deprecated
087    public TransferResource(
088            String repositoryId, String repositoryUrl, String resourceName, File file, RequestTrace trace) {
089        this(repositoryId, repositoryUrl, resourceName, file != null ? file.toPath() : null, null, trace);
090    }
091
092    /**
093     * Creates a new transfer resource with the specified properties.
094     *
095     * @param repositoryId The ID of the repository used to transfer the resource, may be {@code null} or
096     *                     empty if unknown.
097     * @param repositoryUrl The base URL of the repository, may be {@code null} or empty if unknown. If not empty, a
098     *            trailing slash will automatically be added if missing.
099     * @param resourceName The relative path to the resource within the repository, may be {@code null}. A leading slash
100     *            (if any) will be automatically removed.
101     * @param path The source/target file involved in the transfer, may be {@code null}.
102     * @param resource The representation of this resource, may be {@code null}.
103     * @param trace The trace information, may be {@code null}.
104     *
105     * @since 2.0.0
106     */
107    public TransferResource(
108            String repositoryId,
109            String repositoryUrl,
110            String resourceName,
111            Path path,
112            Object resource,
113            RequestTrace trace) {
114        if (repositoryId == null || repositoryId.isEmpty()) {
115            this.repositoryId = "";
116        } else {
117            this.repositoryId = repositoryId;
118        }
119
120        if (repositoryUrl == null || repositoryUrl.isEmpty()) {
121            this.repositoryUrl = "";
122        } else if (repositoryUrl.endsWith("/")) {
123            this.repositoryUrl = repositoryUrl;
124        } else {
125            this.repositoryUrl = repositoryUrl + '/';
126        }
127
128        if (resourceName == null || resourceName.isEmpty()) {
129            this.resourceName = "";
130        } else if (resourceName.startsWith("/")) {
131            this.resourceName = resourceName.substring(1);
132        } else {
133            this.resourceName = resourceName;
134        }
135
136        this.path = path;
137        this.resource = resource;
138        this.trace = trace;
139        this.startTime = getClock().instant();
140    }
141
142    /**
143     * The ID of the repository, e.g., "central".
144     *
145     * @return The ID of the repository or an empty string if unknown, never {@code null}.
146     *
147     * @since 1.1.0
148     */
149    public String getRepositoryId() {
150        return repositoryId;
151    }
152
153    /**
154     * The base URL of the repository, e.g. "https://repo1.maven.org/maven2/". Unless the URL is unknown, it will be
155     * terminated by a trailing slash.
156     *
157     * @return The base URL of the repository or an empty string if unknown, never {@code null}.
158     */
159    public String getRepositoryUrl() {
160        return repositoryUrl;
161    }
162
163    /**
164     * The path of the resource relative to the repository's base URL, e.g. "org/apache/maven/maven/3.0/maven-3.0.pom".
165     *
166     * @return The path of the resource, never {@code null}.
167     */
168    public String getResourceName() {
169        return resourceName;
170    }
171
172    /**
173     * The representation of "resource", if any. The content of this field may be
174     * {@link org.eclipse.aether.artifact.Artifact} or {@link org.eclipse.aether.metadata.Metadata} or {@code null}
175     * in case of some legacy flow. Preferred way to handle returned value is with {@code instanceof}.
176     *
177     * @return The representation of this resource, may be {@code null}.
178     * @since 2.0.0
179     */
180    public Object getResource() {
181        return resource;
182    }
183
184    /**
185     * Gets the local file being uploaded or downloaded. When the repository system merely checks for the existence of a
186     * remote resource, no local file will be involved in the transfer.
187     *
188     * @return The source/target file involved in the transfer or {@code null} if none.
189     * @deprecated Use {@link #getPath()} instead.
190     */
191    @Deprecated
192    public File getFile() {
193        return path != null ? path.toFile() : null;
194    }
195
196    /**
197     * Gets the local file being uploaded or downloaded. When the repository system merely checks for the existence of a
198     * remote resource, no local file will be involved in the transfer.
199     *
200     * @return The source/target file involved in the transfer or {@code null} if none.
201     * @since 2.0.0
202     */
203    public Path getPath() {
204        return path;
205    }
206
207    /**
208     * The size of the resource in bytes. Note that the size of a resource during downloads might be unknown to the
209     * client which is usually the case when transfers employ compression like gzip. In general, the content length is
210     * not known until the transfer has {@link TransferListener#transferStarted(TransferEvent) started}.
211     *
212     * @return The size of the resource in bytes or a negative value if unknown.
213     */
214    public long getContentLength() {
215        return contentLength;
216    }
217
218    /**
219     * Sets the size of the resource in bytes.
220     *
221     * @param contentLength The size of the resource in bytes or a negative value if unknown.
222     * @return This resource for chaining, never {@code null}.
223     */
224    public TransferResource setContentLength(long contentLength) {
225        this.contentLength = contentLength;
226        return this;
227    }
228
229    /**
230     * Gets the byte offset within the resource from which the download starts. A positive offset indicates a previous
231     * download attempt is being resumed, {@code 0} means the transfer starts at the first byte.
232     *
233     * @return The zero-based index of the first byte being transferred, never negative.
234     */
235    public long getResumeOffset() {
236        return resumeOffset;
237    }
238
239    /**
240     * Sets the byte offset within the resource at which the download starts.
241     *
242     * @param resumeOffset The zero-based index of the first byte being transferred, must not be negative.
243     * @return This resource for chaining, never {@code null}.
244     */
245    public TransferResource setResumeOffset(long resumeOffset) {
246        if (resumeOffset < 0L) {
247            throw new IllegalArgumentException("resume offset cannot be negative");
248        }
249        this.resumeOffset = resumeOffset;
250        return this;
251    }
252
253    /**
254     * Gets the timestamp when the transfer of this resource was started.
255     *
256     * @return The timestamp when the transfer of this resource was started.
257     * @deprecated use {@link #getStartTime()}
258     */
259    @Deprecated
260    public long getTransferStartTime() {
261        return startTime.toEpochMilli();
262    }
263
264    /**
265     * Gets the timestamp when the transfer of this resource was started.
266     *
267     * @return The timestamp when the transfer of this resource was started.
268     */
269    public Instant getStartTime() {
270        return startTime;
271    }
272
273    /**
274     * Gets the trace information that describes the higher level request/operation during which this resource is
275     * transferred.
276     *
277     * @return The trace information about the higher level operation or {@code null} if none.
278     */
279    public RequestTrace getTrace() {
280        return trace;
281    }
282
283    @Override
284    public String toString() {
285        return getRepositoryUrl() + getResourceName() + " <> " + getPath();
286    }
287}