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