• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jreleaser / jreleaser / #527

05 Aug 2025 04:08PM UTC coverage: 49.206% (-0.06%) from 49.267%
#527

push

github

aalmiray
build: Remove mocked test cases

26265 of 53378 relevant lines covered (49.21%)

0.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.46
/sdks/jreleaser-java-sdk-commons/src/main/java/org/jreleaser/sdk/commons/AbstractMavenDeployer.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.sdk.commons;
19

20
import feign.form.FormData;
21
import org.apache.commons.io.IOUtils;
22
import org.jreleaser.bundle.RB;
23
import org.jreleaser.model.JReleaserException;
24
import org.jreleaser.model.api.signing.SigningException;
25
import org.jreleaser.model.internal.JReleaserContext;
26
import org.jreleaser.model.internal.deploy.maven.Maven;
27
import org.jreleaser.model.spi.deploy.DeployException;
28
import org.jreleaser.model.spi.deploy.maven.Deployable;
29
import org.jreleaser.model.spi.deploy.maven.MavenDeployer;
30
import org.jreleaser.model.spi.upload.UploadException;
31
import org.jreleaser.sdk.command.Command;
32
import org.jreleaser.sdk.command.CommandException;
33
import org.jreleaser.sdk.signing.SigningUtils;
34
import org.jreleaser.sdk.tool.PomChecker;
35
import org.jreleaser.sdk.tool.ToolException;
36
import org.jreleaser.util.Algorithm;
37
import org.jreleaser.util.ChecksumUtils;
38
import org.jreleaser.util.CollectionUtils;
39
import org.jreleaser.util.Errors;
40
import org.jreleaser.util.FileUtils;
41
import org.w3c.dom.Document;
42
import org.xml.sax.SAXException;
43

44
import javax.xml.parsers.DocumentBuilderFactory;
45
import javax.xml.parsers.ParserConfigurationException;
46
import javax.xml.xpath.XPathConstants;
47
import javax.xml.xpath.XPathExpressionException;
48
import javax.xml.xpath.XPathFactory;
49
import java.io.IOException;
50
import java.net.HttpURLConnection;
51
import java.net.MalformedURLException;
52
import java.net.URI;
53
import java.net.URISyntaxException;
54
import java.net.URL;
55
import java.nio.file.FileVisitResult;
56
import java.nio.file.Files;
57
import java.nio.file.Path;
58
import java.nio.file.SimpleFileVisitor;
59
import java.nio.file.attribute.BasicFileAttributes;
60
import java.nio.file.attribute.FileTime;
61
import java.time.Instant;
62
import java.time.LocalDateTime;
63
import java.time.ZoneId;
64
import java.util.ArrayList;
65
import java.util.Enumeration;
66
import java.util.LinkedHashMap;
67
import java.util.LinkedHashSet;
68
import java.util.List;
69
import java.util.Locale;
70
import java.util.Map;
71
import java.util.Optional;
72
import java.util.Set;
73
import java.util.TreeSet;
74
import java.util.function.Function;
75
import java.util.jar.JarEntry;
76
import java.util.jar.JarFile;
77
import java.util.stream.Collectors;
78

79
import static java.nio.charset.StandardCharsets.UTF_8;
80
import static java.nio.file.FileVisitResult.CONTINUE;
81
import static java.util.stream.Collectors.toSet;
82
import static org.jreleaser.model.spi.deploy.maven.Deployable.EXT_ASC;
83
import static org.jreleaser.model.spi.deploy.maven.Deployable.EXT_JAR;
84
import static org.jreleaser.model.spi.deploy.maven.Deployable.EXT_POM;
85
import static org.jreleaser.model.spi.deploy.maven.Deployable.EXT_WAR;
86
import static org.jreleaser.model.spi.deploy.maven.Deployable.MAVEN_METADATA_XML;
87
import static org.jreleaser.model.spi.deploy.maven.Deployable.PACKAGING_JAR;
88
import static org.jreleaser.model.spi.deploy.maven.Deployable.PACKAGING_WAR;
89
import static org.jreleaser.util.StringUtils.isNotBlank;
90

91
/**
92
 * @author Andres Almiray
93
 * @since 1.4.0
94
 */
