001/*
002 * JDrupes Builder
003 * Copyright (C) 2026 Michael N. Lipp
004 * 
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
017 */
018
019package org.jdrupes.builder.mvnrepo;
020
021import com.google.common.flogger.FluentLogger;
022import java.util.Arrays;
023import java.util.EnumSet;
024import java.util.List;
025import java.util.Set;
026import org.eclipse.aether.artifact.Artifact;
027import org.jdrupes.builder.api.BuildContext;
028
029/// The base class for all Maven publishing destinations.
030/// It provides common functionality for managing a destination's accepted
031/// version types (SNAPSHOT, RELEASE) and for managing repository credentials.
032/// 
033/// Credentials for the destination are resolved using the following 
034/// order of precedence:
035///
036/// 1. Explicitly set credentials: Values provided via the [credentials] 
037///    method take the highest priority.
038/// 2. Build context properties: If no explicit credentials are set, the 
039///    publisher looks for `mvnrepo.user` and `mvnrepo.password` within 
040///    the [BuildContext].
041/// 3. Maven `settings.xml`: If an ID is configured for the destination, 
042///    the publisher searches the `servers` section of the Maven 
043///    `settings.xml` for a server matching that ID.
044/// 4. Fallback: If no credentials are found in any of the above sources, 
045///    empty strings are used, which typically results in an anonymous 
046///    upload attempt.
047///
048public abstract class MvnPublishingDestination {
049
050    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
051    private final Set<MvnVersionType> acceptedTypes;
052    private String repoUser;
053    private String repoPass;
054    private String id;
055
056    /// Initializes a new Maven publishing destination.
057    ///
058    /// @param publicationTypes the accepted publication types
059    ///
060    public MvnPublishingDestination(MvnVersionType... publicationTypes) {
061        acceptedTypes = EnumSet.copyOf(Arrays.asList(publicationTypes));
062    }
063
064    /// Checks if the given publication type is accepted.
065    ///
066    /// @param type the type
067    /// @return true, if successful
068    ///
069    public boolean accepts(MvnVersionType type) {
070        return acceptedTypes.contains(type);
071    }
072
073    /// Returns if this destination requires checksum artifacts to
074    /// be passed to [publish].
075    ///
076    /// @return true, if successful
077    ///
078    public boolean requiresChecksumArtifacts() {
079        return false;
080    }
081
082    /// Sets the id.
083    ///
084    /// @param id the new id
085    /// @return this destination
086    ///
087    @SuppressWarnings("PMD.ShortMethodName")
088    public MvnPublishingDestination id(String id) {
089        this.id = id;
090        return this;
091    }
092
093    ///
094    /// Returns the id.
095    /// 
096    /// @return the id
097    /// 
098    @SuppressWarnings("PMD.ShortMethodName")
099    public String id() {
100        return id;
101    }
102
103    /// Sets the Maven repository credentials.
104    ///
105    /// @param user the user name
106    /// @param pass the password
107    /// @return this destination
108    ///
109    public MvnPublishingDestination credentials(String user, String pass) {
110        logger.atConfig().log("Using explicitly set credentials for %s", this);
111        this.repoUser = user;
112        this.repoPass = pass;
113        return this;
114    }
115
116    /// Returns the repository user set by [credentials] or a fallback.
117    /// 
118    /// The fallback order is:
119    /// 
120    /// 1. Look for properties `mvnrepo.user` and `mvnrepo.password`
121    ///    in the properties provided by the [BuildContext].
122    /// 
123    /// 2. If an id is set, look for the user and password in the
124    ///    `servers` section with this id in the Maven `settings.xml`.
125    ///
126    /// @param context the context
127    /// @return the user
128    ///
129    protected String repositoryUser(BuildContext context) {
130        fillInCredentials(context);
131        return repoUser;
132    }
133
134    /// Returns the repository password set by [credentials] or a fallback.
135    /// See [repositoryUser] for the fallback logic.
136    ///
137    /// @param context the context
138    /// @return the password
139    ///
140    protected String repositoryPassword(BuildContext context) {
141        fillInCredentials(context);
142        return repoPass;
143    }
144
145    @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel")
146    private synchronized void fillInCredentials(BuildContext context) {
147        if (repoUser != null) {
148            return;
149        }
150
151        // Try properties
152        var user = context.property("mvnrepo.user", null);
153        if (user != null) {
154            logger.atConfig().log(
155                "Using credentials from properties for %s", this);
156            repoUser = user;
157            repoPass = context.property("mvnrepo.password", null);
158            return;
159        }
160
161        // Try settings
162        if (id != null && MavenContext.lookupCredentials(id, (u, p) -> {
163            logger.atConfig().log(
164                "Using credentials from settings for %s", this);
165            repoUser = u;
166            repoPass = p;
167        })) {
168            return;
169        }
170
171        // Fallback
172        repoUser = "";
173        repoPass = "";
174    }
175
176    /* default */ abstract void publish(BuildContext context,
177            MvnPublisher publisher, Artifact mainArtifact,
178            List<Artifact> toDeploy);
179}