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