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