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.internal.impl.checksum;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023import javax.inject.Singleton;
024
025import java.io.IOException;
026import java.io.UncheckedIOException;
027import java.nio.file.Files;
028import java.nio.file.Path;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.function.Function;
033
034import org.eclipse.aether.RepositorySystemSession;
035import org.eclipse.aether.artifact.Artifact;
036import org.eclipse.aether.internal.impl.LocalPathComposer;
037import org.eclipse.aether.repository.ArtifactRepository;
038import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
039import org.eclipse.aether.spi.io.ChecksumProcessor;
040import org.eclipse.aether.util.ConfigUtils;
041import org.eclipse.aether.util.repository.RepositoryIdHelper;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045import static java.util.Objects.requireNonNull;
046
047/**
048 * Sparse file {@link FileTrustedChecksumsSourceSupport} implementation that use specified directory as base
049 * directory, where it expects artifacts checksums on standard Maven2 "local" layout. This implementation uses Artifact
050 * coordinates solely to form path from basedir, pretty much as Maven local repository does.
051 * <p>
052 * The source by default is "origin aware", it will factor in origin repository ID as well into base directory name
053 * (for example ".checksums/central/...").
054 * <p>
055 * The checksums files are directly loaded from disk, so in-flight file changes during lifecycle of session are picked
056 * up. This implementation can be simultaneously used to lookup and also write checksums. The written checksums
057 * will become visible across all sessions right after the moment they were written.
058 * <p>
059 * The name of this implementation is "sparseDirectory".
060 *
061 * @see LocalPathComposer
062 * @since 1.9.0
063 */
064@Singleton
065@Named(SparseDirectoryTrustedChecksumsSource.NAME)
066public final class SparseDirectoryTrustedChecksumsSource extends FileTrustedChecksumsSourceSupport {
067    public static final String NAME = "sparseDirectory";
068
069    private static final String CONFIG_PROPS_PREFIX =
070            FileTrustedChecksumsSourceSupport.CONFIG_PROPS_PREFIX + NAME + ".";
071
072    /**
073     * Is checksum source enabled?
074     *
075     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
076     * @configurationType {@link java.lang.Boolean}
077     * @configurationDefaultValue false
078     */
079    public static final String CONFIG_PROP_ENABLED = FileTrustedChecksumsSourceSupport.CONFIG_PROPS_PREFIX + NAME;
080
081    /**
082     * The basedir where checksums are. If relative, is resolved from local repository root.
083     *
084     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
085     * @configurationType {@link java.lang.String}
086     * @configurationDefaultValue {@link #LOCAL_REPO_PREFIX_DIR}
087     */
088    public static final String CONFIG_PROP_BASEDIR = CONFIG_PROPS_PREFIX + "basedir";
089
090    public static final String LOCAL_REPO_PREFIX_DIR = ".checksums";
091
092    /**
093     * Is source origin aware?
094     *
095     * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
096     * @configurationType {@link java.lang.Boolean}
097     * @configurationDefaultValue true
098     */
099    public static final String CONFIG_PROP_ORIGIN_AWARE = CONFIG_PROPS_PREFIX + "originAware";
100
101    private static final Logger LOGGER = LoggerFactory.getLogger(SparseDirectoryTrustedChecksumsSource.class);
102
103    private final ChecksumProcessor checksumProcessor;
104
105    private final LocalPathComposer localPathComposer;
106
107    @Inject
108    public SparseDirectoryTrustedChecksumsSource(
109            ChecksumProcessor checksumProcessor, LocalPathComposer localPathComposer) {
110        this.checksumProcessor = requireNonNull(checksumProcessor);
111        this.localPathComposer = requireNonNull(localPathComposer);
112    }
113
114    @Override
115    protected boolean isEnabled(RepositorySystemSession session) {
116        return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED);
117    }
118
119    private boolean isOriginAware(RepositorySystemSession session) {
120        return ConfigUtils.getBoolean(session, true, CONFIG_PROP_ORIGIN_AWARE);
121    }
122
123    @Override
124    protected Map<String, String> doGetTrustedArtifactChecksums(
125            RepositorySystemSession session,
126            Artifact artifact,
127            ArtifactRepository artifactRepository,
128            List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) {
129        final boolean originAware = isOriginAware(session);
130        final HashMap<String, String> checksums = new HashMap<>();
131        Path basedir = getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, false);
132        if (Files.isDirectory(basedir)) {
133            for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
134                Path checksumPath = basedir.resolve(calculateArtifactPath(
135                        originAware,
136                        artifact,
137                        RepositoryIdHelper.cachedIdToPathSegment(session).apply(artifactRepository),
138                        checksumAlgorithmFactory));
139
140                if (!Files.isRegularFile(checksumPath)) {
141                    LOGGER.debug(
142                            "Artifact '{}' trusted checksum '{}' not found on path '{}'",
143                            artifact,
144                            checksumAlgorithmFactory.getName(),
145                            checksumPath);
146                    continue;
147                }
148
149                try {
150                    String checksum = checksumProcessor.readChecksum(checksumPath);
151                    if (checksum != null) {
152                        checksums.put(checksumAlgorithmFactory.getName(), checksum);
153                    }
154                } catch (IOException e) {
155                    // unexpected, log
156                    LOGGER.warn(
157                            "Could not read artifact '{}' trusted checksum on path '{}'", artifact, checksumPath, e);
158                    throw new UncheckedIOException(e);
159                }
160            }
161        }
162        return checksums;
163    }
164
165    @Override
166    protected Writer doGetTrustedArtifactChecksumsWriter(RepositorySystemSession session) {
167        return new SparseDirectoryWriter(
168                getBasedir(session, LOCAL_REPO_PREFIX_DIR, CONFIG_PROP_BASEDIR, true),
169                isOriginAware(session),
170                RepositoryIdHelper.cachedIdToPathSegment(session));
171    }
172
173    private String calculateArtifactPath(
174            boolean originAware,
175            Artifact artifact,
176            String safeRepositoryId,
177            ChecksumAlgorithmFactory checksumAlgorithmFactory) {
178        String path = localPathComposer.getPathForArtifact(artifact, false) + "."
179                + checksumAlgorithmFactory.getFileExtension();
180        if (originAware) {
181            path = safeRepositoryId + "/" + path;
182        }
183        return path;
184    }
185
186    private class SparseDirectoryWriter implements Writer {
187        private final Path basedir;
188
189        private final boolean originAware;
190
191        private final Function<ArtifactRepository, String> idToPathSegmentFunction;
192
193        private SparseDirectoryWriter(
194                Path basedir, boolean originAware, Function<ArtifactRepository, String> idToPathSegmentFunction) {
195            this.basedir = basedir;
196            this.originAware = originAware;
197            this.idToPathSegmentFunction = idToPathSegmentFunction;
198        }
199
200        @Override
201        public void addTrustedArtifactChecksums(
202                Artifact artifact,
203                ArtifactRepository artifactRepository,
204                List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
205                Map<String, String> trustedArtifactChecksums)
206                throws IOException {
207            for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
208                Path checksumPath = basedir.resolve(calculateArtifactPath(
209                        originAware,
210                        artifact,
211                        idToPathSegmentFunction.apply(artifactRepository),
212                        checksumAlgorithmFactory));
213                String checksum = requireNonNull(trustedArtifactChecksums.get(checksumAlgorithmFactory.getName()));
214                checksumProcessor.writeChecksum(checksumPath, checksum);
215            }
216        }
217    }
218}