View Javadoc

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