View Javadoc

1   package org.apache.maven.plugins.site;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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.project.MavenProject;
33  import org.apache.maven.settings.Proxy;
34  import org.apache.maven.settings.Server;
35  import org.apache.maven.settings.Settings;
36  import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
37  import org.apache.maven.settings.crypto.SettingsDecrypter;
38  import org.apache.maven.settings.crypto.SettingsDecryptionResult;
39  import org.apache.maven.wagon.CommandExecutionException;
40  import org.apache.maven.wagon.CommandExecutor;
41  import org.apache.maven.wagon.ConnectionException;
42  import org.apache.maven.wagon.ResourceDoesNotExistException;
43  import org.apache.maven.wagon.TransferFailedException;
44  import org.apache.maven.wagon.UnsupportedProtocolException;
45  import org.apache.maven.wagon.Wagon;
46  import org.apache.maven.wagon.authentication.AuthenticationException;
47  import org.apache.maven.wagon.authentication.AuthenticationInfo;
48  import org.apache.maven.wagon.authorization.AuthorizationException;
49  import org.apache.maven.wagon.observers.Debug;
50  import org.apache.maven.wagon.proxy.ProxyInfo;
51  import org.apache.maven.wagon.repository.Repository;
52  import org.codehaus.classworlds.ClassRealm;
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.lang.reflect.Method;
69  import java.net.MalformedURLException;
70  import java.net.URL;
71  import java.util.List;
72  import java.util.Locale;
73  import java.util.Set;
74  
75  /**
76   * Abstract base class for deploy mojos.
77   * Since 2.3 this includes {@link SiteStageMojo} and {@link SiteStageDeployMojo}.
78   *
79   * @author ltheussl
80   * @since 2.3
81   */
82  public abstract class AbstractDeployMojo
83      extends AbstractSiteMojo
84      implements Contextualizable
85  {
86      /**
87       * Directory containing the generated project sites and report distributions.
88       */
89      @Parameter( alias = "outputDirectory", property = "project.reporting.outputDirectory", required = true )
90      private File inputDirectory;
91  
92      /**
93       * Whether to run the "chmod" command on the remote site after the deploy.
94       * Defaults to "true".
95       *
96       * @since 2.1
97       */
98      @Parameter( property = "maven.site.chmod", defaultValue = "true" )
99      private boolean chmod;
100 
101     /**
102      * The mode used by the "chmod" command. Only used if chmod = true.
103      * Defaults to "g+w,a+rX".
104      *
105      * @since 2.1
106      */
107     @Parameter( property = "maven.site.chmod.mode", defaultValue = "g+w,a+rX" )
108     private String chmodMode;
109 
110     /**
111      * The options used by the "chmod" command. Only used if chmod = true.
112      * Defaults to "-Rf".
113      *
114      * @since 2.1
115      */
116     @Parameter( property = "maven.site.chmod.options", defaultValue = "-Rf" )
117     private String chmodOptions;
118 
119     /**
120      * Set this to 'true' to skip site deployment.
121      *
122      * @since 3.0
123      */
124     @Parameter( property = "maven.site.deploy.skip", defaultValue = "false" )
125 
126     private boolean skipDeploy;
127 
128     /**
129      */
130     @Component
131     private WagonManager wagonManager;
132 
133     /**
134      * The current user system settings for use in Maven.
135      */
136     @Component
137     private Settings settings;
138 
139     /**
140      * @since 3.0-beta-2
141      */
142     @Component
143     protected MavenSession mavenSession;
144 
145     private PlexusContainer container;
146 
147     /**
148      * The String "staging/".
149      */
150     protected static final String DEFAULT_STAGING_DIRECTORY = "staging/";
151 
152     /**
153      * {@inheritDoc}
154      */
155     public void execute()
156         throws MojoExecutionException
157     {
158         if ( skipDeploy && isDeploy() )
159         {
160             getLog().info( "maven.site.deploy.skip = true: Skipping site deployment" );
161             return;
162         }
163 
164         deployTo( new org.apache.maven.plugins.site.wagon.repository.Repository( getDeployRepositoryID(), appendSlash(
165             getDeployRepositoryURL() ) ) );
166     }
167 
168     /**
169      * Make sure the given url ends with a slash.
170      *
171      * @param url a String.
172      * @return if url already ends with '/' it is returned unchanged,
173      *         otherwise a '/' character is appended.
174      */
175     protected static String appendSlash( final String url )
176     {
177         if ( url.endsWith( "/" ) )
178         {
179             return url;
180         }
181         else
182         {
183             return url + "/";
184         }
185     }
186 
187     /**
188      * Specifies the id to look up credential settings.
189      *
190      * @return the id to look up credentials for the deploy. Not null.
191      * @throws MojoExecutionException if the ID cannot be determined
192      */
193     protected abstract String getDeployRepositoryID()
194         throws MojoExecutionException;
195 
196     /**
197      * Specifies the target URL for the deploy.
198      * This should be the top-level URL, ie above modules and locale sub-directories.
199      *
200      * @return the url to deploy to. Not null.
201      * @throws MojoExecutionException if the URL cannot be constructed
202      */
203     protected abstract String getDeployRepositoryURL()
204         throws MojoExecutionException;
205 
206     /**
207      * Find the relative path between the distribution URLs of the top parent and the current project.
208      *
209      * @return the relative path or "./" if the two URLs are the same.
210      * @throws MojoExecutionException
211      */
212     protected String getDeployModuleDirectory()
213         throws MojoExecutionException
214     {
215         String relative = siteTool.getRelativePath( getSite( project ).getUrl(), getRootSite( project ).getUrl() );
216 
217         // SiteTool.getRelativePath() uses File.separatorChar,
218         // so we need to convert '\' to '/' in order for the URL to be valid for Windows users
219         relative = relative.replace( '\\', '/' );
220 
221         return ( "".equals( relative ) ) ? "./" : relative;
222     }
223 
224     /**
225      * Use wagon to deploy the generated site to a given repository.
226      *
227      * @param repository the repository to deploy to.
228      *                   This needs to contain a valid, non-null {@link Repository#getId() id}
229      *                   to look up credentials for the deploy, and a valid, non-null
230      *                   {@link Repository#getUrl() scm url} to deploy to.
231      * @throws MojoExecutionException if the deploy fails.
232      */
233     private void deployTo( final Repository repository )
234         throws MojoExecutionException
235     {
236         if ( !inputDirectory.exists() )
237         {
238             throw new MojoExecutionException( "The site does not exist, please run site:site first" );
239         }
240 
241         if ( getLog().isDebugEnabled() )
242         {
243             getLog().debug( "Deploying to '" + repository.getUrl() + "',\n    Using credentials from server id '"
244                                 + repository.getId() + "'" );
245         }
246 
247         deploy( inputDirectory, repository );
248     }
249 
250     private void deploy( final File directory, final Repository repository )
251         throws MojoExecutionException
252     {
253         // TODO: work on moving this into the deployer like the other deploy methods
254         final Wagon wagon = getWagon( repository, wagonManager );
255 
256         try
257         {
258             configureWagon( wagon, repository.getId(), settings, container, getLog() );
259         }
260         catch ( TransferFailedException e )
261         {
262             throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e );
263         }
264 
265         try
266         {
267             final ProxyInfo proxyInfo;
268             if ( !isMaven3OrMore() )
269             {
270                 proxyInfo = getProxyInfo( repository, wagonManager );
271             }
272             else
273             {
274                 try
275                 {
276                     SettingsDecrypter settingsDecrypter = container.lookup( SettingsDecrypter.class );
277 
278                     proxyInfo = getProxy( repository, settingsDecrypter );
279                 }
280                 catch ( ComponentLookupException cle )
281                 {
282                     throw new MojoExecutionException( "Unable to lookup SettingsDecrypter: " + cle.getMessage(), cle );
283                 }
284             }
285 
286             push( directory, repository, wagon, proxyInfo, siteTool.getAvailableLocales( locales ),
287                   getDeployModuleDirectory() );
288 
289             if ( chmod )
290             {
291                 chmod( wagon, repository, chmodOptions, chmodMode );
292             }
293         }
294         finally
295         {
296             try
297             {
298                 wagon.disconnect();
299             }
300             catch ( ConnectionException e )
301             {
302                 getLog().error( "Error disconnecting wagon - ignored", e );
303             }
304         }
305     }
306 
307     /**
308      * Find the build directory of the top level project in the reactor.
309      * If no top level project is found, the build directory of the current project is returned.
310      *
311      * @return the build directory of the top level project.
312      */
313     protected File getTopLevelBuildDirectory()
314     {
315         // Find the top level project in the reactor
316         final MavenProject topLevelProject = getTopLevelProject( reactorProjects );
317 
318         // Use the top level project's build directory if there is one, otherwise use this project's build directory
319         final File buildDirectory;
320 
321         if ( topLevelProject == null )
322         {
323             getLog().debug( "No top level project found in the reactor, using the current project." );
324 
325             buildDirectory = new File( project.getBuild().getDirectory() );
326         }
327         else
328         {
329             getLog().debug( "Using the top level project found in the reactor." );
330 
331             buildDirectory = new File( topLevelProject.getBuild().getDirectory() );
332         }
333 
334         return buildDirectory;
335     }
336 
337     private Wagon getWagon( final Repository repository, final WagonManager manager )
338         throws MojoExecutionException
339     {
340         final Wagon wagon;
341 
342         try
343         {
344             wagon = manager.getWagon( repository );
345         }
346         catch ( UnsupportedProtocolException e )
347         {
348             String shortMessage = "Unsupported protocol: '" + repository.getProtocol() + "' for site deployment to "
349                 + "distributionManagement.site.url=" + repository.getUrl() + ".";
350             String longMessage =
351                 "\n" + shortMessage + "\n" + "Currently supported protocols are: " + getSupportedProtocols() + ".\n"
352                     + "    Protocols may be added through wagon providers.\n" + "    For more information, see "
353                     + "http://maven.apache.org/plugins/maven-site-plugin/examples/adding-deploy-protocol.html";
354 
355             getLog().error( longMessage );
356 
357             throw new MojoExecutionException( shortMessage );
358         }
359         catch ( TransferFailedException e )
360         {
361             throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e );
362         }
363 
364         if ( !wagon.supportsDirectoryCopy() )
365         {
366             throw new MojoExecutionException(
367                 "Wagon protocol '" + repository.getProtocol() + "' doesn't support directory copying" );
368         }
369 
370         return wagon;
371     }
372 
373     private String getSupportedProtocols()
374     {
375         try
376         {
377             Set<String> protocols = container.lookupMap( Wagon.class ).keySet();
378 
379             return StringUtils.join( protocols.iterator(), ", " );
380         }
381         catch ( ComponentLookupException e )
382         {
383             // in the unexpected case there is a problem when instantiating a wagon provider
384             getLog().error( e );
385         }
386         return "";
387     }
388 
389     private void push( final File inputDirectory, final Repository repository, final Wagon wagon,
390                        final ProxyInfo proxyInfo, final List<Locale> localesList, final String relativeDir )
391         throws MojoExecutionException
392     {
393         AuthenticationInfo authenticationInfo = wagonManager.getAuthenticationInfo( repository.getId() );
394         getLog().debug( "authenticationInfo with id '" + repository.getId() + "': "
395                             + ( ( authenticationInfo == null ) ? "-" : authenticationInfo.getUserName() ) );
396 
397         try
398         {
399             Debug debug = new Debug();
400 
401             wagon.addSessionListener( debug );
402 
403             wagon.addTransferListener( debug );
404 
405             if ( proxyInfo != null )
406             {
407                 getLog().debug( "connect with proxyInfo" );
408                 wagon.connect( repository, authenticationInfo, proxyInfo );
409             }
410             else if ( proxyInfo == null && authenticationInfo != null )
411             {
412                 getLog().debug( "connect with authenticationInfo and without proxyInfo" );
413                 wagon.connect( repository, authenticationInfo );
414             }
415             else
416             {
417                 getLog().debug( "connect without authenticationInfo and without proxyInfo" );
418                 wagon.connect( repository );
419             }
420 
421             getLog().info( "Pushing " + inputDirectory );
422 
423             // Default is first in the list
424             final String defaultLocale = localesList.get( 0 ).getLanguage();
425 
426             for ( Locale locale : localesList )
427             {
428                 if ( locale.getLanguage().equals( defaultLocale ) )
429                 {
430                     // TODO: this also uploads the non-default locales,
431                     // is there a way to exclude directories in wagon?
432                     getLog().info( "   >>> to " + repository.getUrl() + relativeDir );
433 
434                     wagon.putDirectory( inputDirectory, relativeDir );
435                 }
436                 else
437                 {
438                     getLog().info( "   >>> to " + repository.getUrl() + locale.getLanguage() + "/" + relativeDir );
439 
440                     wagon.putDirectory( new File( inputDirectory, locale.getLanguage() ),
441                                         locale.getLanguage() + "/" + relativeDir );
442                 }
443             }
444         }
445         catch ( ResourceDoesNotExistException e )
446         {
447             throw new MojoExecutionException( "Error uploading site", e );
448         }
449         catch ( TransferFailedException e )
450         {
451             throw new MojoExecutionException( "Error uploading site", e );
452         }
453         catch ( AuthorizationException e )
454         {
455             throw new MojoExecutionException( "Error uploading site", e );
456         }
457         catch ( ConnectionException e )
458         {
459             throw new MojoExecutionException( "Error uploading site", e );
460         }
461         catch ( AuthenticationException e )
462         {
463             throw new MojoExecutionException( "Error uploading site", e );
464         }
465     }
466 
467     private static void chmod( final Wagon wagon, final Repository repository, final String chmodOptions,
468                                final String chmodMode )
469         throws MojoExecutionException
470     {
471         try
472         {
473             if ( wagon instanceof CommandExecutor )
474             {
475                 CommandExecutor exec = (CommandExecutor) wagon;
476                 exec.executeCommand( "chmod " + chmodOptions + " " + chmodMode + " " + repository.getBasedir() );
477             }
478             // else ? silently ignore, FileWagon is not a CommandExecutor!
479         }
480         catch ( CommandExecutionException e )
481         {
482             throw new MojoExecutionException( "Error uploading site", e );
483         }
484     }
485 
486     /**
487      * <p>
488      * Get the <code>ProxyInfo</code> of the proxy associated with the <code>host</code>
489      * and the <code>protocol</code> of the given <code>repository</code>.
490      * </p>
491      * <p>
492      * Extract from <a href="http://java.sun.com/j2se/1.5.0/docs/guide/net/properties.html">
493      * J2SE Doc : Networking Properties - nonProxyHosts</a> : "The value can be a list of hosts,
494      * each separated by a |, and in addition a wildcard character (*) can be used for matching"
495      * </p>
496      * <p>
497      * Defensively support for comma (",") and semi colon (";") in addition to pipe ("|") as separator.
498      * </p>
499      *
500      * @param repository   the Repository to extract the ProxyInfo from.
501      * @param wagonManager the WagonManager used to connect to the Repository.
502      * @return a ProxyInfo object instantiated or <code>null</code> if no matching proxy is found
503      */
504     public static ProxyInfo getProxyInfo( Repository repository, WagonManager wagonManager )
505     {
506         ProxyInfo proxyInfo = wagonManager.getProxy( repository.getProtocol() );
507 
508         if ( proxyInfo == null )
509         {
510             return null;
511         }
512 
513         String host = repository.getHost();
514         String nonProxyHostsAsString = proxyInfo.getNonProxyHosts();
515         for ( String nonProxyHost : StringUtils.split( nonProxyHostsAsString, ",;|" ) )
516         {
517             if ( StringUtils.contains( nonProxyHost, "*" ) )
518             {
519                 // Handle wildcard at the end, beginning or middle of the nonProxyHost
520                 final int pos = nonProxyHost.indexOf( '*' );
521                 String nonProxyHostPrefix = nonProxyHost.substring( 0, pos );
522                 String nonProxyHostSuffix = nonProxyHost.substring( pos + 1 );
523                 // prefix*
524                 if ( StringUtils.isNotEmpty( nonProxyHostPrefix ) && host.startsWith( nonProxyHostPrefix )
525                     && StringUtils.isEmpty( nonProxyHostSuffix ) )
526                 {
527                     return null;
528                 }
529                 // *suffix
530                 if ( StringUtils.isEmpty( nonProxyHostPrefix ) && StringUtils.isNotEmpty( nonProxyHostSuffix )
531                     && host.endsWith( nonProxyHostSuffix ) )
532                 {
533                     return null;
534                 }
535                 // prefix*suffix
536                 if ( StringUtils.isNotEmpty( nonProxyHostPrefix ) && host.startsWith( nonProxyHostPrefix )
537                     && StringUtils.isNotEmpty( nonProxyHostSuffix ) && host.endsWith( nonProxyHostSuffix ) )
538                 {
539                     return null;
540                 }
541             }
542             else if ( host.equals( nonProxyHost ) )
543             {
544                 return null;
545             }
546         }
547         return proxyInfo;
548     }
549 
550     /**
551      * Get proxy information for Maven 3.
552      *
553      * @param repository
554      * @param settingsDecrypter
555      * @return
556      */
557     private ProxyInfo getProxy( Repository repository, SettingsDecrypter settingsDecrypter )
558     {
559         String protocol = repository.getProtocol();
560         String url = repository.getUrl();
561 
562         getLog().debug( "repository protocol " + protocol );
563 
564         String originalProtocol = protocol;
565         // olamy: hackish here protocol (wagon hint in fact !) is dav
566         // but the real protocol (transport layer) is http(s)
567         // and it's the one use in wagon to find the proxy arghhh
568         // so we will check both
569         if ( StringUtils.equalsIgnoreCase( "dav", protocol ) && url.startsWith( "dav:" ) )
570         {
571             url = url.substring( 4 );
572             if ( url.startsWith( "http" ) )
573             {
574                 try
575                 {
576                     URL urlSite = new URL( url );
577                     protocol = urlSite.getProtocol();
578                     getLog().debug( "found dav protocol so transform to real transport protocol " + protocol );
579                 }
580                 catch ( MalformedURLException e )
581                 {
582                     getLog().warn( "fail to build URL with " + url );
583                 }
584 
585             }
586         }
587         else
588         {
589             getLog().debug( "getProxy 'protocol': " + protocol );
590         }
591         if ( mavenSession != null && protocol != null )
592         {
593             MavenExecutionRequest request = mavenSession.getRequest();
594 
595             if ( request != null )
596             {
597                 List<Proxy> proxies = request.getProxies();
598 
599                 if ( proxies != null )
600                 {
601                     for ( Proxy proxy : proxies )
602                     {
603                         if ( proxy.isActive() && ( protocol.equalsIgnoreCase( proxy.getProtocol() )
604                             || originalProtocol.equalsIgnoreCase( proxy.getProtocol() ) ) )
605                         {
606                             SettingsDecryptionResult result =
607                                 settingsDecrypter.decrypt( new DefaultSettingsDecryptionRequest( proxy ) );
608                             proxy = result.getProxy();
609 
610                             ProxyInfo proxyInfo = new ProxyInfo();
611                             proxyInfo.setHost( proxy.getHost() );
612                             // so hackish for wagon the protocol is https for site dav : dav:https://dav.codehaus.org/mojo/
613                             proxyInfo.setType( protocol ); //proxy.getProtocol() );
614                             proxyInfo.setPort( proxy.getPort() );
615                             proxyInfo.setNonProxyHosts( proxy.getNonProxyHosts() );
616                             proxyInfo.setUserName( proxy.getUsername() );
617                             proxyInfo.setPassword( proxy.getPassword() );
618 
619                             getLog().debug( "found proxyInfo " + ( proxyInfo == null
620                                 ? "null"
621                                 : "host:port " + proxyInfo.getHost() + ":" + proxyInfo.getPort() + ", "
622                                     + proxyInfo.getUserName() ) );
623 
624                             return proxyInfo;
625                         }
626                     }
627                 }
628             }
629         }
630         getLog().debug( "getProxy 'protocol': " + protocol + " no ProxyInfo found" );
631         return null;
632     }
633 
634     /**
635      * Configure the Wagon with the information from serverConfigurationMap ( which comes from settings.xml )
636      *
637      * @param wagon
638      * @param repositoryId
639      * @param settings
640      * @param container
641      * @param log
642      * @throws TransferFailedException
643      * @todo Remove when {@link WagonManager#getWagon(Repository) is available}. It's available in Maven 2.0.5.
644      */
645     private static void configureWagon( Wagon wagon, String repositoryId, Settings settings, PlexusContainer container,
646                                         Log log )
647         throws TransferFailedException
648     {
649         log.debug( " configureWagon " );
650 
651         // MSITE-25: Make sure that the server settings are inserted
652         for ( Server server : settings.getServers() )
653         {
654             String id = server.getId();
655 
656             log.debug( "configureWagon server " + id );
657 
658             if ( id != null && id.equals( repositoryId ) && ( server.getConfiguration() != null ) )
659             {
660                 final PlexusConfiguration plexusConf =
661                     new XmlPlexusConfiguration( (Xpp3Dom) server.getConfiguration() );
662 
663                 ComponentConfigurator componentConfigurator = null;
664                 try
665                 {
666                     componentConfigurator =
667                         (ComponentConfigurator) container.lookup( ComponentConfigurator.ROLE, "basic" );
668                     if ( isMaven3OrMore() )
669                     {
670                         componentConfigurator.configureComponent( wagon, plexusConf,
671                                                                   container.getContainerRealm() );
672                     }
673                     else
674                     {
675                         configureWagonWithMaven2( componentConfigurator, wagon, plexusConf, container );
676                     }
677                 }
678                 catch ( final ComponentLookupException e )
679                 {
680                     throw new TransferFailedException(
681                         "While configuring wagon for \'" + repositoryId + "\': Unable to lookup wagon configurator."
682                             + " Wagon configuration cannot be applied.", e );
683                 }
684                 catch ( ComponentConfigurationException e )
685                 {
686                     throw new TransferFailedException( "While configuring wagon for \'" + repositoryId
687                                                            + "\': Unable to apply wagon configuration.", e );
688                 }
689                 finally
690                 {
691                     if ( componentConfigurator != null )
692                     {
693                         try
694                         {
695                             container.release( componentConfigurator );
696                         }
697                         catch ( ComponentLifecycleException e )
698                         {
699                             log.error( "Problem releasing configurator - ignoring: " + e.getMessage() );
700                         }
701                     }
702                 }
703             }
704         }
705     }
706 
707     private static void configureWagonWithMaven2( ComponentConfigurator componentConfigurator, Wagon wagon,
708                                                   PlexusConfiguration plexusConf, PlexusContainer container )
709         throws ComponentConfigurationException
710     {
711         // in maven 2.x   :
712         // * container.getContainerRealm() -> org.codehaus.classworlds.ClassRealm
713         // * componentConfiguration 3rd param is org.codehaus.classworlds.ClassRealm
714         // so use some reflection see MSITE-609
715         try
716         {
717             Method methodContainerRealm = container.getClass().getMethod( "getContainerRealm" );
718             ClassRealm realm = (ClassRealm) methodContainerRealm.invoke( container, null );
719 
720             Method methodConfigure = componentConfigurator.getClass().getMethod( "configureComponent",
721                                                                                  new Class[]{ Object.class,
722                                                                                      PlexusConfiguration.class,
723                                                                                      ClassRealm.class } );
724 
725             methodConfigure.invoke( componentConfigurator, wagon, plexusConf, realm );
726         }
727         catch ( Exception e )
728         {
729             throw new ComponentConfigurationException(
730                 "fail to configure wagon component for a maven2 use " + e.getMessage(), e );
731         }
732     }
733 
734     /**
735      * {@inheritDoc}
736      */
737     public void contextualize( Context context )
738         throws ContextException
739     {
740         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
741     }
742 
743     /**
744      * Find the top level parent in the reactor, i.e. the execution root.
745      *
746      * @param reactorProjects The projects in the reactor. May be null in which case null is returned.
747      * @return The top level project in the reactor, or <code>null</code> if none can be found
748      */
749     private static MavenProject getTopLevelProject( List<MavenProject> reactorProjects )
750     {
751         if ( reactorProjects == null )
752         {
753             return null;
754         }
755 
756         for ( MavenProject reactorProject : reactorProjects )
757         {
758             if ( reactorProject.isExecutionRoot() )
759             {
760                 return reactorProject;
761             }
762         }
763 
764         return null;
765     }
766 
767     /**
768      * Extract the distributionManagement site from the given MavenProject.
769      *
770      * @param project the MavenProject. Not null.
771      * @return the project site. Not null.
772      *         Also site.getUrl() and site.getId() are guaranteed to be not null.
773      * @throws MojoExecutionException if any of the site info is missing.
774      */
775     protected static Site getSite( final MavenProject project )
776         throws MojoExecutionException
777     {
778         final String name =
779             project.getName() + " (" + project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion()
780                 + ")";
781 
782         final DistributionManagement distributionManagement = project.getDistributionManagement();
783 
784         if ( distributionManagement == null )
785         {
786             throw new MojoExecutionException( "Missing distribution management in project " + name );
787         }
788 
789         final Site site = distributionManagement.getSite();
790 
791         if ( site == null )
792         {
793             throw new MojoExecutionException(
794                 "Missing site information in the distribution management of the project " + name );
795         }
796 
797         if ( site.getUrl() == null || site.getId() == null )
798         {
799             throw new MojoExecutionException( "Missing site data: specify url and id for project " + name );
800         }
801 
802         return site;
803     }
804 
805     /**
806      * Extract the distributionManagement site of the top level parent of the given MavenProject.
807      * This climbs up the project hierarchy and returns the site of the last project
808      * for which {@link #getSite(org.apache.maven.project.MavenProject)} returns a site.
809      *
810      * @param project the MavenProject. Not null.
811      * @return the top level site. Not null.
812      *         Also site.getUrl() and site.getId() are guaranteed to be not null.
813      * @throws MojoExecutionException if no site info is found in the tree.
814      */
815     protected Site getRootSite( MavenProject project )
816         throws MojoExecutionException
817     {
818         Site site = getSite( project );
819 
820         MavenProject parent = project;
821 
822         while ( parent.getParent() != null )
823         {
824             // MSITE-585, MNG-1943
825             parent = siteTool.getParentProject( parent, reactorProjects, localRepository );
826 
827             Site oldSite = site;
828 
829             try
830             {
831                 site = getSite( parent );
832             }
833             catch ( MojoExecutionException e )
834             {
835                 break;
836             }
837 
838             // MSITE-600
839             URIPathDescriptor siteURI = new URIPathDescriptor( URIEncoder.encodeURI( site.getUrl() ), "" );
840             URIPathDescriptor oldSiteURI = new URIPathDescriptor( URIEncoder.encodeURI( oldSite.getUrl() ), "" );
841 
842             if ( !siteURI.sameSite( oldSiteURI.getBaseURI() ) )
843             {
844                 return oldSite;
845             }
846         }
847 
848         return site;
849     }
850 
851     /**
852      * Detect if the mojo is staging or deploying.
853      *
854      * @return rue if the mojo is for deploy and not staging
855      */
856     protected boolean isDeploy()
857     {
858         return true;
859     }
860 
861     private static class URIEncoder
862     {
863         private static final String MARK = "-_.!~*'()";
864         private static final String RESERVED = ";/?:@&=+$,";
865 
866         public static String encodeURI( final String uriString )
867         {
868             final char[] chars = uriString.toCharArray();
869             final StringBuilder uri = new StringBuilder( chars.length );
870 
871             for ( char c : chars )
872             {
873                 if ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' )
874                         || MARK.indexOf( c ) != -1  || RESERVED.indexOf( c ) != -1 )
875                 {
876                     uri.append( c );
877                 }
878                 else
879                 {
880                     uri.append( '%' );
881                     uri.append( Integer.toHexString( (int) c ) );
882                 }
883             }
884             return uri.toString();
885         }
886     }
887 }