view com.redhat.thermostat.tools.eclipse.plugin/src/com/redhat/thermostat/tools/eclipse/plugin/wizards/ProjectCreator.java @ 131:b40698bda910

Create a nested project structure Put the parent pom in parent directory and the submodules in nested directories. Use explicit paths when creating projects to teach eclipse about this nested layout.
author Omair Majid <omajid@redhat.com>
date Fri, 30 May 2014 16:37:22 -0400
parents df34d0465d83
children 31d0ab4467ad
line wrap: on
line source

package com.redhat.thermostat.tools.eclipse.plugin.wizards;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.NotEnabledException;
import org.eclipse.core.commands.NotHandledException;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.IHandlerService;

import com.redhat.thermostat.tools.eclipse.plugin.Activator;
import com.redhat.thermostat.tools.eclipse.plugin.wizards.PluginXmlCreator.Decisions;

/** Creates a Thermostat project using the preferred thermostat defaults, settings, and style */
public class ProjectCreator {

    private static final String POM_FILE_NAME = "pom.xml";
    private static final String POM_HEADER = ""
            + "<?xml version='1.0' encoding='UTF-8'?>\n"
            + "<project xsi:schemaLocation='http://maven.apache.org/POM/4.0.0\n"
            + "                             http://maven.apache.org/xsd/maven-4.0.0.xsd'\n"
            + "         xmlns='http://maven.apache.org/POM/4.0.0'\n"
            + "         xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>\n"
            + "\n"
            + "  <modelVersion>4.0.0</modelVersion>\n"
            + "\n";
    private static final String POM_FOOTER = ""
            + "</project>\n";

    private static final String OSGI_DEPENDENCIES_WITH_VERSION = ""
            + "      <dependency>\n"
            + "        <groupId>org.osgi</groupId>\n"
            + "        <artifactId>org.osgi.core</artifactId>\n"
            + "        <version>4.3.1</version>\n"
            + "      </dependency>\n"
            + "      <dependency>\n"
            + "        <groupId>org.osgi</groupId>\n"
            + "        <artifactId>org.osgi.compendium</artifactId>\n"
            + "        <version>4.3.1</version>\n"
            + "      </dependency>\n";

    private static final String OSGI_DEPENDENCIES = ""
            + "    <dependency>\n"
            + "      <groupId>org.osgi</groupId>\n"
            + "      <artifactId>org.osgi.core</artifactId>\n"
            + "    </dependency>\n"
            + "    <dependency>\n"
            + "      <groupId>org.osgi</groupId>\n"
            + "      <artifactId>org.osgi.compendium</artifactId>\n"
            + "    </dependency>\n";

    private static final String BUNDLE_PLUGIN_WITH_VERSION = ""
            + "        <plugin>\n"
            + "          <groupId>org.apache.felix</groupId>\n"
            + "          <artifactId>maven-bundle-plugin</artifactId>\n"
            + "          <version>2.3.7</version>\n"
            + "        </plugin>\n";

    private static final String BUNDLE_PLUGIN = ""
            + "      <plugin>\n"
            + "        <groupId>org.apache.felix</groupId>\n"
            + "        <artifactId>maven-bundle-plugin</artifactId>\n"
            + "        <extensions>true</extensions>\n"
            + "      </plugin>\n";

    private static final String BUNDLE_ACTIVATOR_NAME = "Activator";
    private static final String BUNDLE_ACTIVATOR = ""
            + "package ${package.name};\n" // ${package.name} is replaced with actual package name
            + "\n"
            + "import org.osgi.framework.BundleActivator;\n"
            + "import org.osgi.framework.BundleContext;\n"
            + "\n"
            + "public class " + BUNDLE_ACTIVATOR_NAME + " implements BundleActivator {\n"
            + "\n"
            + "    public void start(BundleContext context) throws Exception {\n"
            + "        // TODO Auto-generated method stub\n"
            + "    }\n"
            + "\n"
            + "    public void stop(BundleContext context) throws Exception {\n"
            + "        // TODO Auto-generated method stub\n"
            + "    }\n"
            + "\n"
            + "}\n";

