1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.gpg;
20
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.Reader;
25 import java.io.Writer;
26 import java.nio.file.Files;
27 import java.util.ArrayList;
28 import java.util.List;
29
30 import org.apache.maven.artifact.handler.ArtifactHandler;
31 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
32 import org.apache.maven.model.Model;
33 import org.apache.maven.model.Parent;
34 import org.apache.maven.model.building.DefaultModelBuildingRequest;
35 import org.apache.maven.model.building.ModelBuildingRequest;
36 import org.apache.maven.model.building.ModelProblem;
37 import org.apache.maven.model.building.ModelProblemCollector;
38 import org.apache.maven.model.building.ModelProblemCollectorRequest;
39 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
40 import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
41 import org.apache.maven.model.validation.ModelValidator;
42 import org.apache.maven.plugin.MojoExecutionException;
43 import org.apache.maven.plugin.MojoFailureException;
44 import org.apache.maven.plugins.annotations.Component;
45 import org.apache.maven.plugins.annotations.Mojo;
46 import org.apache.maven.plugins.annotations.Parameter;
47 import org.apache.maven.project.MavenProject;
48 import org.codehaus.plexus.util.FileUtils;
49 import org.codehaus.plexus.util.ReaderFactory;
50 import org.codehaus.plexus.util.StringUtils;
51 import org.codehaus.plexus.util.WriterFactory;
52 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
53 import org.eclipse.aether.RepositorySystem;
54 import org.eclipse.aether.artifact.Artifact;
55 import org.eclipse.aether.artifact.DefaultArtifact;
56 import org.eclipse.aether.deployment.DeployRequest;
57 import org.eclipse.aether.deployment.DeploymentException;
58 import org.eclipse.aether.repository.RemoteRepository;
59
60
61
62
63
64
65
66 @Mojo(name = "sign-and-deploy-file", requiresProject = false, threadSafe = true)
67 public class SignAndDeployFileMojo extends AbstractGpgMojo {
68
69
70
71
72 @Parameter(property = "gpg.ascDirectory")
73 private File ascDirectory;
74
75
76
77
78 @Parameter(defaultValue = "${settings.offline}", readonly = true)
79 private boolean offline;
80
81
82
83
84 @Parameter(property = "groupId")
85 private String groupId;
86
87
88
89
90 @Parameter(property = "artifactId")
91 private String artifactId;
92
93
94
95
96 @Parameter(property = "version")
97 private String version;
98
99
100
101
102
103 @Parameter(property = "packaging")
104 private String packaging;
105
106
107
108
109 @Parameter(property = "classifier")
110 private String classifier;
111
112
113
114
115 @Parameter(property = "generatePom.description")
116 private String description;
117
118
119
120
121 @Parameter(property = "file", required = true)
122 private File file;
123
124
125
126
127 @Parameter(property = "pomFile")
128 private File pomFile;
129
130
131
132
133 @Parameter(property = "generatePom", defaultValue = "true")
134 private boolean generatePom;
135
136
137
138
139
140 @Parameter(property = "url", required = true)
141 private String url;
142
143
144
145
146
147 @Parameter(property = "repositoryId", defaultValue = "remote-repository", required = true)
148 private String repositoryId;
149
150
151
152
153 @Parameter(property = "repositoryLayout", defaultValue = "default")
154 private String repositoryLayout;
155
156
157
158
159
160
161 @Parameter(property = "javadoc")
162 private File javadoc;
163
164
165
166
167
168
169 @Parameter(property = "sources")
170 private File sources;
171
172
173
174
175
176
177
178 @Parameter(property = "retryFailedDeploymentCount", defaultValue = "1")
179 private int retryFailedDeploymentCount;
180
181
182
183
184
185 @Parameter(property = "types")
186 private String types;
187
188
189
190
191
192 @Parameter(property = "classifiers")
193 private String classifiers;
194
195
196
197
198
199 @Parameter(property = "files")
200 private String files;
201
202
203
204 @Component
205 private RepositorySystem repositorySystem;
206
207
208
209
210 @Component
211 private ModelValidator modelValidator;
212
213
214
215
216
217
218 @Component
219 private MavenProject project;
220
221
222
223
224 @Component
225 private ArtifactHandlerManager artifactHandlerManager;
226
227 private void initProperties() throws MojoExecutionException {
228
229 if (pomFile != null) {
230 generatePom = false;
231
232 Model model = readModel(pomFile);
233
234 processModel(model);
235 }
236
237 if (packaging == null && file != null) {
238 packaging = FileUtils.getExtension(file.getName());
239 }
240 }
241
242 @Override
243 protected void doExecute() throws MojoExecutionException, MojoFailureException {
244 if (offline) {
245 throw new MojoFailureException("Cannot deploy artifacts when Maven is in offline mode");
246 }
247
248 initProperties();
249
250 validateArtifactInformation();
251
252 if (!file.exists()) {
253 throw new MojoFailureException(file.getPath() + " not found.");
254 }
255
256 RemoteRepository deploymentRepository = new RemoteRepository.Builder(repositoryId, "default", url).build();
257
258
259 List<Artifact> artifacts = new ArrayList<>();
260
261
262 ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(packaging);
263 Artifact main = new DefaultArtifact(
264 groupId,
265 artifactId,
266 classifier == null || classifier.trim().isEmpty() ? handler.getClassifier() : classifier,
267 handler.getExtension(),
268 version)
269 .setFile(file);
270
271 File localRepoFile = new File(
272 session.getRepositorySession().getLocalRepository().getBasedir(),
273 session.getRepositorySession().getLocalRepositoryManager().getPathForLocalArtifact(main));
274 if (file.equals(localRepoFile)) {
275 throw new MojoFailureException("Cannot deploy artifact from the local repository: " + file);
276 }
277 artifacts.add(main);
278
279 if (!"pom".equals(packaging)) {
280 if (pomFile == null && generatePom) {
281 pomFile = generatePomFile();
282 }
283 if (pomFile != null) {
284 artifacts.add(
285 new DefaultArtifact(main.getGroupId(), main.getArtifactId(), null, "pom", main.getVersion())
286 .setFile(pomFile));
287 }
288 }
289
290 if (sources != null) {
291 artifacts.add(
292 new DefaultArtifact(main.getGroupId(), main.getArtifactId(), "sources", "jar", main.getVersion())
293 .setFile(sources));
294 }
295
296 if (javadoc != null) {
297 artifacts.add(
298 new DefaultArtifact(main.getGroupId(), main.getArtifactId(), "javadoc", "jar", main.getVersion())
299 .setFile(javadoc));
300 }
301
302 if (files != null) {
303 if (types == null) {
304 throw new MojoExecutionException("You must specify 'types' if you specify 'files'");
305 }
306 if (classifiers == null) {
307 throw new MojoExecutionException("You must specify 'classifiers' if you specify 'files'");
308 }
309 String[] files = this.files.split(",", -1);
310 String[] types = this.types.split(",", -1);
311 String[] classifiers = this.classifiers.split(",", -1);
312 if (types.length != files.length) {
313 throw new MojoExecutionException("You must specify the same number of entries in 'files' and "
314 + "'types' (respectively " + files.length + " and " + types.length + " entries )");
315 }
316 if (classifiers.length != files.length) {
317 throw new MojoExecutionException("You must specify the same number of entries in 'files' and "
318 + "'classifiers' (respectively " + files.length + " and " + classifiers.length + " entries )");
319 }
320 for (int i = 0; i < files.length; i++) {
321 File file = new File(files[i]);
322 if (!file.isFile()) {
323
324 file = new File(project.getBasedir(), files[i]);
325 }
326 if (file.isFile()) {
327 Artifact artifact;
328 String ext =
329 artifactHandlerManager.getArtifactHandler(types[i]).getExtension();
330 if (StringUtils.isWhitespace(classifiers[i])) {
331 artifact = new DefaultArtifact(
332 main.getGroupId(), main.getArtifactId(), null, ext, main.getVersion());
333 } else {
334 artifact = new DefaultArtifact(
335 main.getGroupId(), main.getArtifactId(), classifiers[i], ext, main.getVersion());
336 }
337 artifacts.add(artifact.setFile(file));
338 } else {
339 throw new MojoExecutionException("Specified side artifact " + file + " does not exist");
340 }
341 }
342 } else {
343 if (types != null) {
344 throw new MojoExecutionException("You must specify 'files' if you specify 'types'");
345 }
346 if (classifiers != null) {
347 throw new MojoExecutionException("You must specify 'files' if you specify 'classifiers'");
348 }
349 }
350
351
352 AbstractGpgSigner signer = newSigner(null);
353 signer.setOutputDirectory(ascDirectory);
354 signer.setBaseDirectory(new File("").getAbsoluteFile());
355
356 getLog().info("Signer '" + signer.signerName() + "' is signing " + artifacts.size() + " file"
357 + ((artifacts.size() > 1) ? "s" : ""));
358
359 ArrayList<Artifact> signatures = new ArrayList<>();
360 for (Artifact a : artifacts) {
361 signatures.add(new DefaultArtifact(
362 a.getGroupId(),
363 a.getArtifactId(),
364 a.getClassifier(),
365 a.getExtension() + AbstractGpgSigner.SIGNATURE_EXTENSION,
366 a.getVersion())
367 .setFile(signer.generateSignatureForArtifact(a.getFile())));
368 }
369 artifacts.addAll(signatures);
370
371
372 try {
373 deploy(deploymentRepository, artifacts);
374 } catch (DeploymentException e) {
375 throw new MojoExecutionException(
376 "Error deploying attached artifacts " + artifacts + ": " + e.getMessage(), e);
377 }
378 }
379
380
381
382
383
384
385 private void processModel(Model model) {
386 Parent parent = model.getParent();
387
388 if (this.groupId == null) {
389 this.groupId = model.getGroupId();
390 if (this.groupId == null && parent != null) {
391 this.groupId = parent.getGroupId();
392 }
393 }
394 if (this.artifactId == null) {
395 this.artifactId = model.getArtifactId();
396 }
397 if (this.version == null) {
398 this.version = model.getVersion();
399 if (this.version == null && parent != null) {
400 this.version = parent.getVersion();
401 }
402 }
403 if (this.packaging == null) {
404 this.packaging = model.getPackaging();
405 }
406 }
407
408
409
410
411
412
413
414
415 private Model readModel(File pomFile) throws MojoExecutionException {
416 try (Reader reader = ReaderFactory.newXmlReader(pomFile)) {
417 return new MavenXpp3Reader().read(reader);
418 } catch (FileNotFoundException e) {
419 throw new MojoExecutionException("POM not found " + pomFile, e);
420 } catch (IOException e) {
421 throw new MojoExecutionException("Error reading POM " + pomFile, e);
422 } catch (XmlPullParserException e) {
423 throw new MojoExecutionException("Error parsing POM " + pomFile, e);
424 }
425 }
426
427
428
429
430
431
432
433 private File generatePomFile() throws MojoExecutionException {
434 Model model = generateModel();
435
436 try {
437 File tempFile = Files.createTempFile("mvndeploy", ".pom").toFile();
438 tempFile.deleteOnExit();
439
440 try (Writer fw = WriterFactory.newXmlWriter(tempFile)) {
441 new MavenXpp3Writer().write(fw, model);
442 }
443
444 return tempFile;
445 } catch (IOException e) {
446 throw new MojoExecutionException("Error writing temporary pom file: " + e.getMessage(), e);
447 }
448 }
449
450
451
452
453
454
455 private void validateArtifactInformation() throws MojoFailureException {
456 Model model = generateModel();
457
458 ModelBuildingRequest request =
459 new DefaultModelBuildingRequest().setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_STRICT);
460
461 List<String> result = new ArrayList<>();
462
463 SimpleModelProblemCollector problemCollector = new SimpleModelProblemCollector(result);
464
465 modelValidator.validateEffectiveModel(model, request, problemCollector);
466
467 if (!result.isEmpty()) {
468 StringBuilder msg = new StringBuilder("The artifact information is incomplete or not valid:\n");
469 for (String e : result) {
470 msg.append(" - " + e + '\n');
471 }
472 throw new MojoFailureException(msg.toString());
473 }
474 }
475
476
477
478
479
480
481 private Model generateModel() {
482 Model model = new Model();
483
484 model.setModelVersion("4.0.0");
485
486 model.setGroupId(groupId);
487 model.setArtifactId(artifactId);
488 model.setVersion(version);
489 model.setPackaging(packaging);
490
491 model.setDescription(description);
492
493 return model;
494 }
495
496
497
498
499
500
501
502
503 protected void deploy(RemoteRepository deploymentRepository, List<Artifact> artifacts) throws DeploymentException {
504 int retryFailedDeploymentCount = Math.max(1, Math.min(10, this.retryFailedDeploymentCount));
505 DeploymentException exception = null;
506 for (int count = 0; count < retryFailedDeploymentCount; count++) {
507 try {
508 if (count > 0) {
509
510 getLog().info("Retrying deployment attempt " + (count + 1) + " of " + retryFailedDeploymentCount);
511
512 }
513 DeployRequest deployRequest = new DeployRequest();
514 deployRequest.setRepository(deploymentRepository);
515 deployRequest.setArtifacts(artifacts);
516
517 repositorySystem.deploy(session.getRepositorySession(), deployRequest);
518 exception = null;
519 break;
520 } catch (DeploymentException e) {
521 if (count + 1 < retryFailedDeploymentCount) {
522 getLog().warn("Encountered issue during deployment: " + e.getLocalizedMessage());
523 getLog().debug(e);
524 }
525 if (exception == null) {
526 exception = e;
527 }
528 }
529 }
530 if (exception != null) {
531 throw exception;
532 }
533 }
534
535 private static class SimpleModelProblemCollector implements ModelProblemCollector {
536
537 private final List<String> result;
538
539 SimpleModelProblemCollector(List<String> result) {
540 this.result = result;
541 }
542
543 public void add(ModelProblemCollectorRequest req) {
544 if (!ModelProblem.Severity.WARNING.equals(req.getSeverity())) {
545 result.add(req.getMessage());
546 }
547 }
548 }
549 }