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

jreleaser / jreleaser / #477

04 Apr 2025 05:53PM UTC coverage: 35.124% (-5.1%) from 40.183%
#477

push

github

aalmiray
fix(deploy): Add missing Forgejo messages

Related to #1842

18210 of 51845 relevant lines covered (35.12%)

0.35 hits per line

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

0.0
/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.w3c.dom.Document;
41
import org.xml.sax.SAXException;
42

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

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

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

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

109
    protected final JReleaserContext context;
110

111
    protected AbstractMavenDeployer(JReleaserContext context) {
×
112
        this.context = context;
×
113
    }
×
114

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

121
        return deployables;
×
122
    }
123

124
    protected Set<Deployable> collectDeployables() {
125
        return collectDeployables(true, true);
×
126
    }
127

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

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

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

143
        if (sign) signDeployables(deployablesMap, deployables);
×
144
        if (checksum) checksumDeployables(deployablesMap, deployables);
×
145

146
        return deployables;
×
147
    }
148

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

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

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

164
        try {
165
            DeployableCollector collector = new DeployableCollector(root, context.getModel().getProject().isSnapshot());
×
166

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

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

177
        return deployables;
×
178
    }
179

180
    private Errors checkMavenCentralRules(Map<String, Deployable> deployablesMap) {
181
        Errors errors = new Errors();
×
182

183
        context.getLogger().info(RB.$("deployers.maven.prerequisites"));
×
184

185
        Map<String, Deployable> buildPoms = new LinkedHashMap<>();
×
186

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

193
            String base = deployable.getFilename();
×
194
            base = base.substring(0, base.length() - 4);
×
195

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

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

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

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

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

236
        if (!getDeployer().isVerifyPom()) {
×
237
            return errors;
×
238
        }
239

240
        context.getLogger().info(RB.$("deployers.maven.verify.poms"));
×
241

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

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

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

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

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

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

299
            args.add("--file");
×
300
            args.add(deployable.getLocalPath().toAbsolutePath().toString());
×
301

302
            context.getLogger().debug(RB.$("deployers.maven.verify.pom", deployable.getLocalPath()));
×
303

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

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

316
        return errors;
×
317
    }
318

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

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

335
    private boolean requiresJar(Deployable deployable) {
336
        if (!deployable.requiresJar()) return false;
×
337

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

342
        if (override.isPresent() && (override.get().isJarSet())) return override.get().isJar();
×
343

344
        return !deployable.isRelocated();
×
345
    }
346

347
    private boolean requiresSourcesJar(Deployable deployable, String baseFilename) {
348
        if (!deployable.requiresSourcesJar()) return false;
×
349

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

354
        if (override.isPresent() && (override.get().isSourceJarSet())) return override.get().isSourceJar();
×
355

356
        if (!getDeployer().isSourceJarSet()) {
×
357
            return hasJavaClass(deployable, baseFilename);
×
358
        }
359

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

363
    private boolean requiresJavadocJar(Deployable deployable, String baseFilename) {
364
        if (!deployable.requiresJavadocJar()) return false;
×
365

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

370
        if (override.isPresent() && (override.get().isJavadocJarSet())) return override.get().isJavadocJar();
×
371

372
        if (!getDeployer().isJavadocJarSet()) {
×
373
            return hasJavaClass(deployable, baseFilename);
×
374
        }
375

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

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

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

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

406
        if (override.isPresent() && (override.get().isVerifyPomSet())) return override.get().isVerifyPom();
×
407

408
        return getDeployer().isVerifyPom();
×
409
    }
410

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

416
        verifyKeyIsValid();
×
417

418
        for (Deployable deployable : deployablesMap.values()) {
×
419
            if (deployable.isSignature() || deployable.isChecksum() || deployable.isMavenMetadata()) continue;
×
420

421
            Deployable signedDeployable = deployable.deriveByFilename(deployable.getFilename() + EXT_ASC);
×
422

423
            if (isNewer(signedDeployable, deployable)) {
×
424
                continue;
×
425
            }
426

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

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

445
        Path targetPath = target.getLocalPath();
×
446

447
        FileTime sourceLastModifiedTime = null;
×
448
        FileTime targetLastModifiedTime = null;
×
449

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

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

462
        return sourceLastModifiedTime.compareTo(targetLastModifiedTime) > 0;
×
463
    }
464

465
    private void verifyKeyIsValid() {
466
        Optional<String> publicKeyID = Optional.empty();
×
467
        Optional<String> fingerprint = Optional.empty();
×
468

469
        try {
470
            publicKeyID = SigningUtils.getPublicKeyID(context.asImmutable());
×
471
        } catch (SigningException e) {
×
472
            context.getLogger().warn(RB.$("ERROR_public_key_not_found"));
×
473
            return;
×
474
        }
×
475

476
        if (!publicKeyID.isPresent()) {
×
477
            context.getLogger().warn(RB.$("ERROR_public_key_not_found"));
×
478
            return;
×
479
        }
480

481
        try {
482
            fingerprint = SigningUtils.getFingerprint(context.asImmutable());
×
483
        } catch (SigningException e) {
×
484
            context.getLogger().warn(RB.$("ERROR_public_key_not_found"));
×
485
            return;
×
486
        }
×
487

488
        if (!fingerprint.isPresent()) {
×
489
            context.getLogger().warn(RB.$("ERROR_public_key_not_found"));
×
490
            return;
×
491
        }
492

493
        String keyID = publicKeyID.get().toUpperCase(Locale.ENGLISH);
×
494
        String fp = fingerprint.get().toUpperCase(Locale.ENGLISH);
×
495

496
        try {
497
            Optional<Instant> expirationDate = SigningUtils.getExpirationDateOfPublicKey(context.asImmutable());
×
498

499
            if (expirationDate.isPresent()) {
×
500
                Instant ed = expirationDate.get();
×
501
                if (Instant.EPOCH.equals(ed)) {
×
502
                    context.getLogger().warn(RB.$("signing.public.key.no.expiration.date", keyID));
×
503

504
                } else if (Instant.now().isAfter(ed)) {
×
505
                    context.getLogger().warn(RB.$("ERROR_public_key_expired", keyID, LocalDateTime.ofInstant(ed, ZoneId.systemDefault())));
×
506
                } else {
507
                    context.getLogger().info(RB.$("signing.public.key.expiration.date", keyID, LocalDateTime.ofInstant(ed, ZoneId.systemDefault())));
×
508
                }
509
            }
510
        } catch (SigningException e) {
×
511
            context.getLogger().warn(RB.$("ERROR_public_key_not_found"));
×
512
            return;
×
513
        }
×
514

515
        boolean published = false;
×
516

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

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

546
                if (!notfound) {
×
547
                    context.getLogger().debug(" x " + e.getKey());
×
548
                }
549
            } catch (MalformedURLException | URISyntaxException ignored) {
×
550
                // ignored
551
            } catch (IOException ex) {
×
552
                context.getLogger().debug(RB.$("ERROR_unexpected_error") + " " + ex.getMessage());
×
553
            }
×
554
        }
