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  
24  import java.util.List;
25  import java.util.Locale;
26  
27  import org.apache.maven.artifact.manager.WagonConfigurationException;
28  import org.apache.maven.artifact.manager.WagonManager;
29  import org.apache.maven.model.DistributionManagement;
30  import org.apache.maven.model.Site;
31  import org.apache.maven.plugin.MojoExecutionException;
32  import org.apache.maven.plugin.logging.Log;
33  import org.apache.maven.project.MavenProject;
34  import org.apache.maven.settings.Server;
35  import org.apache.maven.settings.Settings;
36  import org.apache.maven.wagon.CommandExecutionException;
37  import org.apache.maven.wagon.CommandExecutor;
38  import org.apache.maven.wagon.ConnectionException;
39  import org.apache.maven.wagon.ResourceDoesNotExistException;
40  import org.apache.maven.wagon.TransferFailedException;
41  import org.apache.maven.wagon.UnsupportedProtocolException;
42  import org.apache.maven.wagon.Wagon;
43  import org.apache.maven.wagon.authentication.AuthenticationException;
44  import org.apache.maven.wagon.authentication.AuthenticationInfo;
45  import org.apache.maven.wagon.authorization.AuthorizationException;
46  import org.apache.maven.wagon.observers.Debug;
47  import org.apache.maven.wagon.proxy.ProxyInfo;
48  import org.apache.maven.wagon.repository.Repository;
49  
50  import org.codehaus.plexus.PlexusConstants;
51  import org.codehaus.plexus.PlexusContainer;
52  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
53  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
54  import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
55  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
56  import org.codehaus.plexus.configuration.PlexusConfiguration;
57  import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
58  import org.codehaus.plexus.context.Context;
59  import org.codehaus.plexus.context.ContextException;
60  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
61  import org.codehaus.plexus.util.StringUtils;
62  import org.codehaus.plexus.util.xml.Xpp3Dom;
63  
64  /**
65   * Abstract base class for deploy mojos.
66   * Since 2.3 this includes {@link SiteStageMojo} and {@link SiteStageDeployMojo}.
67   *
68   * @author ltheussl
69   *
70   * @since 2.3
71   */
72  public abstract class AbstractDeployMojo
73      extends AbstractSiteMojo implements Contextualizable
74  {
75      /**
76       * Directory containing the generated project sites and report distributions.
77       *
78       * @parameter alias="outputDirectory" expression="${project.reporting.outputDirectory}"
79       * @required
80       */
81      private File inputDirectory;
82  
83      /**
84       * Whether to run the "chmod" command on the remote site after the deploy.
85       * Defaults to "true".
86       *
87       * @parameter expression="${maven.site.chmod}" default-value="true"
88       * @since 2.1
89       */
90      private boolean chmod;
91  
92      /**
93       * The mode used by the "chmod" command. Only used if chmod = true.
94       * Defaults to "g+w,a+rX".
95       *
96       * @parameter expression="${maven.site.chmod.mode}" default-value="g+w,a+rX"
97       * @since 2.1
98       */
99      private String chmodMode;
100 
101     /**
102      * The options used by the "chmod" command. Only used if chmod = true.
103      * Defaults to "-Rf".
104      *
105      * @parameter expression="${maven.site.chmod.options}" default-value="-Rf"
106      * @since 2.1
107      */
108     private String chmodOptions;
109 
110     /**
111      * @component
112      */
113     private WagonManager wagonManager;
114 
115     /**
116      * The current user system settings for use in Maven.
117      *
118      * @parameter expression="${settings}"
119      * @required
120      * @readonly
121      */
122     private Settings settings;
123 
124     private PlexusContainer container;
125 
126     /**
127      * The String "staging/".
128      */
129     protected static final String DEFAULT_STAGING_DIRECTORY = "staging/";
130 
131     /** {@inheritDoc} */
132     public void execute()
133         throws MojoExecutionException
134     {
135         deployTo( new org.apache.maven.plugins.site.wagon.repository.Repository(
136             getDeployRepositoryID(),
137             appendSlash( getDeployRepositoryURL() ) ) );
138     }
139 
140     /**
141      * Make sure the given url ends with a slash.
142      *
143      * @param url a String.
144      *
145      * @return if url already ends with '/' it is returned unchanged,
146      *      otherwise a '/' character is appended.
147      */
148     protected static String appendSlash( final String url )
149     {
150         if ( url.endsWith( "/" ) )
151         {
152             return url;
153         }
154         else
155         {
156             return url + "/";
157         }
158     }
159 
160     /**
161      * Specifies the id to look up credential settings.
162      *
163      * @return the id to look up credentials for the deploy. Not null.
164      *
165      * @throws MojoExecutionException
166      *      if the ID cannot be determined
167      */
168     protected abstract String getDeployRepositoryID()
169         throws MojoExecutionException;
170 
171     /**
172      * Specifies the target URL for the deploy.
173      * This should be the top-level URL, ie above modules and locale sub-directories.
174      *
175      * @return the url to deploy to. Not null.
176      *
177      * @throws MojoExecutionException
178      *      if the URL cannot be constructed
179      */
180     protected abstract String getDeployRepositoryURL()
181         throws MojoExecutionException;
182 
183     /**
184      * Find the relative path between the distribution URLs of the top parent and the current project.
185      *
186      * @return the relative path or "./" if the two URLs are the same.
187      *
188      * @throws MojoExecutionException
189      */
190     private String getDeployModuleDirectory()
191         throws MojoExecutionException
192     {
193         String relative = siteTool.getRelativePath( getSite( project ).getUrl(),
194             getRootSite( project ).getUrl() );
195 
196         // SiteTool.getRelativePath() uses File.separatorChar,
197         // so we need to convert '\' to '/' in order for the URL to be valid for Windows users
198         relative = relative.replace( '\\', '/' );
199 
200         return ( "".equals( relative ) ) ? "./" : relative;
201     }
202 
203     /**
204      * Use wagon to deploy the generated site to a given repository.
205      *
206      * @param repository the repository to deply to.
207      *      This needs to contain a valid, non-null {@link Repository#getId() id}
208      *      to look up credentials for the deploy, and a valid, non-null
209      *      {@link Repository#getUrl() scm url} to deploy to.
210      *
211      * @throws MojoExecutionException if the deploy fails.
212      */
213     private void deployTo( final Repository repository )
214         throws MojoExecutionException
215     {
216         if ( !inputDirectory.exists() )
217         {
218             throw new MojoExecutionException( "The site does not exist, please run site:site first" );
219         }
220 
221         if ( getLog().isDebugEnabled() )
222         {
223             getLog().debug( "Deploying to '" + repository.getUrl()
224                 + "',\n    Using credentials from server id '" + repository.getId() + "'" );
225         }
226 
227         deploy( inputDirectory, repository );
228     }
229 
230     private void deploy( final File directory, final Repository repository )
231         throws MojoExecutionException
232     {
233         // TODO: work on moving this into the deployer like the other deploy methods
234         final Wagon wagon = getWagon( repository, wagonManager );
235 
236         try
237         {
238             configureWagon( wagon, repository.getId(), settings, container, getLog() );
239         }
240         catch ( WagonConfigurationException e )
241         {
242             throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e );
243         }
244 
245         try
246         {
247             final ProxyInfo proxyInfo = getProxyInfo( repository, wagonManager );
248 
249             push( directory, repository, wagonManager, wagon, proxyInfo,
250                 siteTool.getAvailableLocales( locales ), getDeployModuleDirectory(), getLog() );
251 
252             if ( chmod )
253             {
254                 chmod( wagon, repository, chmodOptions, chmodMode );
255             }
256         }
257         finally
258         {
259             try
260             {
261                 wagon.disconnect();
262             }
263             catch ( ConnectionException e )
264             {
265                 getLog().error( "Error disconnecting wagon - ignored", e );
266             }
267         }
268     }
269 
270     /**
271      * Find the build directory of the top level project in the reactor.
272      * If no top level project is found, the build directory of the current project is returned.
273      *
274      * @return the build directory of the top level project.
275      */
276     protected File getTopLevelBuildDirectory()
277     {
278         // Find the top level project in the reactor
279         final MavenProject topLevelProject = getTopLevelProject( reactorProjects );
280 
281         // Use the top level project's build directory if there is one, otherwise use this project's build directory
282         final File buildDirectory;
283 
284         if ( topLevelProject == null )
285         {
286             getLog().debug( "No top level project found in the reactor, using the current project." );
287 
288             buildDirectory = new File( project.getBuild().getDirectory() );
289         }
290         else
291         {
292             getLog().debug( "Using the top level project found in the reactor." );
293 
294             buildDirectory = new File( topLevelProject.getBuild().getDirectory() );
295         }
296 
297         return buildDirectory;
298     }
299 
300     private static Wagon getWagon( final Repository repository, final WagonManager manager )
301         throws MojoExecutionException
302     {
303         final Wagon wagon;
304 
305         try
306         {
307             wagon = manager.getWagon( repository );
308         }
309         catch ( UnsupportedProtocolException e )
310         {
311             throw new MojoExecutionException( "Unsupported protocol: '" + repository.getProtocol() + "'", e );
312         }
313         catch ( WagonConfigurationException e )
314         {
315             throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e );
316         }
317 
318         if ( !wagon.supportsDirectoryCopy() )
319         {
320             throw new MojoExecutionException(
321                 "Wagon protocol '" + repository.getProtocol() + "' doesn't support directory copying" );
322         }
323 
324         return wagon;
325     }
326 
327     private static void push( final File inputDirectory, final Repository repository, final WagonManager manager,
328                               final Wagon wagon, final ProxyInfo proxyInfo, final List<Locale> localesList,
329                               final String relativeDir, final Log log )
330         throws MojoExecutionException
331     {
332         AuthenticationInfo authenticationInfo = manager.getAuthenticationInfo( repository.getId() );
333         log.debug( "authenticationInfo with id '" + repository.getId() + "': "
334             + ( ( authenticationInfo == null ) ? "-" : authenticationInfo.getUserName() ) );
335 
336         try
337         {
338             Debug debug = new Debug();
339 
340             wagon.addSessionListener( debug );
341 
342             wagon.addTransferListener( debug );
343 
344             if ( proxyInfo != null )
345             {
346                 log.debug( "connect with proxyInfo" );
347                 wagon.connect( repository, authenticationInfo, proxyInfo );
348             }
349             else if ( proxyInfo == null && authenticationInfo != null )
350             {
351                 log.debug( "connect with authenticationInfo and without proxyInfo" );
352                 wagon.connect( repository, authenticationInfo );
353             }
354             else
355             {
356                 log.debug( "connect without authenticationInfo and without proxyInfo" );
357                 wagon.connect( repository );
358             }
359 
360             log.info( "Pushing " + inputDirectory );
361 
362             // Default is first in the list
363             final String defaultLocale = localesList.get( 0 ).getLanguage();
364 
365             for ( Locale locale : localesList )
366             {
367                 if ( locale.getLanguage().equals( defaultLocale ) )
368                 {
369                     // TODO: this also uploads the non-default locales,
370                     // is there a way to exclude directories in wagon?
371                     log.info( "   >>> to " + repository.getUrl() + relativeDir );
372 
373                     wagon.putDirectory( inputDirectory, relativeDir );
374                 }
375                 else
376                 {
377                     log.info( "   >>> to " + repository.getUrl() + locale.getLanguage() + "/" + relativeDir );
378 
379                     wagon.putDirectory( new File( inputDirectory, locale.getLanguage() ),
380                         locale.getLanguage() + "/" + relativeDir );
381                 }
382             }
383         }
384         catch ( ResourceDoesNotExistException e )
385         {
386             throw new MojoExecutionException( "Error uploading site", e );
387         }
388         catch ( TransferFailedException e )
389         {
390             throw new MojoExecutionException( "Error uploading site", e );
391         }
392         catch ( AuthorizationException e )
393         {
394             throw new MojoExecutionException( "Error uploading site", e );
395         }
396         catch ( ConnectionException e )
397         {
398             throw new MojoExecutionException( "Error uploading site", e );
399         }
400         catch ( AuthenticationException e )
401         {
402             throw new MojoExecutionException( "Error uploading site", e );
403         }
404     }
405 
406     private static void chmod( final Wagon wagon, final Repository repository,
407         final String chmodOptions, final String chmodMode )
408         throws MojoExecutionException
409     {
410         try
411         {
412             if ( wagon instanceof CommandExecutor )
413             {
414                 CommandExecutor exec = (CommandExecutor) wagon;
415                 exec.executeCommand( "chmod " + chmodOptions + " " + chmodMode + " " + repository.getBasedir() );
416             }
417             // else ? silently ignore, FileWagon is not a CommandExecutor!
418         }
419         catch ( CommandExecutionException e )
420         {
421             throw new MojoExecutionException( "Error uploading site", e );
422         }
423     }
424 
425     /**
426      * <p>
427      * Get the <code>ProxyInfo</code> of the proxy associated with the <code>host</code>
428      * and the <code>protocol</code> of the given <code>repository</code>.
429      * </p>
430      * <p>
431      * Extract from <a href="http://java.sun.com/j2se/1.5.0/docs/guide/net/properties.html">
432      * J2SE Doc : Networking Properties - nonProxyHosts</a> : "The value can be a list of hosts,
433      * each separated by a |, and in addition a wildcard character (*) can be used for matching"
434      * </p>
435      * <p>
436      * Defensively support for comma (",") and semi colon (";") in addition to pipe ("|") as separator.
437      * </p>
438      *
439      * @param repository the Repository to extract the ProxyInfo from.
440      * @param wagonManager the WagonManager used to connect to the Repository.
441      * @return a ProxyInfo object instantiated or <code>null</code> if no matching proxy is found
442      */
443     public static ProxyInfo getProxyInfo( Repository repository, WagonManager wagonManager )
444     {
445         ProxyInfo proxyInfo = wagonManager.getProxy( repository.getProtocol() );
446 
447         if ( proxyInfo == null )
448         {
449             return null;
450         }
451 
452         String host = repository.getHost();
453         String nonProxyHostsAsString = proxyInfo.getNonProxyHosts();
454         String[] nonProxyHosts = StringUtils.split( nonProxyHostsAsString, ",;|" );
455         for ( int i = 0; i < nonProxyHosts.length; i++ )
456         {
457             String nonProxyHost = nonProxyHosts[i];
458             if ( StringUtils.contains( nonProxyHost, "*" ) )
459             {
460                 // Handle wildcard at the end, beginning or middle of the nonProxyHost
461                 final int pos = nonProxyHost.indexOf( '*' );
462                 String nonProxyHostPrefix = nonProxyHost.substring( 0, pos );
463                 String nonProxyHostSuffix = nonProxyHost.substring( pos + 1 );
464                 // prefix*
465                 if ( StringUtils.isNotEmpty( nonProxyHostPrefix ) && host.startsWith( nonProxyHostPrefix )
466                     && StringUtils.isEmpty( nonProxyHostSuffix ) )
467                 {
468                     return null;
469                 }
470                 // *suffix
471                 if ( StringUtils.isEmpty( nonProxyHostPrefix )
472                     && StringUtils.isNotEmpty( nonProxyHostSuffix ) && host.endsWith( nonProxyHostSuffix ) )
473                 {
474                     return null;
475                 }
476                 // prefix*suffix
477                 if ( StringUtils.isNotEmpty( nonProxyHostPrefix ) && host.startsWith( nonProxyHostPrefix )
478                     && StringUtils.isNotEmpty( nonProxyHostSuffix ) && host.endsWith( nonProxyHostSuffix ) )
479                 {
480                     return null;
481                 }
482             }
483             else if ( host.equals( nonProxyHost ) )
484             {
485                 return null;
486             }
487         }
488         return proxyInfo;
489     }
490 
491     /**
492      * Configure the Wagon with the information from serverConfigurationMap ( which comes from settings.xml )
493      *
494      * @todo Remove when {@link WagonManager#getWagon(Repository) is available}. It's available in Maven 2.0.5.
495      * @param wagon
496      * @param repositoryId
497      * @param settings
498      * @param container
499      * @param log
500      * @throws WagonConfigurationException
501      */
502     private static void configureWagon( Wagon wagon, String repositoryId, Settings settings, PlexusContainer container,
503                                         Log log )
504         throws WagonConfigurationException
505     {
506         log.debug( " configureWagon " );
507 
508         // MSITE-25: Make sure that the server settings are inserted
509         for ( int i = 0; i < settings.getServers().size(); i++ )
510         {
511             Server server = settings.getServers().get( i );
512             String id = server.getId();
513 
514             log.debug( "configureWagon server " + id );
515 
516             if ( id != null && id.equals( repositoryId ) )
517             {
518                 if ( server.getConfiguration() != null )
519                 {
520                     final PlexusConfiguration plexusConf =
521                         new XmlPlexusConfiguration( (Xpp3Dom) server.getConfiguration() );
522 
523                     ComponentConfigurator componentConfigurator = null;
524                     try
525                     {
526                         componentConfigurator = (ComponentConfigurator) container.lookup( ComponentConfigurator.ROLE );
527                         componentConfigurator.configureComponent( wagon, plexusConf, container.getContainerRealm() );
528                     }
529                     catch ( final ComponentLookupException e )
530                     {
531                         throw new WagonConfigurationException( repositoryId, "Unable to lookup wagon configurator."
532                             + " Wagon configuration cannot be applied.", e );
533                     }
534                     catch ( ComponentConfigurationException e )
535                     {
536                         throw new WagonConfigurationException( repositoryId, "Unable to apply wagon configuration.",
537                             e );
538                     }
539                     finally
540                     {
541                         if ( componentConfigurator != null )
542                         {
543                             try
544                             {
545                                 container.release( componentConfigurator );
546                             }
547                             catch ( ComponentLifecycleException e )
548                             {
549                                 log.error( "Problem releasing configurator - ignoring: " + e.getMessage() );
550                             }
551                         }
552                     }
553                 }
554             }
555         }
556     }
557 
558     /** {@inheritDoc} */
559     public void contextualize( Context context )
560         throws ContextException
561     {
562         container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
563     }
564 
565     /**
566      * Find the top level parent in the reactor, i.e. the execution root.
567      *
568      * @param reactorProjects The projects in the reactor. May be null in which case null is returnned.
569      *
570      * @return The top level project in the reactor, or <code>null</code> if none can be found
571      */
572     private static MavenProject getTopLevelProject( List<MavenProject> reactorProjects )
573     {
574         if ( reactorProjects == null )
575         {
576             return null;
577         }
578 
579         for ( MavenProject reactorProject : reactorProjects )
580         {
581             if ( reactorProject.isExecutionRoot() )
582             {
583                 return reactorProject;
584             }
585         }
586 
587         return null;
588     }
589 
590     /**
591      * Extract the distributionManagment site from the given MavenProject.
592      *
593      * @param project the MavenProject. Not null.
594      *
595      * @return the project site. Not null.
596      *      Also site.getUrl() and site.getId() are guaranteed to be not null.
597      *
598      * @throws MojoExecutionException if any of the site info is missing.
599      */
600     protected static Site getSite( final MavenProject project )
601         throws MojoExecutionException
602     {
603         final String name = project.getName() + " ("
604             + project.getGroupId() + ":" + project.getArtifactId() + ":" + project.getVersion() + ")";
605 
606         final DistributionManagement distributionManagement = project.getDistributionManagement();
607 
608         if ( distributionManagement == null )
609         {
610             throw new MojoExecutionException( "Missing distribution management in project " + name );
611         }
612 
613         final Site site = distributionManagement.getSite();
614 
615         if ( site == null )
616         {
617             throw new MojoExecutionException(
618                 "Missing site information in the distribution management of the project " + name );
619         }
620 
621         if ( site.getUrl() == null || site.getId() == null )
622         {
623             throw new MojoExecutionException( "Missing site data: specify url and id for project " + name );
624         }
625 
626         return site;
627     }
628 
629     /**
630      * Extract the distributionManagment site of the top level parent of the given MavenProject.
631      * This climbs up the project hierarchy and returns the site of the last project
632      * for which {@link #getSite(org.apache.maven.project.MavenProject)} returns a site.
633      *
634      * @param project the MavenProject. Not null.
635      *
636      * @return the top level site. Not null.
637      *      Also site.getUrl() and site.getId() are guaranteed to be not null.
638      *
639      * @throws MojoExecutionException if no site info is found in the tree.
640      */
641     protected static Site getRootSite( MavenProject project )
642         throws MojoExecutionException
643     {
644         Site site = getSite( project );
645 
646         MavenProject parent = project;
647 
648         while ( parent.getParent() != null )
649         {
650             parent = parent.getParent();
651 
652             try
653             {
654                 site = getSite( parent );
655             }
656             catch ( MojoExecutionException e )
657             {
658                 break;
659             }
660         }
661 
662         return site;
663     }
664 }