001package org.eclipse.aether.internal.impl.synccontext.named;
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.artifact.Artifact;
024import org.eclipse.aether.metadata.Metadata;
025import org.eclipse.aether.named.support.FileSystemFriendly;
026
027import javax.inject.Named;
028import javax.inject.Singleton;
029
030import java.io.File;
031import java.io.IOException;
032import java.io.UncheckedIOException;
033import java.nio.file.Path;
034import java.util.Collection;
035import java.util.TreeSet;
036import java.util.concurrent.ConcurrentHashMap;
037import java.util.concurrent.ConcurrentMap;
038
039/**
040 * A {@link NameMapper} that creates same name mapping as Takari Local Repository does, with
041 * {@code baseDir} (local repo). Part of code blatantly copies parts of the Takari
042 * {@code LockingSyncContext}.
043 *
044 * @see <a href="https://github.com/takari/takari-local-repository/blob/24133e50a0478dccb5620ac2f2255187608f165b/src/main/java/io/takari/aether/concurrency/LockingSyncContext.java">Takari
045 * LockingSyncContext.java</a>
046 */
047@Singleton
048@Named( FileGAVNameMapper.NAME )
049public class FileGAVNameMapper
050    implements NameMapper, FileSystemFriendly
051{
052    public static final String NAME = "file-gav";
053
054    private static final String LOCK_SUFFIX = ".resolverlock";
055
056    private static final char SEPARATOR = '~';
057
058    private final ConcurrentMap<String, Path> baseDirs;
059
060    public FileGAVNameMapper()
061    {
062        this.baseDirs = new ConcurrentHashMap<>();
063    }
064
065    @Override
066    public TreeSet<String> nameLocks( final RepositorySystemSession session,
067                                      final Collection<? extends Artifact> artifacts,
068                                      final Collection<? extends Metadata> metadatas )
069    {
070        File localRepositoryBasedir = session.getLocalRepository().getBasedir();
071        // here we abuse concurrent hash map to make sure costly getCanonicalFile is invoked only once
072        Path baseDir = baseDirs.computeIfAbsent(
073            localRepositoryBasedir.getPath(), k ->
074            {
075                try
076                {
077                    return new File( localRepositoryBasedir, ".locks" ).getCanonicalFile().toPath();
078                }
079                catch ( IOException e )
080                {
081                    throw new UncheckedIOException( e );
082                }
083            }
084        );
085
086        TreeSet<String> paths = new TreeSet<>();
087        if ( artifacts != null )
088        {
089            for ( Artifact artifact : artifacts )
090            {
091                paths.add( getPath( baseDir, artifact ) + LOCK_SUFFIX );
092            }
093        }
094        if ( metadatas != null )
095        {
096            for ( Metadata metadata : metadatas )
097            {
098                paths.add( getPath( baseDir, metadata ) + LOCK_SUFFIX );
099            }
100        }
101        return paths;
102    }
103
104    private String getPath( final Path baseDir, final Artifact artifact )
105    {
106        // NOTE: Don't use LRM.getPath*() as those paths could be different across processes, e.g. due to staging LRMs.
107        String path = artifact.getGroupId()
108            + SEPARATOR + artifact.getArtifactId()
109            + SEPARATOR + artifact.getBaseVersion();
110        return baseDir.resolve( path ).toAbsolutePath().toString();
111    }
112
113    private String getPath( final Path baseDir, final Metadata metadata )
114    {
115        // NOTE: Don't use LRM.getPath*() as those paths could be different across processes, e.g. due to staging.
116        String path = "";
117        if ( metadata.getGroupId().length() > 0 )
118        {
119            path += metadata.getGroupId();
120            if ( metadata.getArtifactId().length() > 0 )
121            {
122                path += SEPARATOR + metadata.getArtifactId();
123                if ( metadata.getVersion().length() > 0 )
124                {
125                    path += SEPARATOR + metadata.getVersion();
126                }
127            }
128        }
129        return baseDir.resolve( path ).toAbsolutePath().toString();
130    }
131}