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.repository;
020
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.List;
025import java.util.Objects;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import static java.util.Objects.requireNonNull;
030
031/**
032 * A repository on a remote server.
033 */
034public final class RemoteRepository implements ArtifactRepository {
035
036    private static final Pattern URL_PATTERN =
037            Pattern.compile("([^:/]+(:[^:/]{2,}+(?=://))?):(//([^@/]*@)?([^/:]+))?.*");
038
039    private final String id;
040
041    private final String type;
042
043    private final String url;
044
045    private final String host;
046
047    private final String protocol;
048
049    private final RepositoryPolicy releasePolicy;
050
051    private final RepositoryPolicy snapshotPolicy;
052
053    private final Proxy proxy;
054
055    private final Authentication authentication;
056
057    private final List<RemoteRepository> mirroredRepositories;
058
059    private final boolean repositoryManager;
060
061    private final boolean blocked;
062
063    RemoteRepository(Builder builder) {
064        if (builder.prototype != null) {
065            id = (builder.delta & Builder.ID) != 0 ? builder.id : builder.prototype.id;
066            type = (builder.delta & Builder.TYPE) != 0 ? builder.type : builder.prototype.type;
067            url = (builder.delta & Builder.URL) != 0 ? builder.url : builder.prototype.url;
068            releasePolicy =
069                    (builder.delta & Builder.RELEASES) != 0 ? builder.releasePolicy : builder.prototype.releasePolicy;
070            snapshotPolicy = (builder.delta & Builder.SNAPSHOTS) != 0
071                    ? builder.snapshotPolicy
072                    : builder.prototype.snapshotPolicy;
073            proxy = (builder.delta & Builder.PROXY) != 0 ? builder.proxy : builder.prototype.proxy;
074            authentication =
075                    (builder.delta & Builder.AUTH) != 0 ? builder.authentication : builder.prototype.authentication;
076            repositoryManager = (builder.delta & Builder.REPOMAN) != 0
077                    ? builder.repositoryManager
078                    : builder.prototype.repositoryManager;
079            blocked = (builder.delta & Builder.BLOCKED) != 0 ? builder.blocked : builder.prototype.blocked;
080            mirroredRepositories = (builder.delta & Builder.MIRRORED) != 0
081                    ? copy(builder.mirroredRepositories)
082                    : builder.prototype.mirroredRepositories;
083        } else {
084            id = builder.id;
085            type = builder.type;
086            url = builder.url;
087            releasePolicy = builder.releasePolicy;
088            snapshotPolicy = builder.snapshotPolicy;
089            proxy = builder.proxy;
090            authentication = builder.authentication;
091            repositoryManager = builder.repositoryManager;
092            blocked = builder.blocked;
093            mirroredRepositories = copy(builder.mirroredRepositories);
094        }
095
096        Matcher m = URL_PATTERN.matcher(url);
097        if (m.matches()) {
098            protocol = m.group(1);
099            String host = m.group(5);
100            this.host = (host != null) ? host : "";
101        } else {
102            protocol = "";
103            host = "";
104        }
105    }
106
107    private static List<RemoteRepository> copy(List<RemoteRepository> repos) {
108        if (repos == null || repos.isEmpty()) {
109            return Collections.emptyList();
110        }
111        return Collections.unmodifiableList(Arrays.asList(repos.toArray(new RemoteRepository[0])));
112    }
113
114    public String getId() {
115        return id;
116    }
117
118    public String getContentType() {
119        return type;
120    }
121
122    /**
123     * Gets the (base) URL of this repository.
124     *
125     * @return The (base) URL of this repository, never {@code null}.
126     */
127    public String getUrl() {
128        return url;
129    }
130
131    /**
132     * Gets the protocol part from the repository's URL, for example {@code file} or {@code http}. As suggested by RFC
133     * 2396, section 3.1 "Scheme Component", the protocol name should be treated case-insensitively.
134     *
135     * @return The protocol or an empty string if none, never {@code null}.
136     */
137    public String getProtocol() {
138        return protocol;
139    }
140
141    /**
142     * Gets the host part from the repository's URL.
143     *
144     * @return The host or an empty string if none, never {@code null}.
145     */
146    public String getHost() {
147        return host;
148    }
149
150    /**
151     * Gets the policy to apply for snapshot/release artifacts.
152     *
153     * @param snapshot {@code true} to retrieve the snapshot policy, {@code false} to retrieve the release policy.
154     * @return The requested repository policy, never {@code null}.
155     */
156    public RepositoryPolicy getPolicy(boolean snapshot) {
157        return snapshot ? snapshotPolicy : releasePolicy;
158    }
159
160    /**
161     * Gets the proxy that has been selected for this repository.
162     *
163     * @return The selected proxy or {@code null} if none.
164     */
165    public Proxy getProxy() {
166        return proxy;
167    }
168
169    /**
170     * Gets the authentication that has been selected for this repository.
171     *
172     * @return The selected authentication or {@code null} if none.
173     */
174    public Authentication getAuthentication() {
175        return authentication;
176    }
177
178    /**
179     * Gets the repositories that this repository serves as a mirror for.
180     *
181     * @return The (read-only) repositories being mirrored by this repository, never {@code null}.
182     */
183    public List<RemoteRepository> getMirroredRepositories() {
184        return mirroredRepositories;
185    }
186
187    /**
188     * Indicates whether this repository refers to a repository manager or not.
189     *
190     * @return {@code true} if this repository is a repository manager, {@code false} otherwise.
191     */
192    public boolean isRepositoryManager() {
193        return repositoryManager;
194    }
195
196    /**
197     * Indicates whether this repository is blocked from performing any download requests.
198     *
199     * @return {@code true} if this repository is blocked from performing any download requests,
200     *         {@code false} otherwise.
201     */
202    public boolean isBlocked() {
203        return blocked;
204    }
205
206    @Override
207    public String toString() {
208        StringBuilder buffer = new StringBuilder(256);
209        buffer.append(getId());
210        buffer.append(" (").append(getUrl());
211        buffer.append(", ").append(getContentType());
212        boolean r = getPolicy(false).isEnabled(), s = getPolicy(true).isEnabled();
213        if (r && s) {
214            buffer.append(", releases+snapshots");
215        } else if (r) {
216            buffer.append(", releases");
217        } else if (s) {
218            buffer.append(", snapshots");
219        } else {
220            buffer.append(", disabled");
221        }
222        if (isRepositoryManager()) {
223            buffer.append(", managed");
224        }
225        if (isBlocked()) {
226            buffer.append(", blocked");
227        }
228        buffer.append(")");
229        return buffer.toString();
230    }
231
232    @Override
233    public boolean equals(Object obj) {
234        if (this == obj) {
235            return true;
236        }
237        if (obj == null || !getClass().equals(obj.getClass())) {
238            return false;
239        }
240
241        RemoteRepository that = (RemoteRepository) obj;
242
243        return Objects.equals(url, that.url)
244                && Objects.equals(type, that.type)
245                && Objects.equals(id, that.id)
246                && Objects.equals(releasePolicy, that.releasePolicy)
247                && Objects.equals(snapshotPolicy, that.snapshotPolicy)
248                && Objects.equals(proxy, that.proxy)
249                && Objects.equals(authentication, that.authentication)
250                && Objects.equals(mirroredRepositories, that.mirroredRepositories)
251                && repositoryManager == that.repositoryManager;
252    }
253
254    @Override
255    public int hashCode() {
256        int hash = 17;
257        hash = hash * 31 + hash(url);
258        hash = hash * 31 + hash(type);
259        hash = hash * 31 + hash(id);
260        hash = hash * 31 + hash(releasePolicy);
261        hash = hash * 31 + hash(snapshotPolicy);
262        hash = hash * 31 + hash(proxy);
263        hash = hash * 31 + hash(authentication);
264        hash = hash * 31 + hash(mirroredRepositories);
265        hash = hash * 31 + (repositoryManager ? 1 : 0);
266        return hash;
267    }
268
269    private static int hash(Object obj) {
270        return obj != null ? obj.hashCode() : 0;
271    }
272
273    /**
274     * A builder to create remote repositories.
275     */
276    public static final class Builder {
277
278        private static final RepositoryPolicy DEFAULT_POLICY = new RepositoryPolicy();
279
280        static final int ID = 0x0001,
281                TYPE = 0x0002,
282                URL = 0x0004,
283                RELEASES = 0x0008,
284                SNAPSHOTS = 0x0010,
285                PROXY = 0x0020,
286                AUTH = 0x0040,
287                MIRRORED = 0x0080,
288                REPOMAN = 0x0100,
289                BLOCKED = 0x0200;
290
291        int delta;
292
293        RemoteRepository prototype;
294
295        String id;
296
297        String type;
298
299        String url;
300
301        RepositoryPolicy releasePolicy = DEFAULT_POLICY;
302
303        RepositoryPolicy snapshotPolicy = DEFAULT_POLICY;
304
305        Proxy proxy;
306
307        Authentication authentication;
308
309        List<RemoteRepository> mirroredRepositories;
310
311        boolean repositoryManager;
312
313        boolean blocked;
314
315        /**
316         * Creates a new repository builder.
317         *
318         * @param id The identifier of the repository, may be {@code null}.
319         * @param type The type of the repository, may be {@code null}.
320         * @param url The (base) URL of the repository, may be {@code null}.
321         */
322        public Builder(String id, String type, String url) {
323            this.id = (id != null) ? id : "";
324            this.type = (type != null) ? type : "";
325            this.url = (url != null) ? url : "";
326        }
327
328        /**
329         * Creates a new repository builder which uses the specified remote repository as a prototype for the new one.
330         * All properties which have not been set on the builder will be copied from the prototype when building the
331         * repository.
332         *
333         * @param prototype The remote repository to use as prototype, must not be {@code null}.
334         */
335        public Builder(RemoteRepository prototype) {
336            this.prototype = requireNonNull(prototype, "remote repository prototype cannot be null");
337        }
338
339        /**
340         * Builds a new remote repository from the current values of this builder. The state of the builder itself
341         * remains unchanged.
342         *
343         * @return The remote repository, never {@code null}.
344         */
345        public RemoteRepository build() {
346            if (prototype != null && delta == 0) {
347                return prototype;
348            }
349            return new RemoteRepository(this);
350        }
351
352        private <T> void delta(int flag, T builder, T prototype) {
353            boolean equal = Objects.equals(builder, prototype);
354            if (equal) {
355                delta &= ~flag;
356            } else {
357                delta |= flag;
358            }
359        }
360
361        /**
362         * Sets the identifier of the repository.
363         *
364         * @param id The identifier of the repository, may be {@code null}.
365         * @return This builder for chaining, never {@code null}.
366         */
367        public Builder setId(String id) {
368            this.id = (id != null) ? id : "";
369            if (prototype != null) {
370                delta(ID, this.id, prototype.getId());
371            }
372            return this;
373        }
374
375        /**
376         * Sets the type of the repository, e.g. "default".
377         *
378         * @param type The type of the repository, may be {@code null}.
379         * @return This builder for chaining, never {@code null}.
380         */
381        public Builder setContentType(String type) {
382            this.type = (type != null) ? type : "";
383            if (prototype != null) {
384                delta(TYPE, this.type, prototype.getContentType());
385            }
386            return this;
387        }
388
389        /**
390         * Sets the (base) URL of the repository.
391         *
392         * @param url The URL of the repository, may be {@code null}.
393         * @return This builder for chaining, never {@code null}.
394         */
395        public Builder setUrl(String url) {
396            this.url = (url != null) ? url : "";
397            if (prototype != null) {
398                delta(URL, this.url, prototype.getUrl());
399            }
400            return this;
401        }
402
403        /**
404         * Sets the policy to apply for snapshot and release artifacts.
405         *
406         * @param policy The repository policy to set, may be {@code null} to use a default policy.
407         * @return This builder for chaining, never {@code null}.
408         */
409        public Builder setPolicy(RepositoryPolicy policy) {
410            this.releasePolicy = (policy != null) ? policy : DEFAULT_POLICY;
411            this.snapshotPolicy = (policy != null) ? policy : DEFAULT_POLICY;
412            if (prototype != null) {
413                delta(RELEASES, this.releasePolicy, prototype.getPolicy(false));
414                delta(SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy(true));
415            }
416            return this;
417        }
418
419        /**
420         * Sets the policy to apply for release artifacts.
421         *
422         * @param releasePolicy The repository policy to set, may be {@code null} to use a default policy.
423         * @return This builder for chaining, never {@code null}.
424         */
425        public Builder setReleasePolicy(RepositoryPolicy releasePolicy) {
426            this.releasePolicy = (releasePolicy != null) ? releasePolicy : DEFAULT_POLICY;
427            if (prototype != null) {
428                delta(RELEASES, this.releasePolicy, prototype.getPolicy(false));
429            }
430            return this;
431        }
432
433        /**
434         * Sets the policy to apply for snapshot artifacts.
435         *
436         * @param snapshotPolicy The repository policy to set, may be {@code null} to use a default policy.
437         * @return This builder for chaining, never {@code null}.
438         */
439        public Builder setSnapshotPolicy(RepositoryPolicy snapshotPolicy) {
440            this.snapshotPolicy = (snapshotPolicy != null) ? snapshotPolicy : DEFAULT_POLICY;
441            if (prototype != null) {
442                delta(SNAPSHOTS, this.snapshotPolicy, prototype.getPolicy(true));
443            }
444            return this;
445        }
446
447        /**
448         * Sets the proxy to use in order to access the repository.
449         *
450         * @param proxy The proxy to use, may be {@code null}.
451         * @return This builder for chaining, never {@code null}.
452         */
453        public Builder setProxy(Proxy proxy) {
454            this.proxy = proxy;
455            if (prototype != null) {
456                delta(PROXY, this.proxy, prototype.getProxy());
457            }
458            return this;
459        }
460
461        /**
462         * Sets the authentication to use in order to access the repository.
463         *
464         * @param authentication The authentication to use, may be {@code null}.
465         * @return This builder for chaining, never {@code null}.
466         */
467        public Builder setAuthentication(Authentication authentication) {
468            this.authentication = authentication;
469            if (prototype != null) {
470                delta(AUTH, this.authentication, prototype.getAuthentication());
471            }
472            return this;
473        }
474
475        /**
476         * Sets the repositories being mirrored by the repository.
477         *
478         * @param mirroredRepositories The repositories being mirrored by the repository, may be {@code null}.
479         * @return This builder for chaining, never {@code null}.
480         */
481        public Builder setMirroredRepositories(List<RemoteRepository> mirroredRepositories) {
482            if (this.mirroredRepositories == null) {
483                this.mirroredRepositories = new ArrayList<>();
484            } else {
485                this.mirroredRepositories.clear();
486            }
487            if (mirroredRepositories != null) {
488                this.mirroredRepositories.addAll(mirroredRepositories);
489            }
490            if (prototype != null) {
491                delta(MIRRORED, this.mirroredRepositories, prototype.getMirroredRepositories());
492            }
493            return this;
494        }
495
496        /**
497         * Adds the specified repository to the list of repositories being mirrored by the repository. If this builder
498         * was {@link Builder constructed from a prototype}, the given repository
499         * will be added to the list of mirrored repositories from the prototype.
500         *
501         * @param mirroredRepository The repository being mirrored by the repository, may be {@code null}.
502         * @return This builder for chaining, never {@code null}.
503         */
504        public Builder addMirroredRepository(RemoteRepository mirroredRepository) {
505            if (mirroredRepository != null) {
506                if (this.mirroredRepositories == null) {
507                    this.mirroredRepositories = new ArrayList<>();
508                    if (prototype != null) {
509                        mirroredRepositories.addAll(prototype.getMirroredRepositories());
510                    }
511                }
512                mirroredRepositories.add(mirroredRepository);
513                if (prototype != null) {
514                    delta |= MIRRORED;
515                }
516            }
517            return this;
518        }
519
520        /**
521         * Marks the repository as a repository manager or not.
522         *
523         * @param repositoryManager {@code true} if the repository points at a repository manager, {@code false} if the
524         *            repository is just serving static contents.
525         * @return This builder for chaining, never {@code null}.
526         */
527        public Builder setRepositoryManager(boolean repositoryManager) {
528            this.repositoryManager = repositoryManager;
529            if (prototype != null) {
530                delta(REPOMAN, this.repositoryManager, prototype.isRepositoryManager());
531            }
532            return this;
533        }
534
535        /**
536         * Marks the repository as blocked or not.
537         *
538         * @param blocked {@code true} if the repository should not be allowed to perform any requests.
539         * @return This builder for chaining, never {@code null}.
540         */
541        public Builder setBlocked(boolean blocked) {
542            this.blocked = blocked;
543            if (prototype != null) {
544                delta(BLOCKED, this.blocked, prototype.isBlocked());
545            }
546            return this;
547        }
548    }
549}