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