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