/* * 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. */ package org.codehaus.mojo.pde.feature; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Properties; 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.model.License; import org.apache.maven.model.Organization; 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.apache.maven.shared.dependency.tree.DependencyNode; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; import org.apache.maven.shared.osgi.Maven2OsgiConverter; import org.codehaus.mojo.pde.updatesite.UpdateSiteMojo; import org.codehaus.plexus.util.WriterFactory; import org.eclipse.core.runtime.CoreException; import org.eclipse.pde.internal.core.feature.Feature; import org.eclipse.pde.internal.core.feature.FeatureImport; import org.eclipse.pde.internal.core.feature.FeatureInfo; import org.eclipse.pde.internal.core.feature.FeaturePlugin; import org.eclipse.pde.internal.core.feature.WorkspaceFeatureModel; import org.eclipse.pde.internal.core.ifeature.IFeature; import org.eclipse.pde.internal.core.ifeature.IFeatureImport; import org.eclipse.pde.internal.core.ifeature.IFeatureModel; import org.eclipse.pde.internal.core.ifeature.IFeaturePlugin; /** * Generate an Eclipse feature file from the dependency list * * @author <a href="mailto:carlos@apache.org">Carlos Sanchez</a> * @version $Id$ * @requiresDependencyResolution runtime * @goal feature */ public class FeatureMojo extends AbstractMojo { public static final String FEATURE_NAME_PROPERTY = "featureName"; public static final String PROVIDER_NAME_PROPERTY = "providerName"; public static final String DESCRIPTION_PROPERTY = "description"; public static final String COPYRIGHT_PROPERTY = "copyright"; public static final String LICENSE_URL_PROPERTY = "licenseURL"; public static final String LICENSE_PROPERTY = "license"; /** * The project we are executing. * * @parameter expression="${project}" * @required * @readonly */ private MavenProject project; /** * The list of resolved dependencies from the current project. Since we're not resolving the dependencies by hand * here, the build will fail if some of these dependencies do not resolve. * * @parameter default-value="${project.artifacts}" * @required * @readonly */ private Collection artifacts; /** * Directory where the generated files will be saved * * @parameter expression="${project.build.outputDirectory}" * @required */ private File outputDirectory; /** * Whether to add dependencies as imports or include them as part of the feature. If <code>true</code>, all Maven * dependencies that are not in the same groupId as this project will be added as Eclipse plugin dependencies. * Dependencies of type "eclipse-feature" are always imported. * * @parameter */ private boolean useImports = false; /** * @component */ private Maven2OsgiConverter maven2OsgiConverter; /** * @component */ private DependencyTreeBuilder dependencyTreeBuilder; /** * Local repository. * * @parameter expression="${localRepository}" * @required * @readonly */ private ArtifactRepository localRepository; /** * @component */ private ArtifactMetadataSource artifactMetadataSource; /** * @component */ private ArtifactCollector collector; /** * @component */ private ArtifactFactory factory; void setProject( MavenProject project ) { this.project = project; } private MavenProject getProject() { return project; } void setArtifacts( Collection artifacts ) { this.artifacts = artifacts; } private Collection getArtifacts() { return artifacts; } void setOutputDirectory( File outputDirectory ) { this.outputDirectory = outputDirectory; } private File getOutputDirectory() { return outputDirectory; } public void execute() throws MojoExecutionException, MojoFailureException { Feature feature = createFeature(); writeFeature( feature ); Properties properties = createProperties(); writeProperties( properties ); } private Feature createFeature() throws MojoExecutionException, MojoFailureException { Feature feature; feature = new Feature(); try { IFeatureModel model = new WorkspaceFeatureModel(); feature.setModel( model ); feature.setId( maven2OsgiConverter.getBundleSymbolicName( getProject().getArtifact() ) ); feature.setLabel( '%' + FEATURE_NAME_PROPERTY ); feature.setVersion( maven2OsgiConverter.getVersion( getProject().getVersion() ) ); feature.setProviderName( '%' + PROVIDER_NAME_PROPERTY ); FeatureInfo description = new FeatureInfo( IFeature.INFO_DESCRIPTION ); description.setModel( model ); description.setDescription( '%' + DESCRIPTION_PROPERTY ); feature.setFeatureInfo( description, IFeature.INFO_DESCRIPTION ); FeatureInfo license = new FeatureInfo( IFeature.INFO_LICENSE ); license.setModel( model ); license.setURL( '%' + LICENSE_URL_PROPERTY ); license.setDescription( '%' + LICENSE_PROPERTY ); feature.setFeatureInfo( license, IFeature.INFO_LICENSE ); FeatureInfo copyright = new FeatureInfo( IFeature.INFO_COPYRIGHT ); copyright.setModel( model ); copyright.setDescription( '%' + COPYRIGHT_PROPERTY ); feature.setFeatureInfo( copyright, IFeature.INFO_COPYRIGHT ); ArrayList plugins = new ArrayList( getArtifacts().size() ); ArrayList imports = new ArrayList( getArtifacts().size() ); DependencyNode dependencyTree; try { dependencyTree = dependencyTreeBuilder.buildDependencyTree( project, localRepository, factory, artifactMetadataSource, null, collector ); } catch ( DependencyTreeBuilderException e ) { throw new MojoExecutionException( "Unable to build dependency tree", e ); } processNode( dependencyTree, model, plugins, imports ); feature.addPlugins( (IFeaturePlugin[]) plugins.toArray( new IFeaturePlugin[plugins.size()] ) ); feature.addImports( (IFeatureImport[]) imports.toArray( new IFeatureImport[imports.size()] ) ); } catch ( CoreException e ) { throw new MojoExecutionException( "Error creating the feature", e ); } return feature; } private void processNode( DependencyNode dependencyNode, IFeatureModel model, List plugins, List imports ) throws CoreException, MojoExecutionException { for ( Iterator it = dependencyNode.getChildren().iterator(); it.hasNext(); ) { DependencyNode node = (DependencyNode) it.next(); if ( node.getState() != DependencyNode.INCLUDED ) { continue; } Artifact artifact = node.getArtifact(); if ( Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) ) { getLog().debug( "Ignoring system scoped artifact " + artifact ); continue; } /* do not add children of features, Eclipse will handle that, and features are always imported */ if ( UpdateSiteMojo.ECLIPSE_FEATURE_TYPE.equals( artifact.getType() ) ) { imports.add( createFeatureImport( model, node ) ); continue; } else /* if it's not a feature we can include it or depend on it */ if ( !useImports || artifact.getGroupId().equals( getProject().getGroupId() ) ) { plugins.add( createFeaturePlugin( model, node ) ); } else { imports.add( createFeatureImport( model, node ) ); } /* continue with the children */ processNode( node, model, plugins, imports ); } } private IFeaturePlugin createFeaturePlugin( IFeatureModel model, DependencyNode node ) throws CoreException, MojoExecutionException { FeaturePlugin featurePlugin = new FeaturePlugin(); featurePlugin.setModel( model ); featurePlugin.setId( maven2OsgiConverter.getBundleSymbolicName( node.getArtifact() ) ); featurePlugin.setVersion( maven2OsgiConverter.getVersion( node.getArtifact() ) ); File file = node.getArtifact().getFile(); if ( file == null ) { /* the file was already resolved, it's just that is not available in the dependency node */ file = new File( this.localRepository.getBasedir(), this.localRepository.pathOf( node.getArtifact() ) ); } long size = file.length() / 1024; featurePlugin.setInstallSize( size ); featurePlugin.setDownloadSize( size ); featurePlugin.setUnpack( false ); return featurePlugin; } private IFeatureImport createFeatureImport( IFeatureModel model, DependencyNode node ) throws CoreException { FeatureImport featureImport = new FeatureImport(); featureImport.setModel( model ); featureImport.setId( maven2OsgiConverter.getBundleSymbolicName( node.getArtifact() ) ); if ( UpdateSiteMojo.ECLIPSE_FEATURE_TYPE.equals( node.getArtifact().getType() ) ) { featureImport.setType( IFeatureImport.FEATURE ); } else { featureImport.setType( IFeatureImport.PLUGIN ); } String version = node.getArtifact().getVersion(); featureImport.setVersion( maven2OsgiConverter.getVersion( version ) ); featureImport.setMatch( IFeatureImport.COMPATIBLE ); return featureImport; } private void writeFeature( Feature feature ) throws MojoExecutionException, MojoFailureException { File featureFile = new File( getOutputDirectory(), "feature.xml" ); featureFile.getParentFile().mkdirs(); PrintWriter writer = null; try { writer = new PrintWriter( WriterFactory.newXmlWriter( featureFile ) ); feature.write( "", writer ); } catch ( IOException e ) { throw new MojoExecutionException( "Unable to create feature file: " + featureFile, e ); } finally { if ( writer != null ) { writer.close(); } } } private void put( Properties properties, String key, String value ) { if ( value != null ) { properties.put( key, value ); } } private Properties createProperties() throws MojoExecutionException, MojoFailureException { Properties properties = new Properties(); put( properties, FEATURE_NAME_PROPERTY, getProject().getName() ); Organization organization = getProject().getOrganization(); if ( organization != null ) { put( properties, PROVIDER_NAME_PROPERTY, organization.getName() ); StringBuffer sb = new StringBuffer(); sb.append( "Copyright " ); sb.append( organization.getName() ); if ( organization.getUrl() != null ) { sb.append( " " ); sb.append( organization.getUrl() ); } put( properties, COPYRIGHT_PROPERTY, sb.toString() ); } put( properties, DESCRIPTION_PROPERTY, getProject().getDescription() ); List licenses = getProject().getLicenses(); if ( licenses.isEmpty() ) { throw new MojoFailureException( "No license in pom. This feature will not work in an update site." ); } else if ( licenses.size() > 1 ) { throw new MojoFailureException( "There is more than one license in the pom, " + "features can only have one license" ); } else { License license = (License) getProject().getLicenses().get( 0 ); put( properties, LICENSE_URL_PROPERTY, license.getUrl() ); put( properties, LICENSE_PROPERTY, license.getName() ); } return properties; } private void writeProperties( Properties properties ) throws MojoExecutionException, MojoFailureException { File propertiesFile = new File( getOutputDirectory(), "feature.properties" ); propertiesFile.getParentFile().mkdirs(); OutputStream out = null; try { out = new FileOutputStream( propertiesFile ); properties.store( out, "" ); } catch ( IOException e ) { throw new MojoExecutionException( "Unable to create properties file: " + propertiesFile, e ); } finally { if ( out != null ) { try { out.close(); } catch ( IOException e ) { // ignore } } } } }