package org.codehaus.mojo.sysdeo.ide; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.metadata.ArtifactMetadataSource; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.resolver.ArtifactCollector; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolutionResult; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.artifact.resolver.DebugResolutionListener; import org.apache.maven.artifact.resolver.ResolutionNode; import org.apache.maven.artifact.resolver.WarningResolutionListener; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; import org.apache.maven.artifact.resolver.filter.ExcludesArtifactFilter; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Exclusion; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.logging.LogEnabled; import org.codehaus.plexus.logging.Logger; /** * Abstract base plugin which takes care of the common stuff usually needed by maven IDE plugins. A plugin extending * AbstractIdeSupportMojo should implement the <code>setup()</code> and <code>writeConfiguration()</code> methods, plus * the getters needed to get the various configuration flags and required components. The lifecycle: * * <pre> * *** calls setup() where you can configure your specific stuff and stop the mojo from execute if appropriate *** * - manually resolve project dependencies, NOT failing if a dependency is missing * - compute project references (reactor projects) if the getUseProjectReferences() flag is set * *** calls writeConfiguration(), passing the list of resolved referenced dependencies *** * - report the list of missing sources or just tell how to turn this feature on if the flag was disabled * </pre> * * @author Fabrizio Giustina * @version $Id$ */ public abstract class AbstractIdeSupportMojo extends AbstractMojo implements LogEnabled { /** * The project whose project files to create. * * @parameter expression="${project}" * @required * @readonly */ private MavenProject project; /** * Artifact factory, needed to download source jars for inclusion in classpath. * * @component role="org.apache.maven.artifact.factory.ArtifactFactory" * @required * @readonly */ private ArtifactFactory artifactFactory; /** * Artifact resolver, needed to download source jars for inclusion in classpath. * * @component role="org.apache.maven.artifact.resolver.ArtifactResolver" * @required * @readonly */ private ArtifactResolver artifactResolver; /** * Artifact collector, needed to resolve dependencies. * * @component role="org.apache.maven.artifact.resolver.ArtifactCollector" * @required * @readonly */ private ArtifactCollector artifactCollector; /** * @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource" hint="maven" */ private ArtifactMetadataSource artifactMetadataSource; /** * Remote repositories which will be searched for source attachments. * * @parameter expression="${project.remoteArtifactRepositories}" * @required * @readonly */ private List remoteArtifactRepositories; /** * Local maven repository. * * @parameter expression="${localRepository}" * @required * @readonly */ private ArtifactRepository localRepository; /** * If the executed project is a reactor project, this will contains the full list of projects in the reactor. * * @parameter expression="${reactorProjects}" * @required * @readonly */ private List reactorProjects; private List workspaceProjects = Collections.EMPTY_LIST; /** * Directory location of the <code>Eclipse</code> workspace. * * @parameter expression="${eclipse.workspace}" */ private File workspace; /** * Plexus logger needed for debugging manual artifact resolution. */ private Logger logger; /** * Getter for <code>artifactMetadataSource</code>. * * @return Returns the artifactMetadataSource. */ public ArtifactMetadataSource getArtifactMetadataSource() { return this.artifactMetadataSource; } /** * Setter for <code>artifactMetadataSource</code>. * * @param artifactMetadataSource The artifactMetadataSource to set. */ public void setArtifactMetadataSource( ArtifactMetadataSource artifactMetadataSource ) { this.artifactMetadataSource = artifactMetadataSource; } /** * Getter for <code>project</code>. * * @return Returns the project. */ public MavenProject getProject() { return this.project; } /** * Setter for <code>project</code>. * * @param project The project to set. */ public void setProject( MavenProject project ) { this.project = project; } /** * Getter for <code>reactorProjects</code>. * * @return Returns the reactorProjects. */ public List getReactorProjects() { return this.reactorProjects; } /** * Setter for <code>reactorProjects</code>. * * @param reactorProjects The reactorProjects to set. */ public void setReactorProjects( List reactorProjects ) { this.reactorProjects = reactorProjects; } /** * Getter for <code>remoteArtifactRepositories</code>. * * @return Returns the remoteArtifactRepositories. */ public List getRemoteArtifactRepositories() { return this.remoteArtifactRepositories; } /** * Setter for <code>remoteArtifactRepositories</code>. * * @param remoteArtifactRepositories The remoteArtifactRepositories to set. */ public void setRemoteArtifactRepositories( List remoteArtifactRepositories ) { this.remoteArtifactRepositories = remoteArtifactRepositories; } /** * Getter for <code>artifactFactory</code>. * * @return Returns the artifactFactory. */ public ArtifactFactory getArtifactFactory() { return this.artifactFactory; } /** * Setter for <code>artifactFactory</code>. * * @param artifactFactory The artifactFactory to set. */ public void setArtifactFactory( ArtifactFactory artifactFactory ) { this.artifactFactory = artifactFactory; } /** * Getter for <code>artifactResolver</code>. * * @return Returns the artifactResolver. */ public ArtifactResolver getArtifactResolver() { return this.artifactResolver; } /** * Setter for <code>artifactResolver</code>. * * @param artifactResolver The artifactResolver to set. */ public void setArtifactResolver( ArtifactResolver artifactResolver ) { this.artifactResolver = artifactResolver; } /** * Getter for <code>localRepository</code>. * * @return Returns the localRepository. */ public ArtifactRepository getLocalRepository() { return this.localRepository; } /** * Setter for <code>localRepository</code>. * * @param localRepository The localRepository to set. */ public void setLocalRepository( ArtifactRepository localRepository ) { this.localRepository = localRepository; } /** * return <code>false</code> if projects available in a reactor build should be considered normal dependencies, * <code>true</code> if referenced project will be linked and not need artifact resolution. * * @return <code>true</code> if referenced project will be linked and not need artifact resolution */ protected abstract boolean getUseProjectReferences(); /** * Hook for preparation steps before the actual plugin execution. * * @return <code>true</code> if execution should continue or <code>false</code> if not. * @throws MojoExecutionException generic mojo exception */ protected abstract boolean setup() throws MojoExecutionException; /** * Main plugin method where dependencies should be processed in order to generate IDE configuration files. * * @param deps list of <code>IdeDependency</code> objects, with artifacts, sources and javadocs already resolved * @throws MojoExecutionException generic mojo exception */ protected abstract void writeConfiguration( IdeDependency[] deps ) throws MojoExecutionException; /** * @see org.apache.maven.plugin.Mojo#execute() */ public final void execute() throws MojoExecutionException, MojoFailureException { boolean processProject = setup(); if ( !processProject ) { return; } // resolve artifacts IdeDependency[] deps = doDependencyResolution(); writeConfiguration( deps ); } /** * {@inheritDoc} * * @see org.codehaus.plexus.logging.LogEnabled#enableLogging(org.codehaus.plexus.logging.Logger) */ public void enableLogging( Logger logger ) { this.logger = logger; } /** * Resolve project dependencies. Manual resolution is needed in order to avoid resoltion of multiproject artifacts * (if projects will be linked each other an installed jar is not needed) and to avoid a failure when a jar is * missing. * * @throws MojoExecutionException if dependencies can't be resolved * @return resoved IDE dependencies, with attached jars for non-reactor dependencies */ protected IdeDependency[] doDependencyResolution() throws MojoExecutionException { if ( workspace != null ) { getLog().info( "read available projects in eclipse workspace" ); workspaceProjects = new ReadWorkspaceLocations().readWorkspace( workspace, getLog() ); } ArtifactRepository localRepo = getLocalRepository(); List dependencies = getProject().getDependencies(); // Collect the list of resolved IdeDependencies. List dependencyList = new ArrayList(); if ( dependencies != null ) { Map managedVersions = createManagedVersionMap( project.getId(), project.getDependencyManagement() ); ArtifactResolutionResult artifactResolutionResult = null; try { List listeners = new ArrayList(); if ( logger.isDebugEnabled() ) { listeners.add( new DebugResolutionListener( logger ) ); } listeners.add( new WarningResolutionListener( logger ) ); artifactResolutionResult = artifactCollector.collect( getProjectArtifacts(), project.getArtifact(), managedVersions, localRepo, project.getRemoteArtifactRepositories(), getArtifactMetadataSource(), null, listeners ); } catch ( ArtifactResolutionException e ) { getLog().debug( e.getMessage(), e ); getLog().error( Messages.getString( "artifactresolution", new Object[] { //$NON-NLS-1$ e.getGroupId(), e.getArtifactId(), e.getVersion(), e.getMessage() } ) ); // if we are here artifactResolutionResult is null, create a project without dependencies but don't fail // (this could be a reactor projects, we don't want to fail everything) return new IdeDependency[0]; } // keep track of added reactor projects in order to avoid duplicates Set emittedReactorProjectId = new HashSet(); for ( Iterator i = artifactResolutionResult.getArtifactResolutionNodes().iterator(); i.hasNext(); ) { ResolutionNode node = (ResolutionNode) i.next(); Artifact art = node.getArtifact(); boolean isReactorProject = getUseProjectReferences() && isAvailableAsAReactorProject( art ); // don't resolve jars for reactor projects if ( !isReactorProject ) { try { artifactResolver.resolve( art, node.getRemoteRepositories(), localRepository ); } catch ( ArtifactNotFoundException e ) { getLog().debug( e.getMessage(), e ); getLog().warn( Messages.getString( "artifactdownload", new Object[] { //$NON-NLS-1$ e.getGroupId(), e.getArtifactId(), e.getVersion(), e.getMessage() } ) ); } catch ( ArtifactResolutionException e ) { getLog().debug( e.getMessage(), e ); getLog().warn( Messages.getString( "artifactresolution", new Object[] { //$NON-NLS-1$ e.getGroupId(), e.getArtifactId(), e.getVersion(), e.getMessage() } ) ); } } if ( !isReactorProject || emittedReactorProjectId.add( art.getGroupId() + '-' + art.getArtifactId() ) ) { IdeDependency dep = new IdeDependency( art, isReactorProject ); dep = resolveWorkspaceProject( dep ); dependencyList.add( dep ); } } // @todo a final report with the list of missingArtifacts? } IdeDependency[] deps = (IdeDependency[]) dependencyList.toArray( new IdeDependency[dependencyList.size()] ); return deps; } /** * Returns the list of project artifacts. Also artifacts generated from referenced projects will be added, but with * the <code>resolved</code> property set to true. * * @return list of projects artifacts * @throws MojoExecutionException if unable to parse dependency versions */ private Set getProjectArtifacts() throws MojoExecutionException { // keep it sorted, this should avoid random classpath order in tests Set artifacts = new TreeSet(); for ( Iterator dependencies = getProject().getDependencies().iterator(); dependencies.hasNext(); ) { Dependency dependency = (Dependency) dependencies.next(); String groupId = dependency.getGroupId(); String artifactId = dependency.getArtifactId(); VersionRange versionRange; try { versionRange = VersionRange.createFromVersionSpec( dependency.getVersion() ); } catch ( InvalidVersionSpecificationException e ) { throw new MojoExecutionException( Messages .getString( "unabletoparseversion", new Object[] { //$NON-NLS-1$ dependency.getArtifactId(), dependency.getVersion(), dependency.getManagementKey(), e.getMessage() } ), e ); } String type = dependency.getType(); if ( type == null ) { type = "jar"; //$NON-NLS-1$ } String classifier = dependency.getClassifier(); boolean optional = dependency.isOptional(); String scope = dependency.getScope(); if ( scope == null ) { scope = Artifact.SCOPE_COMPILE; } Artifact art = getArtifactFactory().createDependencyArtifact( groupId, artifactId, versionRange, type, classifier, scope, optional ); if ( scope.equalsIgnoreCase( Artifact.SCOPE_SYSTEM ) ) { art.setFile( new File( dependency.getSystemPath() ) ); } List exclusions = new ArrayList(); for ( Iterator j = dependency.getExclusions().iterator(); j.hasNext(); ) { Exclusion e = (Exclusion) j.next(); exclusions.add( e.getGroupId() + ":" + e.getArtifactId() ); //$NON-NLS-1$ } ArtifactFilter newFilter = new ExcludesArtifactFilter( exclusions ); art.setDependencyFilter( newFilter ); artifacts.add( art ); } return artifacts; } /** * Utility method that locates a project producing the given artifact. * * @param artifact the artifact a project should produce. * @return <code>true</code> if the artifact is produced by a reactor projectart. */ private boolean isAvailableAsAReactorProject( Artifact artifact ) { if ( reactorProjects != null ) { for ( Iterator iter = reactorProjects.iterator(); iter.hasNext(); ) { MavenProject reactorProject = (MavenProject) iter.next(); if ( reactorProject.getGroupId().equals( artifact.getGroupId() ) && reactorProject.getArtifactId().equals( artifact.getArtifactId() ) ) { if ( reactorProject.getVersion().equals( artifact.getVersion() ) ) { return true; } else { getLog().info( "Artifact " + artifact.getId() + " already available as a reactor project, but with different version. Expected: " + artifact.getVersion() + ", found: " + reactorProject.getVersion() ); } } } } return false; } private Map createManagedVersionMap( String projectId, DependencyManagement dependencyManagement ) throws MojoExecutionException { Map map; if ( dependencyManagement != null && dependencyManagement.getDependencies() != null ) { map = new HashMap(); for ( Iterator i = dependencyManagement.getDependencies().iterator(); i.hasNext(); ) { Dependency d = (Dependency) i.next(); try { VersionRange versionRange = VersionRange.createFromVersionSpec( d.getVersion() ); Artifact artifact = artifactFactory.createDependencyArtifact( d.getGroupId(), d.getArtifactId(), versionRange, d .getType(), d.getClassifier(), d.getScope(), d.isOptional() ); map.put( d.getManagementKey(), artifact ); } catch ( InvalidVersionSpecificationException e ) { throw new MojoExecutionException( Messages.getString( "unabletoparseversion", new Object[] { //$NON-NLS-1$ projectId, d.getVersion(), d.getManagementKey(), e.getMessage() } ), e ); } } } else { map = Collections.EMPTY_MAP; } return map; } /** * @return the workspaceProjects */ public List getWorkspaceProjects() { return workspaceProjects; } public IdeDependency resolveWorkspaceProject( IdeDependency dep ) { if ( workspaceProjects.contains( dep ) ) { IdeDependency workspaceProject = (IdeDependency) workspaceProjects.get( workspaceProjects.indexOf( dep ) ); dep.setIdeProjectName( workspaceProject.getIdeProjectName() ); dep.setOutputDirectory( workspaceProject.getOutputDirectory() ); } return dep; } }