001package org.eclipse.aether.internal.impl;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.eclipse.aether.RepositorySystemSession;
023import org.eclipse.aether.spi.connector.ArtifactDownload;
024import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
025import org.eclipse.aether.spi.connector.checksum.ProvidedChecksumsSource;
026import org.eclipse.aether.spi.io.FileProcessor;
027import org.eclipse.aether.util.ConfigUtils;
028import org.eclipse.aether.util.artifact.ArtifactIdUtils;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import javax.inject.Inject;
033import javax.inject.Named;
034import javax.inject.Singleton;
035import java.io.IOException;
036import java.net.URI;
037import java.nio.file.Files;
038import java.nio.file.Path;
039import java.nio.file.Paths;
040import java.util.ArrayList;
041import java.util.HashMap;
042import java.util.List;
043import java.util.Map;
044
045import static java.util.Objects.requireNonNull;
046
047/**
048 * Local filesystem backed {@link ProvidedChecksumsSource} implementation that use specified directory as base
049 * directory, where it expects artifacts checksums on standard Maven2 "local" layout. This implementation uses Artifact
050 * (and Metadata) coordinates solely to form path from baseDir (for Metadata file name is
051 * {@code maven-metadata-local.xml.sha1} in case of SHA-1 checksum).
052 *
053 * @since 1.8.0
054 */
055@Singleton
056@Named( FileProvidedChecksumsSource.NAME )
057public final class FileProvidedChecksumsSource
058    implements ProvidedChecksumsSource
059{
060    public static final String NAME = "file";
061
062    static final String CONFIG_PROP_BASE_DIR = "aether.artifactResolver.providedChecksumsSource.file.baseDir";
063
064    static final String LOCAL_REPO_PREFIX = ".checksums";
065
066    private static final Logger LOGGER = LoggerFactory.getLogger( FileProvidedChecksumsSource.class );
067
068    private final FileProcessor fileProcessor;
069
070    private final LocalPathComposer localPathComposer;
071
072    @Inject
073    public FileProvidedChecksumsSource( FileProcessor fileProcessor, LocalPathComposer localPathComposer )
074    {
075        this.fileProcessor = requireNonNull( fileProcessor );
076        this.localPathComposer = requireNonNull( localPathComposer );
077    }
078
079    @Override
080    public Map<String, String> getProvidedArtifactChecksums( RepositorySystemSession session,
081                                                             ArtifactDownload transfer,
082                                                             List<ChecksumAlgorithmFactory> checksumAlgorithmFactories )
083    {
084        Path baseDir = getBaseDir( session );
085        if ( baseDir == null )
086        {
087            return null;
088        }
089        ArrayList<ChecksumFilePath> checksumFilePaths = new ArrayList<>( checksumAlgorithmFactories.size() );
090        for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories )
091        {
092            checksumFilePaths.add( new ChecksumFilePath(
093                    localPathComposer.getPathForArtifact( transfer.getArtifact(), false ) + '.'
094                    + checksumAlgorithmFactory.getFileExtension(), checksumAlgorithmFactory ) );
095        }
096        return getProvidedChecksums( baseDir, checksumFilePaths, ArtifactIdUtils.toId( transfer.getArtifact() ) );
097    }
098
099    /**
100     * May return {@code null}.
101     */
102    private Map<String, String> getProvidedChecksums( Path baseDir,
103                                                      List<ChecksumFilePath> checksumFilePaths,
104                                                      String subjectId )
105    {
106        HashMap<String, String> checksums = new HashMap<>();
107        for ( ChecksumFilePath checksumFilePath : checksumFilePaths )
108        {
109            Path checksumPath =  baseDir.resolve( checksumFilePath.path );
110            if ( Files.isReadable( checksumPath ) )
111            {
112                try
113                {
114                    String checksum = fileProcessor.readChecksum( checksumPath.toFile() );
115                    if ( checksum != null )
116                    {
117                        LOGGER.debug( "Resolved provided checksum '{}:{}' for '{}'",
118                                checksumFilePath.checksumAlgorithmFactory.getName(), checksum, subjectId );
119
120                        checksums.put( checksumFilePath.checksumAlgorithmFactory.getName(), checksum );
121                    }
122                }
123                catch ( IOException e )
124                {
125                    LOGGER.warn( "Could not read provided checksum for '{}' at path '{}'",
126                            subjectId, checksumPath, e );
127                }
128            }
129        }
130        return checksums.isEmpty() ? null : checksums;
131    }
132
133    /**
134     * Returns the base {@link URI} of directory where checksums are laid out, may return {@code null}.
135     */
136    private Path getBaseDir( RepositorySystemSession session )
137    {
138        final String baseDirPath = ConfigUtils.getString( session, null, CONFIG_PROP_BASE_DIR );
139        final Path baseDir;
140        if ( baseDirPath != null )
141        {
142            baseDir = Paths.get( baseDirPath );
143        }
144        else
145        {
146            baseDir = session.getLocalRepository().getBasedir().toPath().resolve( LOCAL_REPO_PREFIX );
147        }
148        if ( !Files.isDirectory( baseDir ) )
149        {
150            return null;
151        }
152        return baseDir;
153    }
154
155    private static final class ChecksumFilePath
156    {
157        private final String path;
158
159        private final ChecksumAlgorithmFactory checksumAlgorithmFactory;
160
161        private ChecksumFilePath( String path, ChecksumAlgorithmFactory checksumAlgorithmFactory )
162        {
163            this.path = path;
164            this.checksumAlgorithmFactory = checksumAlgorithmFactory;
165        }
166    }
167}