1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.site.deploy;
20
21 import java.io.File;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.Set;
27
28 import org.apache.maven.artifact.manager.WagonManager;
29 import org.apache.maven.doxia.site.decoration.inheritance.URIPathDescriptor;
30 import org.apache.maven.execution.MavenExecutionRequest;
31 import org.apache.maven.execution.MavenSession;
32 import org.apache.maven.model.DistributionManagement;
33 import org.apache.maven.model.Site;
34 import org.apache.maven.plugin.MojoExecutionException;
35 import org.apache.maven.plugins.annotations.Component;
36 import org.apache.maven.plugins.annotations.Parameter;
37 import org.apache.maven.plugins.site.AbstractSiteMojo;
38 import org.apache.maven.project.MavenProject;
39 import org.apache.maven.settings.Proxy;
40 import org.apache.maven.settings.Settings;
41 import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
42 import org.apache.maven.settings.crypto.SettingsDecrypter;
43 import org.apache.maven.settings.crypto.SettingsDecryptionResult;
44 import org.apache.maven.wagon.CommandExecutionException;
45 import org.apache.maven.wagon.CommandExecutor;
46 import org.apache.maven.wagon.ConnectionException;
47 import org.apache.maven.wagon.ResourceDoesNotExistException;
48 import org.apache.maven.wagon.TransferFailedException;
49 import org.apache.maven.wagon.UnsupportedProtocolException;
50 import org.apache.maven.wagon.Wagon;
51 import org.apache.maven.wagon.authentication.AuthenticationException;
52 import org.apache.maven.wagon.authentication.AuthenticationInfo;
53 import org.apache.maven.wagon.authorization.AuthorizationException;
54 import org.apache.maven.wagon.observers.Debug;
55 import org.apache.maven.wagon.proxy.ProxyInfo;
56 import org.apache.maven.wagon.repository.Repository;
57 import org.codehaus.plexus.PlexusConstants;
58 import org.codehaus.plexus.PlexusContainer;
59 import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
60 import org.codehaus.plexus.context.Context;
61 import org.codehaus.plexus.context.ContextException;
62 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
63 import org.codehaus.plexus.util.StringUtils;
64
65
66
67
68
69
70
71
72 public abstract class AbstractDeployMojo extends AbstractSiteMojo implements Contextualizable {
73
74
75
76
77
78 @Parameter(alias = "outputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true)
79 private File inputDirectory;
80
81
82
83
84
85
86
87 @Parameter(property = "maven.site.chmod", defaultValue = "true")
88 private boolean chmod;
89
90
91
92
93
94
95
96 @Parameter(property = "maven.site.chmod.mode", defaultValue = "g+w,a+rX")
97 private String chmodMode;
98
99
100
101
102
103
104
105 @Parameter(property = "maven.site.chmod.options", defaultValue = "-Rf")
106 private String chmodOptions;
107
108
109
110
111
112
113 @Parameter(property = "maven.site.deploy.skip", defaultValue = "false")
114 private boolean skipDeploy;
115
116
117
118 @Component
119 private WagonManager wagonManager;
120
121
122
123
124 @Parameter(defaultValue = "${settings}", readonly = true)
125 private Settings settings;
126
127
128
129
130 @Parameter(defaultValue = "${session}", readonly = true)
131 protected MavenSession mavenSession;
132
133 private String topDistributionManagementSiteUrl;
134
135 private Site deploySite;
136
137 private PlexusContainer container;
138
139
140
141
142 public void execute() throws MojoExecutionException {
143 if (skip && isDeploy()) {
144 getLog().info("maven.site.skip = true: Skipping site deployment");
145 return;
146 }
147
148 if (skipDeploy && isDeploy()) {
149 getLog().info("maven.site.deploy.skip = true: Skipping site deployment");
150 return;
151 }
152
153 deployTo(new Repository(getDeploySite().getId(), getDeploySite().getUrl()));
154 }
155
156
157
158
159
160
161
162
163 protected static String appendSlash(final String url) {
164 if (url.endsWith("/")) {
165 return url;
166 } else {
167 return url + "/";
168 }
169 }
170
171
172
173
174
175
176 protected abstract boolean isDeploy();
177
178
179
180
181
182
183
184
185
186
187 protected String getTopDistributionManagementSiteUrl() throws MojoExecutionException {
188 if (topDistributionManagementSiteUrl == null) {
189 topDistributionManagementSiteUrl = determineTopDistributionManagementSiteUrl();
190
191 if (!isDeploy()) {
192 getLog().debug("distributionManagement.site.url relative path: " + getDeployModuleDirectory());
193 }
194 }
195 return topDistributionManagementSiteUrl;
196 }
197
198 protected abstract String determineTopDistributionManagementSiteUrl() throws MojoExecutionException;
199
200
201
202
203
204
205
206
207
208
209 protected Site getDeploySite() throws MojoExecutionException {
210 if (deploySite == null) {
211 deploySite = determineDeploySite();
212 }
213 return deploySite;
214 }
215
216 protected abstract Site determineDeploySite() throws MojoExecutionException;
217
218
219
220
221
222
223
224 protected String getDeployModuleDirectory() throws MojoExecutionException {
225 String to = getSite(project).getUrl();
226
227 getLog().debug("Mapping url source calculation: ");
228 String from = getTopDistributionManagementSiteUrl();
229
230 String relative = siteTool.getRelativePath(to, from);
231
232
233
234 relative = relative.replace('\\', '/');
235
236 return ("".equals(relative)) ? "./" : relative;
237 }
238
239
240
241
242
243
244
245
246
247
248 private void deployTo(final Repository repository) throws MojoExecutionException {
249 if (!inputDirectory.exists()) {
250 throw new MojoExecutionException("The site does not exist, please run site:site first");
251 }
252
253 if (getLog().isDebugEnabled()) {
254 getLog().debug("Deploying to '" + repository.getUrl() + "',\n Using credentials from server id '"
255 + repository.getId() + "'");
256 }
257
258 deploy(inputDirectory, repository);
259 }
260
261 private void deploy(final File directory, final Repository repository) throws MojoExecutionException {
262
263 final Wagon wagon = getWagon(repository, wagonManager);
264
265 try {
266 SettingsDecrypter settingsDecrypter = container.lookup(SettingsDecrypter.class);
267
268 ProxyInfo proxyInfo = getProxy(repository, settingsDecrypter);
269
270 push(directory, repository, wagon, proxyInfo, getLocales(), getDeployModuleDirectory());
271
272 if (chmod) {
273 chmod(wagon, repository, chmodOptions, chmodMode);
274 }
275 } catch (ComponentLookupException cle) {
276 throw new MojoExecutionException("Unable to lookup SettingsDecrypter: " + cle.getMessage(), cle);
277 } finally {
278 try {
279 wagon.disconnect();
280 } catch (ConnectionException e) {
281 getLog().error("Error disconnecting wagon - ignored", e);
282 }
283 }
284 }
285
286 private Wagon getWagon(final Repository repository, final WagonManager manager) throws MojoExecutionException {
287 final Wagon wagon;
288
289 try {
290 wagon = manager.getWagon(repository);
291 } catch (UnsupportedProtocolException e) {
292 String shortMessage = "Unsupported protocol: '" + repository.getProtocol() + "' for site deployment to "
293 + "distributionManagement.site.url=" + repository.getUrl() + ".";
294 String longMessage =
295 "\n" + shortMessage + "\n" + "Currently supported protocols are: " + getSupportedProtocols() + ".\n"
296 + " Protocols may be added through wagon providers.\n" + " For more information, see "
297 + "https://maven.apache.org/plugins/maven-site-plugin/examples/adding-deploy-protocol.html";
298
299 getLog().error(longMessage);
300
301 throw new MojoExecutionException(shortMessage);
302 } catch (TransferFailedException e) {
303 throw new MojoExecutionException("Unable to configure Wagon: '" + repository.getProtocol() + "'", e);
304 }
305
306 if (!wagon.supportsDirectoryCopy()) {
307 throw new MojoExecutionException(
308 "Wagon protocol '" + repository.getProtocol() + "' doesn't support directory copying");
309 }
310
311 return wagon;
312 }
313
314 private String getSupportedProtocols() {
315 try {
316 Set<String> protocols = container.lookupMap(Wagon.class).keySet();
317
318 return StringUtils.join(protocols.iterator(), ", ");
319 } catch (ComponentLookupException e) {
320
321 getLog().error(e);
322 }
323 return "";
324 }
325
326 private void push(
327 final File inputDirectory,
328 final Repository repository,
329 final Wagon wagon,
330 final ProxyInfo proxyInfo,
331 final List<Locale> localesList,
332 final String relativeDir)
333 throws MojoExecutionException {
334 AuthenticationInfo authenticationInfo = wagonManager.getAuthenticationInfo(repository.getId());
335 getLog().debug("authenticationInfo with id '" + repository.getId() + "': "
336 + ((authenticationInfo == null) ? "-" : authenticationInfo.getUserName()));
337
338 try {
339 if (getLog().isDebugEnabled()) {
340 Debug debug = new Debug();
341
342 wagon.addSessionListener(debug);
343
344 wagon.addTransferListener(debug);
345 }
346
347 if (proxyInfo != null) {
348 getLog().debug("connect with proxyInfo");
349 wagon.connect(repository, authenticationInfo, proxyInfo);
350 } else if (proxyInfo == null && authenticationInfo != null) {
351 getLog().debug("connect with authenticationInfo and without proxyInfo");
352 wagon.connect(repository, authenticationInfo);
353 } else {
354 getLog().debug("connect without authenticationInfo and without proxyInfo");
355 wagon.connect(repository);
356 }
357
358 getLog().info("Pushing " + inputDirectory);
359
360
361 final Locale defaultLocale = localesList.get(0);
362
363 for (Locale locale : localesList) {
364 if (locale.equals(defaultLocale)) {
365
366
367 getLog().info(" >>> to " + appendSlash(repository.getUrl()) + relativeDir);
368
369 wagon.putDirectory(inputDirectory, relativeDir);
370 } else {
371 getLog().info(" >>> to " + appendSlash(repository.getUrl()) + locale + "/" + relativeDir);
372
373 wagon.putDirectory(new File(inputDirectory, locale.toString()), locale + "/" + relativeDir);
374 }
375 }
376 } catch (ResourceDoesNotExistException
377 | TransferFailedException
378 | AuthorizationException
379 | ConnectionException
380 | AuthenticationException e) {
381 throw new MojoExecutionException("Error uploading site", e);
382 }
383 }
384
385 private static void chmod(
386 final Wagon wagon, final Repository repository, final String chmodOptions, final String chmodMode)
387 throws MojoExecutionException {
388 try {
389 if (wagon instanceof CommandExecutor) {
390 CommandExecutor exec = (CommandExecutor) wagon;
391 exec.executeCommand("chmod " + chmodOptions + " " + chmodMode + " " + repository.getBasedir());
392 }
393
394 } catch (CommandExecutionException e) {
395 throw new MojoExecutionException("Error uploading site", e);
396 }
397 }
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418 public static ProxyInfo getProxyInfo(Repository repository, WagonManager wagonManager) {
419 ProxyInfo proxyInfo = wagonManager.getProxy(repository.getProtocol());
420
421 if (proxyInfo == null) {
422 return null;
423 }
424
425 String host = repository.getHost();
426 String nonProxyHostsAsString = proxyInfo.getNonProxyHosts();
427 for (String nonProxyHost : StringUtils.split(nonProxyHostsAsString, ",;|")) {
428 if (StringUtils.contains(nonProxyHost, "*")) {
429
430 final int pos = nonProxyHost.indexOf('*');
431 String nonProxyHostPrefix = nonProxyHost.substring(0, pos);
432 String nonProxyHostSuffix = nonProxyHost.substring(pos + 1);
433
434 if (StringUtils.isNotEmpty(nonProxyHostPrefix)
435 && host.startsWith(nonProxyHostPrefix)
436 && StringUtils.isEmpty(nonProxyHostSuffix)) {
437 return null;
438 }
439
440 if (StringUtils.isEmpty(nonProxyHostPrefix)
441 && StringUtils.isNotEmpty(nonProxyHostSuffix)
442 && host.endsWith(nonProxyHostSuffix)) {
443 return null;
444 }
445
446 if (StringUtils.isNotEmpty(nonProxyHostPrefix)
447 && host.startsWith(nonProxyHostPrefix)
448 && StringUtils.isNotEmpty(nonProxyHostSuffix)
449 && host.endsWith(nonProxyHostSuffix)) {
450 return null;
451 }
452 } else if (host.equals(nonProxyHost)) {
453 return null;
454 }
455 }
456 return proxyInfo;
457 }
458
459
460
461
462
463
464
465
466 private ProxyInfo getProxy(Repository repository, SettingsDecrypter settingsDecrypter) {
467 String protocol = repository.getProtocol();
468 String url = repository.getUrl();
469
470 getLog().debug("repository protocol " + protocol);
471
472 String originalProtocol = protocol;
473
474
475
476
477 if (StringUtils.equalsIgnoreCase("dav", protocol) && url.startsWith("dav:")) {
478 url = url.substring(4);
479 if (url.startsWith("http")) {
480 try {
481 URL urlSite = new URL(url);
482 protocol = urlSite.getProtocol();
483 getLog().debug("found dav protocol so transform to real transport protocol " + protocol);
484 } catch (MalformedURLException e) {
485 getLog().warn("fail to build URL with " + url);
486 }
487 }
488 } else {
489 getLog().debug("getProxy 'protocol': " + protocol);
490 }
491
492 if (mavenSession != null && protocol != null) {
493 MavenExecutionRequest request = mavenSession.getRequest();
494
495 if (request != null) {
496 List<Proxy> proxies = request.getProxies();
497
498 if (proxies != null) {
499 for (Proxy proxy : proxies) {
500 if (proxy.isActive()
501 && (protocol.equalsIgnoreCase(proxy.getProtocol())
502 || originalProtocol.equalsIgnoreCase(proxy.getProtocol()))) {
503 SettingsDecryptionResult result =
504 settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(proxy));
505 proxy = result.getProxy();
506
507 ProxyInfo proxyInfo = new ProxyInfo();
508 proxyInfo.setHost(proxy.getHost());
509
510
511 proxyInfo.setType(protocol);
512 proxyInfo.setPort(proxy.getPort());
513 proxyInfo.setNonProxyHosts(proxy.getNonProxyHosts());
514 proxyInfo.setUserName(proxy.getUsername());
515 proxyInfo.setPassword(proxy.getPassword());
516
517 getLog().debug("found proxyInfo "
518 + ("host:port " + proxyInfo.getHost() + ":" + proxyInfo.getPort() + ", "
519 + proxyInfo.getUserName()));
520
521 return proxyInfo;
522 }
523 }
524 }
525 }
526 }
527 getLog().debug("getProxy 'protocol': " + protocol + " no ProxyInfo found");
528 return null;
529 }
530
531
532
533
534 public void contextualize(Context context) throws ContextException {
535 container = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY);
536 }
537
538
539
540
541
542
543
544
545
546 protected static Site getSite(final MavenProject project) throws MojoExecutionException {
547 final DistributionManagement distributionManagement = project.getDistributionManagement();
548
549 if (distributionManagement == null) {
550 throw new MojoExecutionException("Missing distribution management in project " + getFullName(project));
551 }
552
553 final Site site = distributionManagement.getSite();
554
555 if (site == null) {
556 throw new MojoExecutionException(
557 "Missing site information in the distribution management of the project " + getFullName(project));
558 }
559
560 if (site.getUrl() == null || site.getId() == null) {
561 throw new MojoExecutionException(
562 "Missing site data: specify url and id for project " + getFullName(project));
563 }
564
565 return site;
566 }
567
568 private static String getFullName(MavenProject project) {
569 return project.getName() + " (" + project.getGroupId() + ':' + project.getArtifactId() + ':'
570 + project.getVersion() + ')';
571 }
572
573
574
575
576
577
578
579
580
581
582
583
584
585 protected MavenProject getTopLevelProject(MavenProject project) throws MojoExecutionException {
586 Site site = getSite(project);
587
588 MavenProject parent = project;
589
590 while (parent.getParent() != null) {
591 MavenProject oldProject = parent;
592
593 parent = parent.getParent();
594
595 Site oldSite = site;
596
597 try {
598 site = getSite(parent);
599 } catch (MojoExecutionException e) {
600 return oldProject;
601 }
602
603
604 URIPathDescriptor siteURI = new URIPathDescriptor(URIEncoder.encodeURI(site.getUrl()), "");
605 URIPathDescriptor oldSiteURI = new URIPathDescriptor(URIEncoder.encodeURI(oldSite.getUrl()), "");
606
607 if (!siteURI.sameSite(oldSiteURI.getBaseURI())) {
608 return oldProject;
609 }
610 }
611
612 return parent;
613 }
614
615 private static class URIEncoder {
616 private static final String MARK = "-_.!~*'()";
617 private static final String RESERVED = ";/?:@&=+$,";
618
619 private static String encodeURI(final String uriString) {
620 final char[] chars = uriString.toCharArray();
621 final StringBuilder uri = new StringBuilder(chars.length);
622
623
624 if (uriString.startsWith("dav:http")) {
625
626 chars[3] = '-';
627 }
628
629 for (char c : chars) {
630 if ((c >= '0' && c <= '9')
631 || (c >= 'a' && c <= 'z')
632 || (c >= 'A' && c <= 'Z')
633 || MARK.indexOf(c) != -1
634 || RESERVED.indexOf(c) != -1) {
635 uri.append(c);
636 } else {
637 uri.append('%');
638 uri.append(Integer.toHexString((int) c));
639 }
640 }
641 return uri.toString();
642 }
643 }
644 }