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