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///
031/// It provides common functionality for managing repository credentials,
032/// supporting fallbacks to build context properties and Maven's `settings.xml`,
033/// and defining which [PublicationType] (SNAPSHOT or RELEASE) the destination
034/// accepts.
035///
036public abstract class MvnPublishingDestination {
037
038    private static final FluentLogger logger = FluentLogger.forEnclosingClass();
039    private final Set<PublicationType> acceptedTypes;
040    private String repoUser;
041    private String repoPass;
042    private String id;
043
044    /// The versions of artifacts that can be published to this destination.
045    ///
046    public enum PublicationType {
047        /// The snapshot type.
048        SNAPSHOT,
049        /// The release type.
050        RELEASE
051    }
052
053    /// Initializes a new Maven publishing destination.
054    ///
055    /// @param publicationTypes the accepted publication types
056    ///
057    public MvnPublishingDestination(PublicationType... publicationTypes) {
058        acceptedTypes = EnumSet.copyOf(Arrays.asList(publicationTypes));
059    }
060
061    /// Checks if the given publication type is accepted.
062    ///
063    /// @param type the type
064    /// @return true, if successful
065    ///
066    public boolean accepts(PublicationType type) {
067        return acceptedTypes.contains(type);
068    }
069
070    /// Sets the id.
071    ///
072    /// @param id the new id
073    /// @return this destination
074    ///
075    @SuppressWarnings("PMD.ShortMethodName")
076    public MvnPublishingDestination id(String id) {
077        this.id = id;
078        return this;
079    }
080
081    ///
082    /// Returns the id.
083    /// 
084    /// @return the id
085    /// 
086    @SuppressWarnings("PMD.ShortMethodName")
087    public String id() {
088        return id;
089    }
090
091    /// Sets the Maven repository credentials.
092    ///
093    /// @param user the user name
094    /// @param pass the password
095    /// @return this destination
096    ///
097    public MvnPublishingDestination credentials(String user, String pass) {
098        logger.atConfig().log("Using explicitly set credentials for %s", this);
099        this.repoUser = user;
100        this.repoPass = pass;
101        return this;
102    }
103
104    /// Returns the repository user set by [credentials] or a fallback.
105    /// 
106    /// The fallback order is:
107    /// 
108    /// 1. Look for properties `mvnrepo.user` and `mvnrepo.password`
109    ///    in the properties provided by the [BuildContext].
110    /// 
111    /// 2. If an id is set, look for the user and password in the
112    ///    `servers` section with this id in the Maven `settings.xml`.
113    ///
114    /// @param context the context
115    /// @return the user
116    ///
117    protected String repositoryUser(BuildContext context) {
118        fillInCredentials(context);
119        return repoUser;
120    }
121
122    /// Returns the repository password set by [credentials] or a fallback.
123    /// See [repositoryUser] for the fallback logic.
124    ///
125    /// @param context the context
126    /// @return the password
127    ///
128    protected String repositoryPassword(BuildContext context) {
129        fillInCredentials(context);
130        return repoPass;
131    }
132
133    @SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel")
134    private synchronized void fillInCredentials(BuildContext context) {
135        if (repoUser != null) {
136            return;
137        }
138
139        // Try properties
140        var user = context.property("mvnrepo.user", null);
141        if (user != null) {
142            logger.atConfig().log(
143                "Using credentials from properties for %s", this);
144            repoUser = user;
145            repoPass = context.property("mvnrepo.password", null);
146            return;
147        }
148
149        // Try settings
150        if (id != null
151            && MvnRepoLookup.mavenContext().settings().getServers().stream()
152                .filter(s -> id.equals(s.getId())).findFirst().map(s -> {
153                    logger.atConfig().log(
154                        "Using credentials from settings for %s", this);
155                    repoUser = s.getUsername();
156                    repoPass = s.getPassword();
157                    return true;
158                }).orElse(false)) {
159            return;
160        }
161
162        // Fallback
163        repoUser = "";
164        repoPass = "";
165    }
166
167    /* default */ abstract void publish(BuildContext context,
168            MvnPublisher publisher, Artifact mainArtifact,
169            List<Artifact> toDeploy);
170}