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