95
public abstract class AbstractMavenDeployer<A extends org.jreleaser.model.api.deploy.maven.MavenDeployer,
96
    D extends org.jreleaser.model.internal.deploy.maven.MavenDeployer<A>> implements MavenDeployer<A, D> {
97
    private static final Algorithm[] ALGORITHMS = {
1✔
98
        Algorithm.MD5,
99
        Algorithm.SHA_1,
100
        Algorithm.SHA_256,
101
        Algorithm.SHA_512
102
    };
103

104
    private static final String BUILD_TAG = "-build";
105
    private static final Map<String, String> KEY_SERVERS = CollectionUtils.<String, String>map()
1✔
106
        .e("https://keys.openpgp.org", "https://keys.openpgp.org/search?q=%s")
1✔
107
        .e("https://keyserver.ubuntu.com", "https://keyserver.ubuntu.com/pks/lookup?search=%s&fingerprint=on&options=mr&op=index")
1✔
108
        .e("https://pgp.mit.edu", "https://pgp.mit.edu/pks/lookup?op=get&search=0x%s");
1✔
109

110
    protected final JReleaserContext context;
111

112
    protected AbstractMavenDeployer(JReleaserContext context) {
1✔
113
        this.context = context;
1✔
114
    }
1✔
115

116
    protected Set<Deployable> collectDeployableArtifacts() {
117
        Set<Deployable> deployables = new TreeSet<>();
1✔
118
        for (String stagingRepository : getDeployer().getStagingRepositories()) {
1✔
119
            collectDeployables(deployables, stagingRepository);
1✔
120
        }
1✔
121

122
        return deployables;
1✔
123
    }
124

125
    protected Set<Deployable> collectDeployables() {
126
        return collectDeployables(true, true);
1✔
127
    }
128

129
    protected Set<Deployable> collectDeployables(boolean sign, boolean checksum) {
130
        Set<Deployable> deployables = collectDeployableArtifacts().stream()
1✔
131
            .filter(deployable -> sign || !deployable.isSignature())
1✔
132
            .filter(deployable -> checksum || !deployable.isChecksum())
1✔
133
            .collect(toSet());
1✔
134

135
        Map<String, Deployable> deployablesMap = deployables.stream()
1✔
136
            .collect(Collectors.toMap(Deployable::getFullDeployPath, Function.identity()));
1✔
137

138
        Errors errors = checkMavenCentralRules(deployablesMap);
1✔
139
        if (errors.hasErrors()) {
1✔
140
            errors.logErrors(context.getLogger());
×
141
            throw new JReleaserException(RB.$("ERROR_deployer_maven_central_rules"));
×
142
        }
143

144
        if (sign) signDeployables(deployablesMap, deployables);
1✔
145
        if (checksum) checksumDeployables(deployablesMap, deployables);
1✔
146

147
        return deployables;
1✔
148
    }
149

150
    public Set<Deployable> collectDeployables(Set<Deployable> deployables, String stagingRepository) {
151
        Path root = context.getBasedir().resolve(stagingRepository).normalize();
1✔
152

153
        if (!Files.exists(root)) {
1✔
154
            throw new JReleaserException(RB.$("validation_directory_not_exist",
×
155
                "maven." + getDeployer().getType() + "." + getDeployer().getName() + ".stagingRepository",
×
156
                context.relativizeToBasedir(root).toString()));
×
157
        }
158

159
        if (!root.toFile().isDirectory()) {
1✔
160
            throw new JReleaserException(RB.$("validation_is_not_a_directory",
×
161
                "maven." + getDeployer().getType() + "." + getDeployer().getName() + ".stagingRepository",
×
162
                context.relativizeToBasedir(root).toString()));
×
163
        }
164

165
        try {
166
            DeployableCollector collector = new DeployableCollector(root, context.getModel().getProject().isSnapshot());
1✔
167

168
            java.nio.file.Files.walkFileTree(root, collector);
1✔
169
            if (collector.failed) {
1✔
170
                throw new JReleaserException(RB.$("ERROR_deployer_stage_resolution"));
×
171
            }
172

173
            deployables.addAll(collector.deployables);
1✔
174
        } catch (IOException e) {
×
175
            throw new JReleaserException(RB.$("ERROR_deployer_unexpected_error_stage"), e);
×
176
        }
1✔
177

178
        return deployables;
1✔
179
    }
180

181
    private Errors checkMavenCentralRules(Map<String, Deployable> deployablesMap) {
182
        Errors errors = new Errors();
1✔
183

184
        context.getLogger().info(RB.$("deployers.maven.prerequisites"));
1✔
185

186
        Map<String, Deployable> buildPoms = new LinkedHashMap<>();
1✔
187

188
        // 1st check jar, sources, javadoc if applicable
189
        for (Deployable deployable : deployablesMap.values()) {
1✔
190
            if (!deployable.getFilename().endsWith(EXT_POM)) {
1✔
191
                continue;
1✔
192
            }
193

194
            String base = deployable.getFilename();
1✔
195
            base = base.substring(0, base.length() - 4);
1✔
196

197
            boolean buildPom = false;
1✔
198
            if (base.endsWith(BUILD_TAG)) {
1✔
199
                Deployable baseDeployable = deployable.deriveByFilename(base.substring(0, base.length() - BUILD_TAG.length()) + EXT_POM);
×
200
                if (deployablesMap.containsKey(baseDeployable.getFullDeployPath())) {
×
201
                    buildPom = true;
×
202
                    buildPoms.put(deployable.getFullDeployPath(), deployable);
×
203
                }
204
            }
205

206
            if (!buildPom) {
1✔
207
                if (requiresJar(deployable)) {
1✔
208
                    Deployable derived = deployable.deriveByFilename(PACKAGING_JAR, base + EXT_JAR);
1✔
209
                    if (!deployablesMap.containsKey(derived.getFullDeployPath())) {
1✔
210
                        errors.configuration(RB.$("validation_is_missing", derived.getFilename()));
×
211
                    }
212
                }
213

214
                if (!deployable.isRelocated() && deployable.requiresWar()) {
1✔
215
                    Deployable derived = deployable.deriveByFilename(PACKAGING_WAR, base + EXT_WAR);
×
216
                    if (!deployablesMap.containsKey(derived.getFullDeployPath())) {
×
217
                        errors.configuration(RB.$("validation_is_missing", derived.getFilename()));
×
218
                    }
219
                }
220

221
                if (!deployable.isRelocated() && requiresSourcesJar(deployable, base)) {
1✔
222
                    Deployable derived = deployable.deriveByFilename(PACKAGING_JAR, base + "-sources.jar");
1✔
223
                    if (!deployablesMap.containsKey(derived.getFullDeployPath())) {
1✔
224
                        errors.configuration(RB.$("validation_is_missing", derived.getFilename()));
×
225
                    }
226
                }
227

228
                if (!deployable.isRelocated() && requiresJavadocJar(deployable, base)) {
1✔
229
                    Deployable derived = deployable.deriveByFilename(PACKAGING_JAR, base + "-javadoc.jar");
1✔
230
                    if (!deployablesMap.containsKey(derived.getFullDeployPath())) {
1✔
231
                        errors.configuration(RB.$("validation_is_missing", derived.getFilename()));
×
232
                    }
233
                }
234
            }
235
        }
1✔
236

237
        if (!getDeployer().isVerifyPom()) {
1✔
238
            return errors;
1✔
239
        }
240

241
        context.getLogger().info(RB.$("deployers.maven.verify.poms"));
1✔
242

243
        Maven.Pomchecker pomcheckerModel = context.getModel().getDeploy().getMaven().getPomchecker();
1✔
244
        PomChecker pomChecker = new PomChecker(context.asImmutable(),
1✔
245
            pomcheckerModel.getVersion());
1✔
246
        try {
247
            if (!pomChecker.setup()) {
1✔
248
                context.getLogger().warn(RB.$("tool_unavailable", "pomchecker"));
×
249
                return errors;
×
250
            }
251
        } catch (ToolException e) {
×
252
            context.getLogger().warn(RB.$("tool_unavailable", "pomchecker"), e);
×
253
            return errors;
×
254
        }
1✔
255

256
        Set<String> paths = new LinkedHashSet<>();
1✔
257
        for (String stagingRepository : getDeployer().getStagingRepositories()) {
1✔
258
            paths.add(context.getBasedir().resolve(stagingRepository)
1✔
259
                .toAbsolutePath()
1✔
260
                .normalize()
1✔
261
                .toString());
1✔
262
        }
1✔
263

264
        // 2nd check pom
265
        for (Deployable deployable : deployablesMap.values()) {
1✔
266
            if (!deployable.getFilename().endsWith(EXT_POM) || buildPoms.containsKey(deployable.getFullDeployPath()) ||
1✔
267
                !requiresPomVerification(deployable)) {
1✔
268
                continue;
×
269
            }
270

271
            List<String> args = new ArrayList<>();
1✔
272
            args.add("check-maven-central");
1✔
273
            args.add("--quiet");
1✔
274
            if (context.getModel().getProject().isSnapshot() &&
1✔
275
                getDeployer().isSnapshotSupported()) {
×
276
                args.add("--no-release");
×
277
            }
278
            if (pomChecker.isVersionCompatibleWith("1.9.0")) {
1✔
279
                if (!pomcheckerModel.isFailOnWarning()) {
1✔
280
                    args.add("--no-fail-on-warning");
×
281
                }
282
                if (!pomcheckerModel.isFailOnError()) {
1✔
283
                    args.add("--no-fail-on-error");
×
284
                }
285
            }
286

287
            if (pomChecker.isVersionCompatibleWith("1.13.0")) {
1✔
288
                for (String path : paths) {
1✔
289
                    args.add("--repository");
1✔
290
                    args.add(path);
1✔
291
                }
1✔
292
            }
293

294
            if (pomcheckerModel.isStrict()) {
1✔
295
                args.add("--strict");
1✔
296
            } else {
297
                args.add("--no-strict");
×
298
            }
299

300
            args.add("--file");
1✔
301
            args.add(deployable.getLocalPath().toAbsolutePath().toString());
1✔
302

303
            context.getLogger().debug(RB.$("deployers.maven.verify.pom", deployable.getLocalPath()));
1✔
304

305
            Command.Result result = Command.Result.empty();
1✔
306
            try {
307
                result = pomChecker.check(context.getBasedir(), args);
1✔
308
            } catch (CommandException e) {
×
309
                handlePomcheckerResult(deployable.getLocalPath().getFileName().toString(), result, e, errors);
×
310
            }
1✔
311

312
            if (result.getExitValue() != 0) {
1✔
313
                handlePomcheckerResult(deployable.getLocalPath().getFileName().toString(), result, null, errors);
×
314
            }
315
        }
1✔
316

317
        return errors;
1✔
318
    }
319

320
    private void handlePomcheckerResult(String filename, Command.Result result, CommandException e, Errors errors) {
321
        String plumbing = result.getErr();
×
322
        String validation = result.getOut();
×
323

324
        // 1st check out -> validation issues
325
        if (isNotBlank(validation)) {
×
326
            errors.configuration(RB.$("ERROR_deployer_pomchecker_header", filename, validation));
×
327
        } else if (isNotBlank(plumbing)) {
×
328
            // 2nd check err -> plumbing issues
329
            errors.configuration(RB.$("ERROR_deployer_pomchecker_header", filename, plumbing));
×
330
        } else if (null != e) {
×
331
            // command failed and we've got no clue!
332
            errors.configuration(e.getMessage());
×
333
        }
334
    }
×
335

336
    private boolean requiresJar(Deployable deployable) {
337
        if (!deployable.requiresJar()) return false;
1✔
338

339
        Optional<org.jreleaser.model.internal.deploy.maven.MavenDeployer.ArtifactOverride> override = getDeployer().getArtifactOverrides().stream()
1✔
340
            .filter(a -> a.getGroupId().equals(deployable.getGroupId()) && a.getArtifactId().equals(deployable.getArtifactId()))
1✔
341
            .findFirst();
1✔
342

343
        if (override.isPresent() && (override.get().isJarSet())) return override.get().isJar();
1✔
344

345
        return !deployable.isRelocated();
1✔
346
    }
347

348
    private boolean requiresSourcesJar(Deployable deployable, String baseFilename) {
349
        if (!deployable.requiresSourcesJar()) return false;
1✔
350

351
        Optional<org.jreleaser.model.internal.deploy.maven.MavenDeployer.ArtifactOverride> override = getDeployer().getArtifactOverrides().stream()
1✔
352
            .filter(a -> a.getGroupId().equals(deployable.getGroupId()) && a.getArtifactId().equals(deployable.getArtifactId()))
1✔
353
            .findFirst();
1✔
354

355
        if (override.isPresent() && (override.get().isSourceJarSet())) return override.get().isSourceJar();
1✔
356

357
        if (!getDeployer().isSourceJarSet()) {
1✔
358
            return hasJavaClass(deployable, baseFilename);
1✔
359
        }
360

361
        return getDeployer().isSourceJar();
×
362
    }
363

364
    private boolean requiresJavadocJar(Deployable deployable, String baseFilename) {
365
        if (!deployable.requiresJavadocJar()) return false;
1✔
366

367
        Optional<org.jreleaser.model.internal.deploy.maven.MavenDeployer.ArtifactOverride> override = getDeployer().getArtifactOverrides().stream()
1✔
368
            .filter(a -> a.getGroupId().equals(deployable.getGroupId()) && a.getArtifactId().equals(deployable.getArtifactId()))
1✔
369
            .findFirst();
1✔
370

371
        if (override.isPresent() && (override.get().isJavadocJarSet())) return override.get().isJavadocJar();
1✔
372

373
        if (!getDeployer().isJavadocJarSet()) {
1✔
374
            return hasJavaClass(deployable, baseFilename);
1✔
375
        }
376

377
        return getDeployer().isJavadocJar();
×
378
    }
379

380
    private boolean hasJavaClass(Deployable deployable, String baseFilename) {
381
        Deployable jar = deployable.deriveByFilename(PACKAGING_JAR, baseFilename + EXT_JAR);
1✔
382
        JarFile jarFile = null;
1✔
383

384
        try {
385
            jarFile = new JarFile(jar.getLocalPath().toFile());
1✔
386
            Enumeration<JarEntry> entries = jarFile.entries();
1✔
387
            while (entries.hasMoreElements()) {
1✔
388
                JarEntry entry = entries.nextElement();
1✔
389
                if (entry.getName().endsWith(".class")) {
1✔
390
                    return true;
1✔
391
                }
392
            }
1✔
393
            return false;
×
394
        } catch (IOException e) {
×
395
            context.getLogger().warn(RB.$("ERROR_deployer_read_local_file", jar.getLocalPath()), e);
×
396
            return false;
×
397
        } finally {
398
            IOUtils.closeQuietly(jarFile);
1✔
399
        }
400
    }
401

402
    private boolean requiresPomVerification(Deployable deployable) {
403
        Optional<org.jreleaser.model.internal.deploy.maven.MavenDeployer.ArtifactOverride> override = getDeployer().getArtifactOverrides().stream()
1✔
404
            .filter(a -> a.getGroupId().equals(deployable.getGroupId()) && a.getArtifactId().equals(deployable.getArtifactId()))
1✔
405
            .findFirst();
1✔
406

407
        if (override.isPresent() && (override.get().isVerifyPomSet())) return override.get().isVerifyPom();
1✔
408

409
        return getDeployer().isVerifyPom();
1✔
410
    }
411

412
    private void signDeployables(Map<String, Deployable> deployablesMap, Set<Deployable> deployables) {
413
        if (!getDeployer().isSign()) {
1✔
414
            return;
1✔
415
        }
416

417
        verifyKeyIsValid();
1✔
418

419
        for (Deployable deployable : deployablesMap.values()) {
1✔
420
            if (deployable.isSignature() || deployable.isChecksum() || deployable.isMavenMetadata()) continue;
1✔
421

422
            Deployable signedDeployable = deployable.deriveByFilename(deployable.getFilename() + EXT_ASC);
1✔
423

424
            if (isNewer(signedDeployable, deployable)) {
1✔
425
                continue;
1✔
426
            }
427

428
            try {
429
                context.getLogger().setPrefix("sign");
1✔
430
                SigningUtils.sign(context.asImmutable(), deployable.getLocalPath());
1✔
431
                deployables.add(signedDeployable);
1✔
432
            } catch (SigningException e) {
×
433
                throw new JReleaserException(RB.$("ERROR_unexpected_error_signing_file", deployable.getFilename()), e);
×
434
            } finally {
435
                context.getLogger().restorePrefix();
1✔
436
            }
437
        }
1✔
438
    }
1✔
439

440
    private boolean isNewer(Deployable source, Deployable target) {
441
        Path sourcePath = source.getLocalPath();
1✔
442
        if (!Files.exists(sourcePath)) {
1✔
443
            return false;
1✔
444
        }
445

446
        Path targetPath = target.getLocalPath();
1✔
447

448
        FileTime sourceLastModifiedTime = null;
1✔
449
        FileTime targetLastModifiedTime = null;
1✔
450

451
        try {
452
            sourceLastModifiedTime = Files.getLastModifiedTime(sourcePath);
1✔
453
        } catch (IOException e) {
×
454
            throw new JReleaserException(RB.$("ERROR_unexpected_error_timestamp_file", sourcePath.getFileName()), e);
×
455
        }
1✔
456

457
        try {
458
            targetLastModifiedTime = Files.getLastModifiedTime(targetPath);
1✔
459
        } catch (IOException e) {
×
460
            throw new JReleaserException(RB.$("ERROR_unexpected_error_timestamp_file", targetPath.getFileName()), e);
×
461
        }
1✔
462

463
        return sourceLastModifiedTime.compareTo(targetLastModifiedTime) > 0;
1✔
464
    }
465

466
    private void verifyKeyIsValid() {
467
        Optional<String> publicKeyID = Optional.empty();
1✔
468
        Optional<String> fingerprint = Optional.empty();
1✔
469

470
        try {
471
            publicKeyID = SigningUtils.getPublicKeyID(context.asImmutable());
1✔
472
        } catch (SigningException e) {
×
473
            throw new JReleaserException(RB.$("ERROR_public_key_not_found"));
×
474
        }
1✔
475

476
        if (!publicKeyID.isPresent()) {
1✔
477
            throw new JReleaserException(RB.$("ERROR_public_key_not_found"));
×
478
        }
479

480
        try {
481
            fingerprint = SigningUtils.getFingerprint(context.asImmutable());
1✔
482
        } catch (SigningException e) {
×
483
            throw new JReleaserException(RB.$("ERROR_public_key_not_found"));
×
484
        }
1✔
485

486
        if (!fingerprint.isPresent()) {
1✔
487
            throw new JReleaserException(RB.$("ERROR_public_key_not_found"));
×
488
        }
489

490
        String keyID = publicKeyID.get().toUpperCase(Locale.ENGLISH);
1✔
491
        String fp = fingerprint.get().toUpperCase(Locale.ENGLISH);
1✔
492

493
        try {
494
            Optional<Instant> expirationDate = SigningUtils.getExpirationDateOfPublicKey(context.asImmutable());
1✔
495

496
            if (expirationDate.isPresent()) {
1✔
497
                Instant ed = expirationDate.get();
1✔
498
                if (Instant.EPOCH.equals(ed)) {
1✔
499
                    context.getLogger().warn(RB.$("signing.public.key.no.expiration.date", keyID));
×
500

501
                } else if (Instant.now().isAfter(ed)) {
1✔
502
                        throw new JReleaserException(RB.$("ERROR_public_key_expired", keyID, LocalDateTime.ofInstant(ed, ZoneId.systemDefault())));
×
503
                } else {
504
                    context.getLogger().info(RB.$("signing.public.key.expiration.date", keyID, LocalDateTime.ofInstant(ed, ZoneId.systemDefault())));
1✔
505
                }
506
            }
507
        } catch (SigningException e) {
×
508
            throw new JReleaserException(RB.$("ERROR_public_key_not_found"));
×
509
        }
1✔
510

511
        boolean published = false;
1✔
512

513
        context.getLogger().info(RB.$("signing.check.published.key", keyID));
1✔
514
        for (Map.Entry<String, String> e : KEY_SERVERS.entrySet()) {
1✔
515
            try {
516
                boolean notfound = false;
1✔
517
                // 1st try with keyId
518
                URL url = new URI(String.format(e.getValue(), fp)).toURL();
1✔
519
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
1✔
520
                connection.setConnectTimeout(20_000);
1✔
521
                connection.setReadTimeout(40_000);
1✔
522
                if (connection.getResponseCode() < 400) {
1✔
523
                    context.getLogger().debug(" + " + e.getKey());
1✔
524
                    published = true;
1✔
525
                } else {
526
                    notfound = true;
1✔
527
                }
528

529
                if (!notfound) {
1✔
530
                    // 2nd try with fingerprint
531
                    url = new URI(String.format(e.getValue(), keyID)).toURL();
1✔
532
                    connection = (HttpURLConnection) url.openConnection();
1✔
533
                    connection.setConnectTimeout(20_000);
1✔
534
                    connection.setReadTimeout(40_000);
1✔
535
                    if (connection.getResponseCode() < 400) {
1✔
536
                        context.getLogger().debug(" + " + e.getKey());
1✔
537
                        published = true;
1✔
538
                        notfound = false;
1✔
539
                    }
540
                }
541

542
                if (!notfound) {
1✔
543
                    context.getLogger().debug(" x " + e.getKey());
1✔
544
                }
545
            } catch (MalformedURLException | URISyntaxException ignored) {
×
546
                // ignored
547
            } catch (IOException ex) {
1✔
548
                context.getLogger().debug(RB.$("ERROR_unexpected_error") + " " + ex.getMessage());
1✔
549
            }
1✔
550
        }
1✔
551

552
        if (published) {
1✔
553
            context.getLogger().info(RB.$("signing.key.published", keyID));
1✔
554
        } else {
555
            context.getLogger().warn(RB.$("signing.key.not.published", keyID));
×
556
        }
557
    }
1✔
558

559
    private void checksumDeployables(Map<String, Deployable> deployablesMap, Set<Deployable> deployables) {
560
        if (!getDeployer().isChecksums()) {
1✔
561
            return;
1✔
562
        }
563

564
        for (Deployable deployable : deployablesMap.values()) {
1✔
565
            if (deployable.isChecksum()) continue;
1✔
566

567
            if (deployable.getFilename().endsWith(EXT_ASC)) {
1✔
568
                // remove checksum for signature files
569
                for (Algorithm algorithm : ALGORITHMS) {
1✔
570
                    Deployable checksumDeployable = deployable.deriveByFilename(deployable.getFilename() + "." + algorithm.formatted());
1✔
571
                    deployables.remove(checksumDeployable);
1✔
572
                }
573
                continue;
1✔
574
            }
575

576
            try {
577
                byte[] data = Files.readAllBytes(deployable.getLocalPath());
1✔
578
                for (Algorithm algorithm : ALGORITHMS) {
1✔
579
                    Deployable checksumDeployable = deployable.deriveByFilename(deployable.getFilename() + "." + algorithm.formatted());
1✔
580

581
                    if (isNewer(checksumDeployable, deployable)) {
1✔
582
                        continue;
1✔
583
                    }
584

585
                    context.getLogger().debug(RB.$("checksum.calculating", algorithm.formatted(), deployable.getFilename()));
1✔
586
                    String checksum = ChecksumUtils.checksum(algorithm, data);
1✔
587
                    Files.write(checksumDeployable.getLocalPath(), checksum.getBytes(UTF_8));
1✔
588
                    deployables.add(checksumDeployable);
1✔
589
                }
590
            } catch (IOException e) {
×
591
                throw new JReleaserException(RB.$("ERROR_unexpected_error_calculate_checksum", deployable.getFilename()), e);
×
592
            }
1✔
593
        }
1✔
594
    }
1✔
595

596
    protected void deployPackages() throws DeployException {
597
        Set<Deployable> deployables = collectDeployables();
1✔
598
        if (deployables.isEmpty()) {
1✔
599
            context.getLogger().info(RB.$("artifacts.no.match"));
×
600
        }
601

602
        D deployer = getDeployer();
1✔
603
        String baseUrl = deployer.getResolvedUrl(context.fullProps());
1✔
604
        String token = deployer.getPassword();
1✔
605

606
        if (!baseUrl.endsWith("/")) {
1✔
607
            baseUrl += " ";
×
608
        }
609

610
        // delete existing packages (if any)
611
        deleteExistingPackages(baseUrl, token, deployables);
1✔
612

613
        for (Deployable deployable : deployables) {
1✔
614
            if (deployable.isSignature() || deployable.isChecksum()) continue;
1✔
615
            Path localPath = deployable.getLocalPath();
1✔
616
            context.getLogger().info(" - {}", deployable.getFilename());
1✔
617

618
            if (!context.isDryrun()) {
1✔
619
                try {
620
                    Map<String, String> headers = new LinkedHashMap<>();
×
621
                    headers.put("Authorization", "Bearer " + token);
×
622
                    FormData data = ClientUtils.toFormData(localPath);
×
623

624
                    String url = baseUrl + deployable.getFullDeployPath();
×
625
                    ClientUtils.putFile(context.getLogger(),
×
626
                        url,
627
                        deployer.getConnectTimeout(),
×
628
                        deployer.getReadTimeout(),
×
629
                        data,
630
                        headers);
631
                } catch (IOException | UploadException e) {
×
632
                    context.getLogger().trace(e);
×
633
                    throw new DeployException(RB.$("ERROR_unexpected_deploy",
×
634
                        context.getBasedir().relativize(localPath), e.getMessage()), e);
×
635
                }
×
636
            }
637
        }
1✔
638
    }
1✔
639

640
    protected void deleteExistingPackages(String baseUrl, String token, Set<Deployable> deployables) throws DeployException {
641
        // noop
642
    }
×
643

644
    private class DeployableCollector extends SimpleFileVisitor<Path> {
645
        private final Path root;
646
        private final Set<Deployable> deployables = new TreeSet<>();
1✔
647
        private final boolean projectIsSnapshot;
648
        private boolean failed;
649

650
        public DeployableCollector(Path root, boolean projectIsSnapshot) {
1✔
651
            this.root = root;
1✔
652
            this.projectIsSnapshot = projectIsSnapshot;
1✔
653
        }
1✔
654

655
        private void match(Path path) {
656
            if (FileUtils.isHidden(path)) return;
1✔
657

658
            String filename = path.getFileName().toString();
1✔
659

660
            if (filename.contains(MAVEN_METADATA_XML)) {
1✔
661
                if (projectIsSnapshot) {
1✔
662
                    addDeployable(path);
×
663
                }
664
            } else {
665
                addDeployable(path);
1✔
666
            }
667
        }
1✔
668

669
        private void addDeployable(Path path) {
670
            String stagingRepository = root.toAbsolutePath().toString();
1✔
671
            String stagingPath = path.getParent().toAbsolutePath().toString();
1✔
672
            PomResult pomResult = parsePom(path);
1✔
673
            deployables.add(new Deployable(
1✔
674
                projectIsSnapshot,
675
                stagingRepository,
676
                stagingPath.substring(stagingRepository.length()),
1✔
677
                pomResult.packaging,
1✔
678
                path.getFileName().toString(),
1✔
679
                pomResult.relocated
1✔
680
            ));
681
        }
1✔
682

683
        private PomResult parsePom(Path artifactPath) {
684
            // only inspect if artifactPath ends with .pom
685
            if (artifactPath.getFileName().toString().endsWith(EXT_JAR)) return new PomResult(PACKAGING_JAR);
1✔
686
            if (!artifactPath.getFileName().toString().endsWith(EXT_POM)) return new PomResult("");
1✔
687

688
            try {
689
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
1✔
690
                factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
1✔
691
                Document document = factory
1✔
692
                    .newDocumentBuilder()
1✔
693
                    .parse(artifactPath.toFile());
1✔
694
                String query = "/project/packaging";
1✔
695
                String packaging = (String) XPathFactory.newInstance()
1✔
696
                    .newXPath()
1✔
697
                    .compile(query)
1✔
698
                    .evaluate(document, XPathConstants.STRING);
1✔
699

700
                query = "/project/distributionManagement/relocation";
1✔
701
                Object relocation = XPathFactory.newInstance()
1✔
702
                    .newXPath()
1✔
703
                    .compile(query)
1✔
704
                    .evaluate(document, XPathConstants.NODE);
1✔
705

706
                return new PomResult(isNotBlank(packaging) ? packaging.trim() : PACKAGING_JAR,
1✔
707
                    relocation != null);
708
            } catch (ParserConfigurationException | IOException | SAXException | XPathExpressionException e) {
×
709
                throw new IllegalStateException(e);
×
710
            }
711
        }
712

713
        @Override
714
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
715
            match(file);
1✔
716
            return CONTINUE;
1✔
717
        }
718

719
        @Override
720
        public FileVisitResult visitFileFailed(Path file, IOException e) throws IOException {
721
            failed = true;
×
722
            context.getLogger().trace(e);
×
723
            context.getLogger().error(RB.$("ERROR_artifacts_unexpected_error_path"),
×
724
                root.toAbsolutePath().relativize(file.toAbsolutePath()), e);
×
725
            return CONTINUE;
×
726
        }
727

728
        private final class PomResult {
729
            private String packaging;
730
            private boolean relocated;
731

732
            private PomResult(String packaging) {
1✔
733
                this.packaging = packaging;
1✔
734
            }
1✔
735

736
            private PomResult(String packaging, boolean relocated) {
1✔
737
                this.packaging = packaging;
1✔
738
                this.relocated = relocated;
1✔
739
            }
1✔
740
        }
741
    }
742
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc