View Javadoc

1   /*
2    * Copyright (c) 2006 Your Corporation. All Rights Reserved.
3    */
4   package org.apache.maven.plugin.source;
5   
6   import org.apache.commons.lang.StringUtils;
7   import org.apache.commons.logging.Log;
8   import org.apache.commons.logging.LogFactory;
9   import org.apache.maven.AbstractMavenComponent;
10  import org.apache.maven.project.Project;
11  import org.apache.maven.util.HttpUtils;
12  
13  import java.io.File;
14  import java.io.FileNotFoundException;
15  import java.util.Iterator;
16  
17  /**
18   * An helper class used to download java sources archives.
19   *
20   * @author <a href="snicoll@apache.org">Stephane Nicoll</a>
21   * @version $Id: JavaSourcesDownloader.java 371078 2006-01-21 15:55:44Z snicoll $
22   */
23  public class JavaSourcesDownloader
24      extends AbstractMavenComponent
25  {
26      private static final String PROXY_LOGINHOST = "maven.proxy.ntlm.host";
27  
28      private static final String PROXY_LOGINDOMAIN = "maven.proxy.ntlm.domain";
29  
30      private static final Log log = LogFactory.getLog( JavaSourcesDownloader.class );
31  
32      private Project project;
33  
34      private String groupId;
35  
36      private String artifactId;
37  
38      private String version;
39  
40      private boolean ignoreErrors;
41  
42      private boolean backwardCompatible;
43  
44  
45      /**
46       * Download the Java sources archive from the configured repositories.
47       *
48       * @throws NullPointerException  if a mandatory parameter is not initialized
49       * @throws FileNotFoundException if ignoreErrors is false and the archive is not found
50       */
51      public void downloadJavaSources()
52          throws NullPointerException, FileNotFoundException
53      {
54          if ( project == null )
55          {
56              throw new NullPointerException( "project should be set." );
57          }
58  
59          if ( groupId == null )
60          {
61              throw new NullPointerException( "groupId should be set." );
62          }
63  
64          if ( artifactId == null )
65          {
66              throw new NullPointerException( "artifactId should be set." );
67          }
68  
69          if ( version == null )
70          {
71              throw new NullPointerException( "version should be set." );
72          }
73  
74          final String dependencyId = groupId + ":" + artifactId + ":" + version;
75  
76          final String relativePath = buildRelativePath();
77          final File localFile = new File( project.getContext().getMavenRepoLocal(), relativePath );
78          final String backwareCompatibleRelativePath = buildBackwardCompatibleRelativePath();
79          final File backwardCompatibleLocalFile =
80              new File( project.getContext().getMavenRepoLocal(), backwareCompatibleRelativePath );
81  
82          boolean artifactFound;
83          if ( isSnapshot() )
84          {
85              artifactFound = getRemoteArtifact( localFile, relativePath, dependencyId );
86              if ( backwardCompatible )
87              {
88                  artifactFound =
89                      getRemoteArtifact( backwardCompatibleLocalFile, backwareCompatibleRelativePath, dependencyId );
90              }
91  
92              // Do not go further if the artifact is in the local repository
93              if ( localFile.exists() || ( backwardCompatible && backwardCompatibleLocalFile.exists() ) )
94              {
95                  return;
96              }
97          }
98          else
99          {
100             if ( localFile.exists() || ( backwardCompatible && backwardCompatibleLocalFile.exists() ) )
101             {
102                 log.debug( "source for " + dependencyId + " is available in the local repository." );
103                 return;
104             }
105             else
106             {
107                 // download it
108                 artifactFound = getRemoteArtifact( localFile, relativePath, dependencyId );
109                 if ( !artifactFound && backwardCompatible )
110                 {
111                     artifactFound =
112                         getRemoteArtifact( backwardCompatibleLocalFile, backwareCompatibleRelativePath, dependencyId );
113                 }
114             }
115         }
116 
117         if ( !ignoreErrors && !artifactFound )
118         {
119             throw new FileNotFoundException( "Could not download source for " + dependencyId + " from any repository " +
120                 getProject().getContext().getMavenRepoRemote() );
121         }
122     }
123 
124     // Getters & Setters
125 
126 
127     public Project getProject()
128     {
129         return project;
130     }
131 
132     public void setProject( final Project project )
133     {
134         this.project = project;
135     }
136 
137     public String getGroupId()
138     {
139         return groupId;
140     }
141 
142     public void setGroupId( final String groupId )
143     {
144         this.groupId = groupId;
145     }
146 
147     public String getArtifactId()
148     {
149         return artifactId;
150     }
151 
152     public void setArtifactId( final String artifactId )
153     {
154         this.artifactId = artifactId;
155     }
156 
157     public String getVersion()
158     {
159         return version;
160     }
161 
162     public void setVersion( final String version )
163     {
164         this.version = version;
165     }
166 
167     public boolean isIgnoreErrors()
168     {
169         return ignoreErrors;
170     }
171 
172     public void setIgnoreErrors( final boolean ignoreErrors )
173     {
174         this.ignoreErrors = ignoreErrors;
175     }
176 
177     public boolean isBackwardCompatible()
178     {
179         return backwardCompatible;
180     }
181 
182     public void setBackwardCompatible( final boolean backwardCompatible )
183     {
184         this.backwardCompatible = backwardCompatible;
185     }
186 
187     /**
188      * Builds the relative path to the source archive.
189      *
190      * @return the relative path to the source archive
191      */
192     private String buildRelativePath()
193     {
194         StringBuffer sb = new StringBuffer();
195         sb.append( groupId ).append( "/java-sources/" ).append( artifactId ).append( "-" ).append( version ).append(
196             "-sources.jar" );
197         return sb.toString();
198     }
199 
200     /**
201      * Build the backware compatible relative path to the source archive.
202      *
203      * @return the backware compatible relative path to the source archive
204      */
205     private String buildBackwardCompatibleRelativePath()
206     {
207         StringBuffer sb = new StringBuffer();
208         sb.append( groupId ).append( "/src/" ).append( artifactId ).append( "-" ).append( version ).append( ".zip" );
209         return sb.toString();
210     }
211 
212     /**
213      * Specify whether the source archive to download is a SNAPSHOT or not.
214      *
215      * @return true if the source archive is a SNAPSHOT, false otherwise
216      */
217     private boolean isSnapshot()
218     {
219         return version.endsWith( "SNAPSHOT" );
220     }
221 
222     // Taken from maven core code in order to mimic the current behavior
223 
224     /**
225      * Retrieve a <code>remoteFile</code> from the maven remote repositories
226      * and store it at <code>destinationFile</code>
227      *
228      * @param destinationFile the destination file in the local repository
229      * @param relativePath    the relative path to the dependency
230      * @param dependencyId    the dependency Id
231      * @return true if the retrieval succeeds, false otherwise.
232      */
233     private boolean getRemoteArtifact( File destinationFile, String relativePath, String dependencyId )
234     {
235 
236         // The directory structure for the project this dependency belongs to
237         // may not exists so attempt to create the project directory structure
238         // before attempting to download the dependency.
239         File directory = destinationFile.getParentFile();
240 
241         if ( !directory.exists() )
242         {
243             directory.mkdirs();
244         }
245 
246         log.info( "Attempting to download sources for " + dependencyId + " ("+relativePath+")" );
247 
248         boolean artifactFound = false;
249 
250         for ( Iterator i = getProject().getContext().getMavenRepoRemote().iterator(); i.hasNext(); )
251         {
252             String remoteRepo = (String) i.next();
253 
254             if ( remoteRepo.endsWith( "/" ) )
255             {
256                 remoteRepo = remoteRepo.substring( 0, remoteRepo.length() - 1 );
257             }
258 
259             // The username and password parameters are not being
260             // used here. Those are the "" parameters you see below.
261             String url = remoteRepo + "/" + relativePath;
262             url = StringUtils.replace( url, "//", "/" );
263 
264             if ( !url.startsWith( "file" ) )
265             {
266                 if ( url.startsWith( "https" ) )
267                 {
268                     url = StringUtils.replace( url, "https:/", "https://" );
269                 }
270                 else
271                 {
272                     url = StringUtils.replace( url, "http:/", "http://" );
273                 }
274             }
275             log.debug( "Trying to download source at " + url );
276 
277             // Attempt to retrieve the artifact and set the checksum if retrieval
278             // of the checksum file was successful.
279             try
280             {
281                 String loginHost = (String) getProject().getContext().getVariable( PROXY_LOGINHOST );
282                 String loginDomain = (String) getProject().getContext().getVariable( PROXY_LOGINDOMAIN );
283                 HttpUtils.getFile( url, destinationFile, false, true, getProject().getContext().getProxyHost(),
284                                    getProject().getContext().getProxyPort(),
285                                    getProject().getContext().getProxyUserName(),
286                                    getProject().getContext().getProxyPassword(), loginHost, loginDomain, true );
287 
288                 // Artifact was found, continue checking additional remote repos (if any)
289                 // in case there is a newer version (i.e. snapshots) in another repo
290                 artifactFound = true;
291 
292                 if ( !isSnapshot() )
293                 {
294                     log.info( "Downloded source for " + dependencyId + " at " + url );
295                     break;
296                 }
297             }
298             catch ( FileNotFoundException e )
299             {
300                 // Multiple repositories may exist, and if the file is not found
301                 // in just one of them, it's no problem, and we don't want to
302                 // even print out an error.
303                 // if it's not found at all, artifactFound will be false, and the
304                 // build _will_ break, and the user will get an error message
305                 log.debug( "File not found on one of the repos", e );
306             }
307             catch ( Exception e )
308             {
309                 // If there are additional remote repos, then ignore exception
310                 // as artifact may be found in another remote repo. If there
311                 // are no more remote repos to check and the artifact wasn't found in
312                 // a previous remote repo, then artifactFound is false indicating
313                 // that the artifact could not be found in any of the remote repos
314                 //
315                 // arguably, we need to give the user better control (another command-
316                 // line switch perhaps) of what to do in this case? Maven already has
317                 // a command-line switch to work in offline mode, but what about when
318                 // one of two or more remote repos is unavailable? There may be multiple
319                 // remote repos for redundancy, in which case you probably want the build
320                 // to continue. There may however be multiple remote repos because some
321                 // artifacts are on one, and some are on another. In this case, you may
322                 // want the build to break.
323                 //
324                 // print a warning, in any case, so user catches on to mistyped
325                 // hostnames, or other snafus
326                 // FIXME: localize this message
327                 String[] parsedUrl = HttpUtils.parseUrl( url );
328                 log.warn( "Error retrieving artifact from [" + parsedUrl[2] + "]: " + e );
329                 if ( parsedUrl[0] != null )
330                 {
331                     log.debug( "Username was '" + parsedUrl[0] + "', password hidden" );
332                 }
333                 log.debug( "Error details", e );
334             }
335         }
336 
337         return artifactFound;
338     }
339 }