    private IHandlerService serviceHandler;

    private String packagePrefix;
    private String groupId;
    private String artifactId;
    private String version;
    private String thermostatVersion;

    private String parentId;

    public ProjectCreator(String groupId, String artifactId, String version, String packagePrefix, String thermostatVersion) {
        this((IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class), groupId, artifactId, version, packagePrefix, thermostatVersion);
    }

    public ProjectCreator(IHandlerService handler, String groupId, String artifactId, String version, String packagePrefix, String thermostatVersion) {
        this.serviceHandler = handler;
        this.groupId = groupId;
        this.artifactId = artifactId;
        this.version = version;
        this.packagePrefix = packagePrefix;

        this.thermostatVersion = thermostatVersion;

        this.parentId = artifactId + "-parent";

    }

    public void create() throws CoreException, IOException {
        List<Project> projects = new ArrayList<>();
        Project project;

        Project parent;
        project = parent = createParentModuleProject();
        projects.add(project);

        project = createCommonSubproject(parent);
        projects.add(project);

        project = createJavaSubproject(parent, "agent");
        projects.add(project);

        project = createJavaSubproject(parent, "client");
        projects.add(project);

        project = createJavaSubproject(parent, "cli");
        projects.add(project);

        project = createDistributionProject(parent);
        projects.add(project);

        refreshProjects(projects);
    }

    private Project createParentModuleProject() throws CoreException, IOException {
        String projectName = artifactId;
        Project project = createMavenProject(null, projectName);

        String pomContents = ""
                + POM_HEADER
                + "  <groupId>" + groupId + "</groupId>\n"
                + "  <artifactId>" + parentId + "</artifactId>\n"
                + "  <version>" + version + "</version>\n"
                + "  <packaging>pom</packaging>\n"
                + "\n"
                + "  <modules>\n"
                + "    <module>" + this.artifactId + "-agent</module>\n"
                + "    <module>" + this.artifactId + "-common</module>\n"
                + "    <module>" + this.artifactId + "-client</module>\n"
                + "    <module>" + this.artifactId + "-cli</module>\n"
                + "    <module>" + this.artifactId + "-distribution</module>\n"
                + "  </modules>\n"
                + "\n"
                + "  <build>\n"
                + "    <pluginManagement>\n"
                + "      <plugins>\n"
                + BUNDLE_PLUGIN_WITH_VERSION
                + "      </plugins>\n"
                + "    </pluginManagement>\n"
                + "  </build>\n"
                + "\n"
                + "  <dependencyManagement>\n"
                + "    <dependencies>\n"
                + OSGI_DEPENDENCIES_WITH_VERSION
                + "    </dependencies>\n"
                + "  </dependencyManagement>\n"
                + POM_FOOTER;

        createPomFile(project, pomContents);

        return project;
    }

    private Project createCommonSubproject(Project parent) throws IOException, CoreException {
        String suffix = "common";
        String artifactId = this.artifactId + "-" + suffix;
        String packageName = packagePrefix + "." + suffix;
        Project project = createJavaCodeProject(parent, artifactId, packageName);

        String pomContents = ""
                + POM_HEADER
                + "\n"
                + createParentSection(groupId, parentId, version)
                + "\n"
                + "  <artifactId>" + artifactId + "</artifactId>\n"
                + "  <packaging>bundle</packaging>\n"
                + "\n"
                + "  <build>\n"
                + "    <plugins>\n"
                + BUNDLE_PLUGIN
                + "    </plugins>\n"
                + "  </build>\n"
                + "\n"
                + "  <dependencies>\n"
                + OSGI_DEPENDENCIES
                + "  </dependencies>\n"
                + POM_FOOTER;

        createPomFile(project, pomContents);

        return project;
    }

