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