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