    private Project createJavaSubproject(Project parent, String subproject) throws CoreException, IOException {
        String artifactId = this.artifactId + "-" + subproject;
        String subpackage = this.packagePrefix + "." + subproject;

        Project project = createJavaCodeProject(parent, artifactId, subpackage);

        String pomContents = ""
                + POM_HEADER
                + createParentSection(groupId, parentId, version)
                + "\n"
                + "  <artifactId>" + artifactId + "</artifactId>\n"
                + "  <packaging>bundle</packaging>\n"
                + "\n"
                + "  <build>\n"
                + "    <plugins>\n"
                + BUNDLE_PLUGIN
                + "    </plugins>\n"
                + "  </build>\n"
                + "\n"
                + "  <dependencies>\n"
                + "    <dependency>\n"
                + "      <groupId>" + groupId + "</groupId>\n"
                + "      <artifactId>" + this.artifactId + "-common</artifactId>\n"
                + "      <version>" + version + "</version>\n"
                + "    </dependency>\n"
                + OSGI_DEPENDENCIES
                + "  </dependencies>\n"
                + POM_FOOTER;

        createPomFile(project, pomContents);

        return project;
    }

    private Project createJavaCodeProject(Project parent, String projectName, String packageName) throws CoreException {
        Project projects = createJavaAndMavenProject(parent, projectName);

        IProject project = projects.project;

        IFolder srcFolder = project.getFolder("src");
        srcFolder.create(false, true, null);

        IFolder srcMainFolder = srcFolder.getFolder("main");
        srcMainFolder.create(true, true, null);

        IFolder srcMainJavaFolder = srcMainFolder.getFolder("java");
        srcMainJavaFolder.create(true, true, null);

        IFolder srcTestFolder = srcFolder.getFolder("test");
        srcTestFolder.create(true, true, null);

        IFolder srcTestJavaFolder = srcTestFolder.getFolder("java");
        srcTestJavaFolder.create(true, true, null);

        createBundleActivator(projects, srcMainJavaFolder, packageName);

        return projects;
    }

    private void createBundleActivator(Project projects, IFolder root, String packageName) throws CoreException {
        IJavaProject javaProject = projects.javaProject;

        IPackageFragment pack = javaProject.getPackageFragmentRoot(root)
                                    .createPackageFragment(packageName, false, null);

        String bundleActivatorContents = BUNDLE_ACTIVATOR.replace("${package.name}", pack.getElementName());
        pack.createCompilationUnit(BUNDLE_ACTIVATOR_NAME + ".java", bundleActivatorContents, false, null);
    }

