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.synccontext.named;
020
021import java.util.Collection;
022import java.util.Comparator;
023import java.util.TreeSet;
024
025import org.eclipse.aether.RepositorySystemSession;
026import org.eclipse.aether.artifact.Artifact;
027import org.eclipse.aether.metadata.Metadata;
028import org.eclipse.aether.named.NamedLockKey;
029import org.eclipse.aether.util.PathUtils;
030import org.eclipse.aether.util.artifact.ArtifactIdUtils;
031
032import static java.util.Objects.requireNonNull;
033
034/**
035 * Artifact GAV {@link NameMapper}, uses artifact and metadata coordinates to name their corresponding locks. Is not
036 * considering local repository, only the artifact coordinates. May use custom prefixes and suffixes and separators,
037 * hence this instance may or may not be filesystem friendly (depends on strings used).
038 * <p>
039 * Note: in earlier Resolver 1.9.x versions this mapper was the default, but it changed to {@link GAECVNameMapper}
040 * in 1.9.25.
041 */
042public class GAVNameMapper implements NameMapper {
043    protected final boolean fileSystemFriendly;
044
045    protected final String artifactPrefix;
046
047    protected final String artifactSuffix;
048
049    protected final String metadataPrefix;
050
051    protected final String metadataSuffix;
052
053    protected final String fieldSeparator;
054
055    public GAVNameMapper(
056            boolean fileSystemFriendly,
057            String artifactPrefix,
058            String artifactSuffix,
059            String metadataPrefix,
060            String metadataSuffix,
061            String fieldSeparator) {
062        this.fileSystemFriendly = fileSystemFriendly;
063        this.artifactPrefix = requireNonNull(artifactPrefix);
064        this.artifactSuffix = requireNonNull(artifactSuffix);
065        this.metadataPrefix = requireNonNull(metadataPrefix);
066        this.metadataSuffix = requireNonNull(metadataSuffix);
067        this.fieldSeparator = requireNonNull(fieldSeparator);
068    }
069
070    @Override
071    public boolean isFileSystemFriendly() {
072        return fileSystemFriendly;
073    }
074
075    @Override
076    public Collection<NamedLockKey> nameLocks(
077            final RepositorySystemSession session,
078            final Collection<? extends Artifact> artifacts,
079            final Collection<? extends Metadata> metadatas) {
080        // Deadlock prevention: https://stackoverflow.com/a/16780988/696632
081        // We must acquire multiple locks always in the same order!
082        TreeSet<NamedLockKey> keys = new TreeSet<>(Comparator.comparing(NamedLockKey::name));
083        if (artifacts != null) {
084            for (Artifact artifact : artifacts) {
085                keys.add(NamedLockKey.of(getArtifactName(artifact), ArtifactIdUtils.toBaseId(artifact)));
086            }
087        }
088
089        if (metadatas != null) {
090            for (Metadata metadata : metadatas) {
091                keys.add(NamedLockKey.of(getMetadataName(metadata), toMetadataId(metadata)));
092            }
093        }
094        return keys;
095    }
096
097    protected String getArtifactName(Artifact artifact) {
098        return artifactPrefix
099                + fieldToSegment(artifact.getGroupId())
100                + fieldSeparator
101                + fieldToSegment(artifact.getArtifactId())
102                + fieldSeparator
103                + fieldToSegment(artifact.getBaseVersion())
104                + artifactSuffix;
105    }
106
107    /**
108     * Returns the given coordinate field in a form that is safe to use as (part of) a file name: when this
109     * mapper is {@link #isFileSystemFriendly()}, characters that act as path separators or are otherwise
110     * illegal in file names are replaced via {@link PathUtils#stringToPathSegment(String)}. File-system
111     * friendly names are resolved against the locks base directory by {@link BasedirNameMapper}, so every
112     * field must be a valid path segment.
113     *
114     * @since 2.0.23
115     */
116    protected String fieldToSegment(String field) {
117        return fileSystemFriendly ? PathUtils.stringToPathSegment(field) : field;
118    }
119
120    protected static final String MAVEN_METADATA = "maven-metadata.xml";
121
122    protected String getMetadataName(Metadata metadata) {
123        String name = metadataPrefix;
124        if (!metadata.getGroupId().isEmpty()) {
125            name += fieldToSegment(metadata.getGroupId());
126            if (!metadata.getArtifactId().isEmpty()) {
127                name += fieldSeparator + fieldToSegment(metadata.getArtifactId());
128                if (!metadata.getVersion().isEmpty()) {
129                    name += fieldSeparator + fieldToSegment(metadata.getVersion());
130                }
131            }
132            if (!MAVEN_METADATA.equals(metadata.getType())) {
133                name += fieldSeparator + fieldToSegment(metadata.getType());
134            }
135        } else {
136            if (!MAVEN_METADATA.equals(metadata.getType())) {
137                name += fieldToSegment(metadata.getType());
138            }
139        }
140        return name + metadataSuffix;
141    }
142
143    protected String toMetadataId(Metadata metadata) {
144        String name = "";
145        if (!metadata.getGroupId().isEmpty()) {
146            name += metadata.getGroupId();
147            if (!metadata.getArtifactId().isEmpty()) {
148                name += ":" + metadata.getArtifactId();
149                if (!metadata.getVersion().isEmpty()) {
150                    name += ":" + metadata.getVersion();
151                }
152            }
153        }
154        if (!metadata.getType().isEmpty()) {
155            name += (name.isEmpty() ? "" : ":") + metadata.getType();
156        }
157        return name;
158    }
159
160    /**
161     * @deprecated Use {@link NameMappers} to create name mappers instead.
162     */
163    @Deprecated
164    public static NameMapper gav() {
165        return new GAVNameMapper(false, "artifact:", "", "metadata:", "", ":");
166    }
167
168    /**
169     * @deprecated Use {@link NameMappers} to create name mappers instead.
170     */
171    @Deprecated
172    public static NameMapper fileGav() {
173        return new GAVNameMapper(true, "artifact~", ".lock", "metadata~", ".lock", "~");
174    }
175}