×
555

556
        if (published) {
×
557
            context.getLogger().info(RB.$("signing.key.published", keyID));
×
558
        } else {
559
            context.getLogger().warn(RB.$("signing.key.not.published", keyID));
×
560
        }
561
    }
×
562

563
    private void checksumDeployables(Map<String, Deployable> deployablesMap, Set<Deployable> deployables) {
564
        if (!getDeployer().isChecksums()) {
×
565
            return;
×
566
        }
567

568
        for (Deployable deployable : deployablesMap.values()) {
×
569
            if (deployable.isChecksum()) continue;
×
570

571
            if (deployable.getFilename().endsWith(EXT_ASC)) {
×
572
                // remove checksum for signature files
573
                for (Algorithm algorithm : ALGORITHMS) {
×
574
                    Deployable checksumDeployable = deployable.deriveByFilename(deployable.getFilename() + "." + algorithm.formatted());
×
575
                    deployables.remove(checksumDeployable);
×
576
                }
577
                continue;
×
578
            }
579

580
            try {
581
                byte[] data = Files.readAllBytes(deployable.getLocalPath());
×
582
                for (Algorithm algorithm : ALGORITHMS) {
×
583
                    Deployable checksumDeployable = deployable.deriveByFilename(deployable.getFilename() + "." + algorithm.formatted());
×
584

585
                    if (isNewer(checksumDeployable, deployable)) {
×
586
                        continue;
×
587
                    }
588

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

600
    protected void deployPackages() throws DeployException {
601
        Set<Deployable> deployables = collectDeployables();
×
602
        if (deployables.isEmpty()) {
×
603
            context.getLogger().info(RB.$("artifacts.no.match"));
×
604
        }
605

606
        D deployer = getDeployer();
×
607
        String baseUrl = deployer.getResolvedUrl(context.fullProps());
×
608
        String token = deployer.getPassword();
×
609

610
        if (!baseUrl.endsWith("/")) {
×
611
            baseUrl += " ";
×
612
        }
613

614
        // delete existing packages (if any)
615
        deleteExistingPackages(baseUrl, token, deployables);
×
616

617
        for (Deployable deployable : deployables) {
×
618
            if (deployable.isSignature() || deployable.isChecksum()) continue;
×
619
            Path localPath = deployable.getLocalPath();
×
620
            context.getLogger().info(" - {}", deployable.getFilename());
×
621

622
            if (!context.isDryrun()) {
×
623
                try {
624
                    Map<String, String> headers = new LinkedHashMap<>();
×
625
                    headers.put("Authorization", "Bearer " + token);
×
626
                    FormData data = ClientUtils.toFormData(localPath);
×
627

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

644
    protected void deleteExistingPackages(String baseUrl, String token, Set<Deployable> deployables) throws DeployException {
645
        // noop
646
    }
×
647

648
    private class DeployableCollector extends SimpleFileVisitor<Path> {
649
        private final Path root;
650
        private final Set<Deployable> deployables = new TreeSet<>();
×
651
        private final boolean projectIsSnapshot;
652
        private boolean failed;
653

654
        public DeployableCollector(Path root, boolean projectIsSnapshot) {
×
655
            this.root = root;
×
656
            this.projectIsSnapshot = projectIsSnapshot;
×
657
        }
×
658

659
        private void match(Path path) {
660
            String filename = path.getFileName().toString();
×
661

662
            if (filename.contains(MAVEN_METADATA_XML)) {
×
663
                if (projectIsSnapshot) {
×
664
                    addDeployable(path);
×
665
                }
666
            } else {
667
                addDeployable(path);
×
668
            }
669
        }
×
670

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

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

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

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

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

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

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

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

733
            private PomResult(String packaging) {
×
734
                this.packaging = packaging;
×
735
            }
×
736

737
            private PomResult(String packaging, boolean relocated) {
×
738
                this.packaging = packaging;
×
739
                this.relocated = relocated;
×
740
            }
×
741
        }
742
    }
743
}
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