    private Project createDistributionProject(Project parent) throws CoreException, IOException {
        String artifactId = this.artifactId + "-distribution";

        Project projects = createJavaAndMavenProject(parent, artifactId);

        String pomContents = ""
                + POM_HEADER
                + createParentSection(groupId, parentId, version)
                + "\n"
                + "  <artifactId>" + artifactId + "</artifactId>\n"
                + "  <packaging>jar</packaging>\n"
                + "\n"
                + "  <properties>\n"
                + "    <thermostat.plugin>" + this.artifactId + "</thermostat.plugin>\n"
                + "  </properties>\n"
                + "\n"
                + "  <build>\n"
                + "    <plugins>\n"
                + "      <plugin>\n"
                + "        <artifactId>maven-assembly-plugin</artifactId>\n"
                + "        <dependencies>\n"
                + "          <dependency>\n"
                + "            <groupId>com.redhat.thermostat</groupId>\n"
                + "            <artifactId>thermostat-assembly</artifactId>\n"
                + "            <version>" + thermostatVersion + "</version>\n"
                + "          </dependency>\n"
                + "        </dependencies>\n"
                + "        <configuration>\n"
                + "          <descriptorRefs>\n"
                + "            <descriptorRef>plugin-assembly</descriptorRef>\n"
                + "          </descriptorRefs>\n"
                + "          <appendAssemblyId>false</appendAssemblyId>\n"
                + "        </configuration>\n"
                + "        <executions>\n"
                + "          <execution>\n"
                + "            <id>assemble-plugin</id>\n"
                + "            <phase>package</phase>\n"
                + "            <goals>\n"
                + "              <goal>single</goal>\n"
                + "            </goals>\n"
                + "          </execution>\n"
                + "        </executions>\n"
                + "      </plugin>\n"
                + "    </plugins>\n"
                + "  </build>\n"
                + "\n"
                + "  <!-- Explicitly list all plug-in artifacts, transitive dependencies\n"
                + "       are not included in assembly. -->\n"
                + "  <dependencies>\n"
                + "    <dependency>\n"
                + "      <groupId>" + groupId + "</groupId>\n"
                + "      <artifactId>" + this.artifactId + "-agent</artifactId>\n"
                + "      <version>${project.version}</version>\n"
                + "    </dependency>\n"
                + "    <dependency>\n"
                + "      <groupId>" + groupId + "</groupId>\n"
                + "      <artifactId>" + this.artifactId + "-common</artifactId>\n"
                + "      <version>${project.version}</version>\n"
                + "    </dependency>\n"
                + "    <dependency>\n"
                + "      <groupId>" + groupId + "</groupId>\n"
                + "      <artifactId>" + this.artifactId + "-client</artifactId>\n"
                + "      <version>${project.version}</version>\n"
                + "    </dependency>\n"
                + "    <dependency>\n"
                + "      <groupId>" + groupId + "</groupId>\n"
                + "      <artifactId>" + this.artifactId + "-cli</artifactId>\n"
                + "      <version>${project.version}</version>\n"
                + "    </dependency>\n"
                + "  </dependencies>\n"
                + POM_FOOTER;

        createPomFile(projects, pomContents);

        IProject project = projects.project;

        IFolder srcFolder = project.getFolder("src");
        srcFolder.create(false, true, null);

        IFolder srcMainFolder = srcFolder.getFolder("main");
        srcMainFolder.create(true, true, null);

        IFolder srcMainResourcesFolder = srcMainFolder.getFolder("resources");
        srcMainResourcesFolder.create(true, true, null);

        createThermostatPluginXml(project.getName());

        return projects;
    }

    private void createThermostatPluginXml(String projectName) throws CoreException, IOException {
        PluginXmlCreator creator = new PluginXmlCreator(ResourcesPlugin.getWorkspace().getRoot());
        creator.create(projectName, new NullProgressMonitor(), new Decisions() {
            @Override
            public boolean overwriteExistingFile() {
                return true;
            }
        });
    }

    private Project createJavaAndMavenProject(Project parent, String projectName) throws CoreException {
        Project project = createMavenProject(parent, projectName);

        addJavaNature(project.project);

        IJavaProject javaProject = JavaCore.create(project.project);

        return new Project(project.project, javaProject);
    }

    private Project createMavenProject(Project parent, String projectName) throws CoreException {
        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        IWorkspaceRoot root = workspace.getRoot();

        IProject project = root.getProject(projectName);

        if (project.exists()) {
            project.delete(true, null);
        }
        try {
            if (parent == null) {
                project.create(null);
            } else {
                IProjectDescription description = workspace.newProjectDescription(projectName);
                description.setLocation(parent.project.getLocation().append(projectName));
                project.create(description, null);
            }
            project.open(null);
        } catch (CoreException e) {
            e.printStackTrace();
        }

        addMavenNature(project);

        return new Project(project, null);
    }

    private void addJavaNature(IProject project) throws CoreException {
        IProjectDescription description = project.getDescription();
        addJavaNature(description);
        project.setDescription(description, null);
    }

    private void addJavaNature(IProjectDescription description) {
        addNature(description, JavaCore.NATURE_ID);
    }

    private void addMavenNature(IProject project) throws CoreException {
        IProjectDescription description = project.getDescription();
        addMavenNature(description);
        project.setDescription(description, null);
    }

