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.util.repository;
020
021import java.nio.file.Files;
022import java.nio.file.Path;
023import java.util.List;
024
025import org.eclipse.aether.ConfigurationProperties;
026import org.eclipse.aether.RepositorySystemSession;
027import org.eclipse.aether.artifact.Artifact;
028import org.eclipse.aether.metadata.Metadata;
029import org.eclipse.aether.repository.LocalArtifactRegistration;
030import org.eclipse.aether.repository.LocalArtifactRequest;
031import org.eclipse.aether.repository.LocalArtifactResult;
032import org.eclipse.aether.repository.LocalMetadataRegistration;
033import org.eclipse.aether.repository.LocalMetadataRequest;
034import org.eclipse.aether.repository.LocalMetadataResult;
035import org.eclipse.aether.repository.LocalRepository;
036import org.eclipse.aether.repository.LocalRepositoryManager;
037import org.eclipse.aether.repository.RemoteRepository;
038import org.eclipse.aether.util.ConfigUtils;
039
040import static java.util.Objects.requireNonNull;
041import static java.util.stream.Collectors.toList;
042
043/**
044 * A local repository manager that chains multiple local repository managers: it directs all the write operations
045 * to chain head, while uses tail for {@link #find(RepositorySystemSession, LocalArtifactRequest)} and
046 * {@link #find(RepositorySystemSession, LocalMetadataRequest)} methods only. Hence, tail is used in resolving
047 * metadata and artifacts with or without (configurable) artifact availability tracking.
048 * <p>
049 * Implementation represents itself using the head local repository manager.
050 *
051 * @since 1.9.2
052 */
053public final class ChainedLocalRepositoryManager implements LocalRepositoryManager {
054    private static final String CONFIG_PROPS_PREFIX = ConfigurationProperties.PREFIX_AETHER + "chainedLocalRepository.";
055
056    /**
057     * When using chained local repository, should be the artifact availability ignored in tail.
058     *
059     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
060     * @configurationType {@link java.lang.Boolean}
061     * @configurationDefaultValue {@link #DEFAULT_IGNORE_TAIL_AVAILABILITY}
062     */
063    public static final String CONFIG_PROP_IGNORE_TAIL_AVAILABILITY = CONFIG_PROPS_PREFIX + "ignoreTailAvailability";
064
065    public static final boolean DEFAULT_IGNORE_TAIL_AVAILABILITY = true;
066
067    private final LocalRepositoryManager head;
068
069    private final List<LocalRepositoryManager> tail;
070
071    private final boolean ignoreTailAvailability;
072
073    private final int installTarget;
074
075    private final int cacheTarget;
076
077    public ChainedLocalRepositoryManager(
078            LocalRepositoryManager head, List<LocalRepositoryManager> tail, boolean ignoreTailAvailability) {
079        this(head, tail, ignoreTailAvailability, 0, 0);
080    }
081
082    public ChainedLocalRepositoryManager(
083            LocalRepositoryManager head, List<LocalRepositoryManager> tail, RepositorySystemSession session) {
084        this(
085                head,
086                tail,
087                ConfigUtils.getBoolean(session, DEFAULT_IGNORE_TAIL_AVAILABILITY, CONFIG_PROP_IGNORE_TAIL_AVAILABILITY),
088                0,
089                0);
090    }
091
092    /**
093     * Warning: this is experimental feature of chained, is not recommended to be used/integrated into plain Maven.
094     *
095     * @param head The head LRM
096     * @param tail The tail LRMs
097     * @param ignoreTailAvailability Whether tail availability should be ignored (usually you do want this)
098     * @param installTarget The installation LRM index, integer from 0 to size of tail.
099     * @param cacheTarget The cache LRM index, integer from 0 to size of tail.
100     * @since 2.0.5
101     */
102    public ChainedLocalRepositoryManager(
103            LocalRepositoryManager head,
104            List<LocalRepositoryManager> tail,
105            boolean ignoreTailAvailability,
106            int installTarget,
107            int cacheTarget) {
108        this.head = requireNonNull(head, "head cannot be null");
109        this.tail = requireNonNull(tail, "tail cannot be null");
110        this.ignoreTailAvailability = ignoreTailAvailability;
111        if (installTarget < 0 || installTarget > tail.size()) {
112            throw new IllegalArgumentException("Illegal installTarget value");
113        }
114        this.installTarget = installTarget;
115        if (cacheTarget < 0 || cacheTarget > tail.size()) {
116            throw new IllegalArgumentException("Illegal cacheTarget value");
117        }
118        this.cacheTarget = cacheTarget;
119    }
120
121    @Override
122    public LocalRepository getRepository() {
123        return head.getRepository();
124    }
125
126    private LocalRepositoryManager getInstallTarget() {
127        if (installTarget == 0) {
128            return head;
129        } else {
130            return tail.get(installTarget - 1);
131        }
132    }
133
134    private LocalRepositoryManager getCacheTarget() {
135        if (cacheTarget == 0) {
136            return head;
137        } else {
138            return tail.get(cacheTarget - 1);
139        }
140    }
141
142    @Override
143    public Path getAbsolutePathForLocalArtifact(Artifact artifact) {
144        return getInstallTarget().getAbsolutePathForLocalArtifact(artifact);
145    }
146
147    @Override
148    public Path getAbsolutePathForRemoteArtifact(Artifact artifact, RemoteRepository repository, String context) {
149        return getCacheTarget().getAbsolutePathForRemoteArtifact(artifact, repository, context);
150    }
151
152    @Override
153    public Path getAbsolutePathForLocalMetadata(Metadata metadata) {
154        return getInstallTarget().getAbsolutePathForLocalMetadata(metadata);
155    }
156
157    @Override
158    public Path getAbsolutePathForRemoteMetadata(Metadata metadata, RemoteRepository repository, String context) {
159        return getCacheTarget().getAbsolutePathForRemoteMetadata(metadata, repository, context);
160    }
161
162    @Override
163    public String getPathForLocalArtifact(Artifact artifact) {
164        return getInstallTarget().getPathForLocalArtifact(artifact);
165    }
166
167    @Override
168    public String getPathForRemoteArtifact(Artifact artifact, RemoteRepository repository, String context) {
169        return getCacheTarget().getPathForRemoteArtifact(artifact, repository, context);
170    }
171
172    @Override
173    public String getPathForLocalMetadata(Metadata metadata) {
174        return getInstallTarget().getPathForLocalMetadata(metadata);
175    }
176
177    @Override
178    public String getPathForRemoteMetadata(Metadata metadata, RemoteRepository repository, String context) {
179        return getCacheTarget().getPathForRemoteMetadata(metadata, repository, context);
180    }
181
182    @Override
183    public LocalArtifactResult find(RepositorySystemSession session, LocalArtifactRequest request) {
184        LocalArtifactResult result = head.find(session, request);
185        if (result.isAvailable()) {
186            return result;
187        }
188
189        for (LocalRepositoryManager lrm : tail) {
190            result = lrm.find(session, request);
191            if (result.getPath() != null) {
192                if (ignoreTailAvailability) {
193                    result.setAvailable(true);
194                    return result;
195                } else if (result.isAvailable()) {
196                    return result;
197                }
198            }
199        }
200        return new LocalArtifactResult(request);
201    }
202
203    @Override
204    public void add(RepositorySystemSession session, LocalArtifactRegistration request) {
205        String artifactPath;
206        LocalRepositoryManager target;
207        if (request.getRepository() != null) {
208            artifactPath = getPathForRemoteArtifact(request.getArtifact(), request.getRepository(), "check");
209            target = getCacheTarget();
210        } else {
211            artifactPath = getPathForLocalArtifact(request.getArtifact());
212            target = getInstallTarget();
213        }
214        Path file = target.getRepository().getBasePath().resolve(artifactPath);
215        if (Files.isRegularFile(file)) {
216            target.add(session, request);
217        }
218    }
219
220    @Override
221    public LocalMetadataResult find(RepositorySystemSession session, LocalMetadataRequest request) {
222        LocalMetadataResult result = head.find(session, request);
223        if (result.getPath() != null) {
224            return result;
225        }
226
227        for (LocalRepositoryManager lrm : tail) {
228            result = lrm.find(session, request);
229            if (result.getPath() != null) {
230                return result;
231            }
232        }
233        return new LocalMetadataResult(request);
234    }
235
236    @Override
237    public void add(RepositorySystemSession session, LocalMetadataRegistration request) {
238        String metadataPath;
239        LocalRepositoryManager target;
240        if (request.getRepository() != null) {
241            metadataPath = getPathForRemoteMetadata(request.getMetadata(), request.getRepository(), "check");
242            target = getCacheTarget();
243        } else {
244            metadataPath = getPathForLocalMetadata(request.getMetadata());
245            target = getInstallTarget();
246        }
247
248        Path file = target.getRepository().getBasePath().resolve(metadataPath);
249        if (Files.isRegularFile(file)) {
250            target.add(session, request);
251        }
252    }
253
254    @Override
255    public String toString() {
256        return head.getRepository().toString()
257                + tail.stream().map(LocalRepositoryManager::getRepository).collect(toList());
258    }
259}