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