    private void addMavenNature(IProjectDescription description) {
        // provided by m2e
        // TODO what's the symbolic constant for this? IMavenConstants.NATURE_ID?
        addNature(description, "org.eclipse.m2e.core.maven2Nature");
    }

    private void addNature(IProjectDescription description, String nature) {
        String[] existingNatureIds = description.getNatureIds();
        Set<String> natures = new TreeSet<>(Arrays.asList(existingNatureIds));
        natures.add(nature);
        String[] newNatureIds = natures.toArray(new String[natures.size()]);
        description.setNatureIds(newNatureIds);
    }

    private void createPomFile(Project projects, String contents) throws IOException, CoreException {
        IProject project = projects.project;
        IFile pomFile = project.getFile(POM_FILE_NAME);
        try (InputStream contentStream = new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))) {
            if (pomFile.exists()) {
                pomFile.setContents(contentStream, true, true, null);
            } else {
                pomFile.create(contentStream, true, null);
            }
        }
    }

    private String createParentSection(String groupId, String parentId, String version) {
        return ""
            + "  <parent>\n"
            + "    <groupId>" + groupId + "</groupId>\n"
            + "    <artifactId>" + parentId + "</artifactId>\n"
            + "    <version>" + version + "</version>\n"
            + "  </parent>\n";
    }

    private void refreshProjects(List<Project> projects) throws CoreException {
        // Refresh projects
        ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IWorkspaceRoot.DEPTH_INFINITE, new NullProgressMonitor());

        // Refresh again, just to make sure changes are sane by the time maven sees them
        ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IWorkspaceRoot.DEPTH_INFINITE, new NullProgressMonitor());

        // TODO wait for refresh to complete

        tellM2eAboutNewProjects(projects);
    }

    private void tellM2eAboutNewProjects(List<Project> projects) throws CoreException {
        try {
            refreshMavenProjectUsingInternalApi(projects);
        } catch (UnsupportedOperationException e) {
            try {
                refreshMavenProjectUsingUglyButPublicCommandFallback();
            } catch (UnsupportedOperationException e2) {
                Status s = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error refreshing maven configuration", e2);
                throw new CoreException(s);
            }
        }
    }

    private void refreshMavenProjectUsingInternalApi(List<Project> projects) throws UnsupportedOperationException {
        List<IProject> actualProjects = new ArrayList<>();
        for (Project project : projects) {
            actualProjects.add(project.project);
        }
        IProject[] projectsToUpdate = actualProjects.toArray(new IProject[0]);

        try {
            // UpdateMavenProjectJob updateProjectsJob = new UpdateMavenProjectJob(projectsToUpdate);
            Class<?> klass = Class.forName("org.eclipse.m2e.core.ui.internal.UpdateMavenProjectJob");
            Constructor<?> constructor = klass.getConstructor(IProject[].class);
            Object updateProjectsJob = constructor.newInstance((Object)projectsToUpdate);

            // updateProjectsJob.schedule();
            Method schedule = klass.getMethod("schedule");
            schedule.invoke(updateProjectsJob);
        } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) {
            throw new UnsupportedOperationException("Incompatible m2e version", e);
        }
    }

    /** This interrupts the UI by showing a completely un-needed prompt */
    private void refreshMavenProjectUsingUglyButPublicCommandFallback() throws UnsupportedOperationException {
        final String UPADATE_MAVEN_PROJECT_COMMAND = "org.eclipse.m2e.core.ui.command.updateProject";
        try {
            serviceHandler.executeCommand(UPADATE_MAVEN_PROJECT_COMMAND, null);
        } catch (ExecutionException | NotDefinedException | NotEnabledException | NotHandledException e) {
            throw new UnsupportedOperationException("Unable to refresh m2e using " + UPADATE_MAVEN_PROJECT_COMMAND);
        }
    }

    private static class Project {
        private final IProject project;
        private final IJavaProject javaProject;

        public Project(IProject project, IJavaProject javaProject) {
            this.project = project;
            this.javaProject = javaProject;
        }
    }
}