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.net.URI; 023import java.util.List; 024import java.util.concurrent.atomic.AtomicBoolean; 025import java.util.concurrent.atomic.AtomicInteger; 026import org.eclipse.aether.AbstractRepositoryListener; 027import org.eclipse.aether.DefaultRepositorySystemSession; 028import org.eclipse.aether.RepositoryEvent; 029import org.eclipse.aether.artifact.Artifact; 030import org.eclipse.aether.deployment.DeployRequest; 031import org.eclipse.aether.deployment.DeploymentException; 032import org.eclipse.aether.repository.RemoteRepository; 033import org.eclipse.aether.util.repository.AuthenticationBuilder; 034import org.jdrupes.builder.api.BuildContext; 035import org.jdrupes.builder.api.BuildException; 036 037/// A Maven publishing destination that uploads artifacts using the traditional 038/// Maven deployment approach. 039/// 040/// This implementation of a [MvnPublishingDestination] uploads artifacts 041/// and their associated metadata (like `maven-metadata.xml`) individually 042/// to the specified repository URI. It is typically used for publishing 043/// snapshots or deploying to internal Maven repositories. 044/// 045public class MvnDeployDestination extends MvnPublishingDestination { 046 047 private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 048 private String id; 049 private URI repositoryUri; 050 051 /// Initializes a new Maven deploy destination. 052 /// 053 /// @param publicationTypes the supported publication types 054 /// 055 public MvnDeployDestination(MvnVersionType... publicationTypes) { 056 super(publicationTypes); 057 if (publicationTypes.length == 1 058 && publicationTypes[0] == MvnVersionType.SNAPSHOT) { 059 repositoryUri = URI.create( 060 "https://central.sonatype.com/repository/maven-snapshots/"); 061 } 062 } 063 064 /// Sets the Maven repository URI. 065 /// 066 /// @param uri the repository URI 067 /// @return this destination 068 /// 069 public MvnDeployDestination repositoryUri(URI uri) { 070 this.repositoryUri = uri; 071 return this; 072 } 073 074 /// Returns the repository URI. Defaults to 075 /// `https://central.sonatype.com/repository/maven-snapshots/` if 076 /// this destination was created specifically for publication type 077 /// `SNAPSHOT`. 078 /// 079 /// @return the uri 080 /// 081 public URI repositoryUri() { 082 return repositoryUri; 083 } 084 085 @Override 086 /* default */void publish(BuildContext context, MvnPublisher publisher, 087 Artifact mainArtifact, List<Artifact> toDeploy) { 088 // Now deploy everything 089 var session = new DefaultRepositorySystemSession( 090 MavenContext.repositorySession()); 091 session.setRepositoryListener(new UploadListener( 092 context, mainArtifact.getGroupId() + ":" 093 + mainArtifact.getArtifactId() 094 + ":" + mainArtifact.getVersion(), 095 toDeploy.size())); 096 var user = repositoryUser(context); 097 var password = repositoryPassword(context); 098 var repo = new RemoteRepository.Builder("deploy", "default", 099 repositoryUri.toString()) 100 .setAuthentication(new AuthenticationBuilder() 101 .addUsername(user).addPassword(password).build()) 102 .build(); 103 var deployReq = new DeployRequest().setRepository(repo); 104 toDeploy.stream().forEach(deployReq::addArtifact); 105 try { 106 MavenContext.repositorySystem().deploy(session, deployReq); 107 } catch (DeploymentException e) { 108 throw new BuildException().from(publisher).cause(e); 109 } 110 } 111 112 @SuppressWarnings("PMD.CommentRequired") 113 private final class UploadListener extends AbstractRepositoryListener { 114 private final AtomicBoolean startMsgLogged = new AtomicBoolean(false); 115 private final AtomicInteger deployedCount = new AtomicInteger(0); 116 private final String artifact; 117 private final BuildContext context; 118 private final int artifacts; 119 120 @SuppressWarnings("PMD.PublicMemberInNonPublicType") 121 public UploadListener(BuildContext context, String artifact, 122 int artifacts) { 123 this.context = context; 124 this.artifact = artifact; 125 this.artifacts = artifacts; 126 } 127 128 @Override 129 public void artifactDeploying(RepositoryEvent event) { 130 if (!startMsgLogged.getAndSet(true)) { 131 logger.atInfo().log("Start deploying artifacts..."); 132 context.statusLine().update( 133 "%s deploys to %s", this, repositoryUri); 134 } 135 } 136 137 @Override 138 public void artifactDeployed(RepositoryEvent event) { 139 if (!"jar".equals(event.getArtifact().getExtension())) { 140 return; 141 } 142 logger.atInfo().log("Deployed: %s", event.getArtifact()); 143 context.statusLine().update("%s deployed %d/%d", this, 144 deployedCount.incrementAndGet(), artifacts); 145 } 146 147 @Override 148 public void metadataDeployed(RepositoryEvent event) { 149 logger.atInfo().log("Deployed: %s", event.getMetadata()); 150 context.statusLine().update("%s deployed %d/%d", this, 151 deployedCount.incrementAndGet(), artifacts); 152 } 153 154 @Override 155 public String toString() { 156 return "Maven deployer for " + artifact; 157 } 158 159 } 160 161 @Override 162 public String toString() { 163 return "Maven deploy destination " + (id != null ? (id + "::") 164 : "") + repositoryUri; 165 } 166}