org.s4i.feature.FeatureMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.s4i.feature.FeatureMojo.java

Source

/*
 * 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.s4i.feature;

import org.apache.commons.lang.StringUtils;
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.plexus.util.WriterFactory;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.pde.internal.core.feature.*;
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;

import java.io.*;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;

/**
 * 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 ECLIPSE_FEATURE_TYPE = "eclipse-feature";

    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);
    }

    /**
     * The feature.xml version calculated based on the maven pom version
     */
    private String getVersion() {
        String osgiVersion = maven2OsgiConverter.getVersion(getProject().getVersion());

        // Replace "SNAPSHOT" with "qualifier" for Tycho
        String lastPart = StringUtils.substringAfterLast(osgiVersion, ".");
        if (StringUtils.equals(lastPart, "SNAPSHOT")) {
            return StringUtils.substringBeforeLast(osgiVersion, ".") + ".qualifier";
        }

        return osgiVersion;
    }

    private Feature createFeature() throws MojoExecutionException, MojoFailureException {
        Feature feature;

        feature = new Feature();

        try {
            IFeatureModel model = new WorkspaceFeatureModel();
            feature.setModel(model);

            feature.setId(getProject().getArtifactId());
            feature.setLabel('%' + FEATURE_NAME_PROPERTY);
            feature.setVersion(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 (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())) {
                IFeaturePlugin plugin = createFeaturePlugin(model, node);
                if (plugin == null) {
                    getLog().debug("Ignoring artifact " + artifact);
                } else {
                    plugins.add(plugin);
                }
            } else {
                imports.add(createFeatureImport(model, node));
            }

            /* continue with the children */
            processNode(node, model, plugins, imports);
        }
    }

    private static String getBundleSymbolicName(Attributes attrs) {
        String name = attrs.getValue("Bundle-SymbolicName");
        if (name == null)
            return null;

        StringTokenizer token = new StringTokenizer(name, ";");
        return token.nextToken().trim();
    }

    private static String getBundleVersion(Attributes attrs) {
        return attrs.getValue("Bundle-Version");
    }

    private static boolean isFragment(Attributes attrs) {
        return attrs.getValue("Fragment-Host") != null;
    }

    private IFeaturePlugin createFeaturePlugin(IFeatureModel model, DependencyNode node)
            throws CoreException, MojoExecutionException {
        FeaturePlugin featurePlugin = new FeaturePlugin();
        featurePlugin.setModel(model);
        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()));
        }

        String type = node.getArtifact().getType();
        if (!"jar".equals(type)) {
            getLog().error("Unsupported type \"" + type + "\" for artifact: " + node.getArtifact().getId());
            return null;
        }

        try {
            JarFile jarFile = new JarFile(file);
            Manifest manifest = jarFile.getManifest();
            if (manifest == null) {
                getLog().warn("Artifact is not an OSGi bundle and will be ignored (no manifest): "
                        + node.getArtifact().getId());
                return null;
            }

            Attributes attrs = manifest.getMainAttributes();
            String bsn = getBundleSymbolicName(attrs);
            String ver = getBundleVersion(attrs);
            if (bsn == null || ver == null) {
                getLog().warn("Artifact is not an OSGi bundle and will be ignored (no BSN/version): "
                        + node.getArtifact().getId());
                return null;
            }

            featurePlugin.setId(bsn);
            featurePlugin.setVersion(ver);
            featurePlugin.setFragment(isFragment(attrs));

            getLog().info("Adding OSGi bundle to feature: " + node.getArtifact().getId());
        } catch (Exception e) {
            getLog().error("Artifact is not an OSGi bundle and will be ignored: " + node.getArtifact().getId(), e);
            return null;
        }

        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 (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));
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
            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
                }
            }
        }
    }
}