View Javadoc

1   package org.apache.maven.plugin.idea;
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.Artifact;
23  import org.apache.maven.artifact.factory.ArtifactFactory;
24  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
25  import org.apache.maven.artifact.repository.ArtifactRepository;
26  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
27  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
28  import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
29  import org.apache.maven.artifact.resolver.ArtifactResolver;
30  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
31  import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter;
32  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
33  import org.apache.maven.artifact.versioning.VersionRange;
34  import org.apache.maven.model.Dependency;
35  import org.apache.maven.model.DependencyManagement;
36  import org.apache.maven.model.Exclusion;
37  import org.apache.maven.model.Plugin;
38  import org.apache.maven.plugin.AbstractMojo;
39  import org.apache.maven.plugin.logging.Log;
40  import org.apache.maven.project.MavenProject;
41  import org.apache.maven.project.ProjectBuildingException;
42  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
43  import org.codehaus.plexus.util.StringUtils;
44  import org.codehaus.plexus.util.xml.Xpp3Dom;
45  import org.dom4j.Document;
46  import org.dom4j.DocumentException;
47  import org.dom4j.Element;
48  import org.dom4j.io.SAXReader;
49  import org.dom4j.io.XMLWriter;
50  
51  import java.io.File;
52  import java.io.IOException;
53  import java.util.ArrayList;
54  import java.util.Collections;
55  import java.util.HashMap;
56  import java.util.HashSet;
57  import java.util.Iterator;
58  import java.util.List;
59  import java.util.Map;
60  import java.util.Set;
61  import java.util.StringTokenizer;
62  
63  /**
64   * @author Edwin Punzalan
65   */
66  public abstract class AbstractIdeaMojo
67      extends AbstractMojo
68  {
69      /**
70       * The Maven Project.
71       *
72       * @parameter expression="${executedProject}"
73       * @required
74       * @readonly
75       */
76      protected MavenProject executedProject;
77  
78      /* holder for the log object only */
79      protected Log log;
80  
81      /**
82       * Whether to update the existing project files or overwrite them.
83       *
84       * @parameter expression="${overwrite}" default-value="false"
85       */
86      protected boolean overwrite;
87  
88      /**
89       * @component
90       */
91      protected ArtifactFactory artifactFactory;
92  
93      /**
94       * @parameter expression="${localRepository}"
95       * @required
96       * @readonly
97       */
98      protected ArtifactRepository localRepo;
99  
100     /**
101      * @component
102      */
103     protected ArtifactResolver artifactResolver;
104 
105     /**
106      * @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource" hint="maven"
107      */
108     protected ArtifactMetadataSource artifactMetadataSource;
109 
110     public void initParam( MavenProject project, ArtifactFactory artifactFactory, ArtifactRepository localRepo,
111                            ArtifactResolver artifactResolver, ArtifactMetadataSource artifactMetadataSource, Log log,
112                            boolean overwrite )
113     {
114         this.executedProject = project;
115 
116         this.log = log;
117 
118         this.artifactFactory = artifactFactory;
119 
120         this.localRepo = localRepo;
121 
122         this.artifactResolver = artifactResolver;
123 
124         this.artifactMetadataSource = artifactMetadataSource;
125 
126         this.overwrite = overwrite;
127     }
128 
129     protected Document readXmlDocument( File file, String altFilename )
130         throws DocumentException
131     {
132         SAXReader reader = new SAXReader();
133         if ( file.exists() && !overwrite )
134         {
135             return reader.read( file );
136         }
137         else
138         {
139             File altFile = new File( executedProject.getBasedir(), "src/main/idea/" + altFilename );
140             if ( altFile.exists() )
141             {
142                 return reader.read( altFile );
143             }
144             else
145             {
146                 return reader.read( getClass().getResourceAsStream( "/templates/default/" + altFilename ) );
147             }
148         }
149     }
150 
151     protected void writeXmlDocument( File file, Document document )
152         throws IOException
153     {
154         XMLWriter writer = new IdeaXmlWriter( file );
155         writer.write( document );
156         writer.close();
157     }
158 
159     /**
160      * Finds element from the module element.
161      *
162      * @param module Xpp3Dom element
163      * @param name   Name attribute to find
164      * @return component  Returns the Xpp3Dom element found.
165      */
166     protected Element findComponent( Element module, String name )
167     {
168         return findElement( module, "component", name );
169     }
170 
171     protected Element findElement( Element element, String elementName, String attributeName )
172     {
173         for ( Iterator children = element.elementIterator( elementName ); children.hasNext(); )
174         {
175             Element child = (Element) children.next();
176             if ( attributeName.equals( child.attributeValue( "name" ) ) )
177             {
178                 return child;
179             }
180         }
181         return createElement( element, elementName ).addAttribute( "name", attributeName );
182     }
183 
184     protected Element findElement( Element component, String name )
185     {
186         Element element = component.element( name );
187         if ( element == null )
188         {
189             element = createElement( component, name );
190         }
191         return element;
192     }
193 
194     /**
195      * Creates an Xpp3Dom element.
196      *
197      * @param module Xpp3Dom element
198      * @param name   Name of the element
199      * @return component Xpp3Dom element
200      */
201     protected Element createElement( Element module, String name )
202     {
203         return module.addElement( name );
204     }
205 
206     /**
207      * Translate the absolutePath into its relative path.
208      *
209      * @param basedir      The basedir of the project.
210      * @param absolutePath The absolute path that must be translated to relative path.
211      * @return relative  Relative path of the parameter absolute path.
212      */
213     protected String toRelative( String basedir, String absolutePath )
214     {
215         String relative;
216 
217         // Convert drive letter
218         String convertedBasedir = convertDriveLetter( basedir );
219         String convertedAbsolutePath = convertDriveLetter( absolutePath );
220 
221         // Normalize path separators
222         convertedBasedir = StringUtils.replace( convertedBasedir, "\\", "/" );
223         convertedAbsolutePath = StringUtils.replace( convertedAbsolutePath, "\\", "/" );
224 
225         // Strip trailing slash
226         if ( convertedBasedir.endsWith( "/" ) )
227         {
228             convertedBasedir = convertedBasedir.substring( 0, convertedBasedir.length() - 1 );
229         }
230         if ( convertedAbsolutePath.endsWith( "/" ) )
231         {
232             convertedAbsolutePath = convertedAbsolutePath.substring( 0, convertedAbsolutePath.length() - 1 );
233         }
234 
235         // IDEA-103 Make sure that the basedir is appended with a / before we attempt to match it to the absolute path
236         String matchableBasedir = convertedBasedir + "/";
237         if ( convertedAbsolutePath.startsWith( matchableBasedir )
238             && convertedAbsolutePath.length() > matchableBasedir.length() )
239         {
240             // Simple case, path starts with basepath
241             relative = convertedAbsolutePath.substring( matchableBasedir.length() );
242         }
243         else
244         {
245             // It's more complex...
246             StringTokenizer baseTokens = new StringTokenizer( convertedBasedir, "/", false );
247 
248             int baseCount = baseTokens.countTokens();
249             List baseTokenList = new ArrayList( baseCount );
250             while ( baseTokens.hasMoreTokens() )
251             {
252                 baseTokenList.add( baseTokens.nextToken() );
253             }
254 
255             StringTokenizer pathTokens = new StringTokenizer( convertedAbsolutePath, "/", false );
256 
257             int pathCount = pathTokens.countTokens();
258             List pathTokenList = new ArrayList( pathCount );
259             while ( pathTokens.hasMoreTokens() )
260             {
261                 pathTokenList.add( pathTokens.nextToken() );
262             }
263 
264             int maxCount = Math.max( baseTokenList.size(), pathTokenList.size() );
265             int differIndex = -1;
266             for ( int i = 0; i < maxCount; i++ )
267             {
268                 if ( i >= pathTokenList.size() || i >= baseTokenList.size() )
269                 {
270                     differIndex = i;
271                     break;
272                 }
273                 String basePart = (String) baseTokenList.get( i );
274                 String pathPart = (String) pathTokenList.get( i );
275                 if ( !basePart.equals( pathPart ) )
276                 {
277                     differIndex = i;
278                     break;
279                 }
280             }
281             if ( getLog().isDebugEnabled() )
282             {
283                 getLog().debug( "Construction of relative path... differIndex=" + differIndex );
284             }
285             if ( differIndex < 1 )
286             {
287                 // Paths are either equal or completely different
288                 relative = convertedAbsolutePath;
289             }
290             else
291             {
292                 StringBuilder result = new StringBuilder();
293                 int parentCount = baseTokenList.size() - differIndex;
294                 if ( getLog().isDebugEnabled() )
295                 {
296                     getLog().debug( "parentCount=" + parentCount );
297                 }
298                 boolean isFirst = true;
299                 for ( int i = 0; i < parentCount; i++ )
300                 {
301                     // Add parents
302                     if ( isFirst )
303                     {
304                         isFirst = false;
305                     }
306                     else
307                     {
308                         result.append( "/" );
309                     }
310                     result.append( ".." );
311                 }
312                 for ( int i = differIndex; i < pathTokenList.size(); i++ )
313                 {
314                     // Add the remaining path elements
315                     if ( isFirst )
316                     {
317                         isFirst = false;
318                     }
319                     else
320                     {
321                         result.append( "/" );
322                     }
323                     result.append( pathTokenList.get( i ) );
324                 }
325                 relative = result.toString();
326             }
327         }
328 
329         if ( getLog().isDebugEnabled() )
330         {
331             getLog().debug( "toRelative(" + basedir + ", " + absolutePath + ") => " + relative );
332         }
333 
334         return relative;
335     }
336 
337     /**
338      * Convert the drive letter, if there is one, to upper case. This is done
339      * to avoid case mismatch when running cygwin on Windows.
340      *
341      * @param absolutePath The path to convert
342      * @return The path that came in with its drive letter converted to upper case
343      */
344     String convertDriveLetter( String absolutePath )
345     {
346         if ( absolutePath != null && absolutePath.length() >= 3 && !absolutePath.startsWith( "/" ) )
347         {
348             // See if the path starts with "?:\", where ? must be a letter
349             if ( Character.isLetter( absolutePath.substring( 0, 1 ).charAt( 0 ) )
350                 && absolutePath.substring( 1, 3 ).equals( ":\\" ) )
351             {
352                 // In that case we convert the first character to upper case
353                 return absolutePath.substring( 0, 1 ).toUpperCase() + absolutePath.substring( 1 );
354             }
355         }
356         return absolutePath;
357     }
358 
359     /**
360      * Remove elements from content (Xpp3Dom).
361      *
362      * @param content Xpp3Dom element
363      * @param name    Name of the element to be removed
364      */
365     protected void removeOldElements( Element content, String name )
366     {
367         for ( Iterator children = content.elementIterator(); children.hasNext(); )
368         {
369             Element child = (Element) children.next();
370             if ( name.equals( child.getName() ) )
371             {
372                 content.remove( child );
373             }
374         }
375     }
376 
377     protected void doDependencyResolution( MavenProject project, ArtifactRepository localRepo )
378         throws InvalidDependencyVersionException, ProjectBuildingException, InvalidVersionSpecificationException
379     {
380         Map managedVersions =
381             createManagedVersionMap( artifactFactory, project.getId(), project.getDependencyManagement() );
382 
383         try
384         {
385             ArtifactResolutionResult result = artifactResolver.resolveTransitively( getProjectArtifacts(),
386                                                                                     project.getArtifact(),
387                                                                                     managedVersions, localRepo,
388                                                                                     project.getRemoteArtifactRepositories(),
389                                                                                     artifactMetadataSource );
390 
391             project.setArtifacts( result.getArtifacts() );
392         }
393         catch ( ArtifactNotFoundException e )
394         {
395             getLog().debug( e.getMessage(), e );
396 
397             StringBuilder msg = new StringBuilder();
398             msg.append( "An error occurred during dependency resolution.\n\n" );
399             msg.append( "    Failed to retrieve " + e.getDownloadUrl() + "\n" );
400             msg.append( "from the following repositories:" );
401             for ( Iterator repositories = e.getRemoteRepositories().iterator(); repositories.hasNext(); )
402             {
403                 ArtifactRepository repository = (ArtifactRepository) repositories.next();
404                 msg.append( "\n    " + repository.getId() + "(" + repository.getUrl() + ")" );
405             }
406             msg.append( "\nCaused by: " + e.getMessage() );
407 
408             getLog().warn( msg );
409         }
410         catch ( ArtifactResolutionException e )
411         {
412             getLog().debug( e.getMessage(), e );
413 
414             StringBuilder msg = new StringBuilder();
415             msg.append( "An error occurred during dependency resolution of the following artifact:\n\n" );
416             msg.append( "    " + e.getGroupId() + ":" + e.getArtifactId() + e.getVersion() + "\n\n" );
417             msg.append( "Caused by: " + e.getMessage() );
418 
419             getLog().warn( msg );
420         }
421     }
422 
423     /*
424     * @todo we need a more permanent feature that does this properly
425     */
426     protected String getPluginSetting( String artifactId, String optionName, String defaultValue )
427     {
428         for ( Iterator it = executedProject.getBuildPlugins().iterator(); it.hasNext(); )
429         {
430             Plugin plugin = (Plugin) it.next();
431             if ( plugin.getArtifactId().equals( artifactId ) )
432             {
433                 Xpp3Dom o = (Xpp3Dom) plugin.getConfiguration();
434                 if ( o != null && o.getChild( optionName ) != null )
435                 {
436                     return o.getChild( optionName ).getValue();
437                 }
438             }
439         }
440         return defaultValue;
441     }
442 
443     private Set getProjectArtifacts()
444         throws InvalidVersionSpecificationException
445     {
446         Set artifacts = new HashSet();
447 
448         for ( Iterator dependencies = executedProject.getDependencies().iterator(); dependencies.hasNext(); )
449         {
450             Dependency dep = (Dependency) dependencies.next();
451 
452             String groupId = dep.getGroupId();
453             String artifactId = dep.getArtifactId();
454             VersionRange versionRange = VersionRange.createFromVersionSpec( dep.getVersion() );
455             String type = dep.getType();
456             if ( type == null )
457             {
458                 type = "jar";
459             }
460             String classifier = dep.getClassifier();
461             boolean optional = dep.isOptional();
462             String scope = dep.getScope();
463             if ( scope == null )
464             {
465                 scope = Artifact.SCOPE_COMPILE;
466             }
467 
468             Artifact artifact = artifactFactory.createDependencyArtifact( groupId, artifactId, versionRange, type,
469                                                                           classifier, scope, optional );
470 
471             if ( scope.equalsIgnoreCase( Artifact.SCOPE_SYSTEM ) )
472             {
473                 artifact.setFile( new File( dep.getSystemPath() ) );
474             }
475 
476             List exclusions = new ArrayList();
477             for ( Iterator j = dep.getExclusions().iterator(); j.hasNext(); )
478             {
479                 Exclusion e = (Exclusion) j.next();
480                 exclusions.add( e.getGroupId() + ":" + e.getArtifactId() );
481             }
482 
483             ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions );
484 
485             artifact.setDependencyFilter( newFilter );
486 
487             artifacts.add( artifact );
488         }
489 
490         return artifacts;
491     }
492 
493     private Map createManagedVersionMap( ArtifactFactory artifactFactory, String projectId,
494                                          DependencyManagement dependencyManagement )
495         throws ProjectBuildingException
496     {
497         Map map;
498         if ( dependencyManagement != null && dependencyManagement.getDependencies() != null )
499         {
500             map = new HashMap();
501             for ( Iterator i = dependencyManagement.getDependencies().iterator(); i.hasNext(); )
502             {
503                 Dependency d = (Dependency) i.next();
504 
505                 try
506                 {
507                     VersionRange versionRange = VersionRange.createFromVersionSpec( d.getVersion() );
508                     Artifact artifact = artifactFactory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(),
509                                                                                   versionRange, d.getType(),
510                                                                                   d.getClassifier(), d.getScope(),
511                                                                                   d.isOptional() );
512                     map.put( d.getManagementKey(), artifact );
513                 }
514                 catch ( InvalidVersionSpecificationException e )
515                 {
516                     throw new ProjectBuildingException( projectId, "Unable to parse version '" + d.getVersion()
517                         + "' for dependency '" + d.getManagementKey() + "': " + e.getMessage(), e );
518                 }
519             }
520         }
521         else
522         {
523             map = Collections.EMPTY_MAP;
524         }
525         return map;
526     }
527 
528     public Log getLog()
529     {
530         if ( log == null )
531         {
532             log = super.getLog();
533         }
534 
535         return log;
536     }
537 }