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