blob: 83a87cbd831a3f044259ffb2ac7180f35a9b40cd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2015 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.git.pgm.internal.app;
import static org.eclipse.emf.compare.git.pgm.internal.Options.SHOW_STACK_TRACE_OPT;
import static org.eclipse.emf.compare.git.pgm.internal.util.EMFCompareGitPGMUtil.EMPTY_STRING;
import static org.eclipse.emf.compare.git.pgm.internal.util.EMFCompareGitPGMUtil.SEP;
import static org.eclipse.emf.compare.git.pgm.internal.util.EMFCompareGitPGMUtil.toFileWithAbsolutePath;
import static org.eclipse.emf.compare.git.pgm.internal.util.EMFCompareGitPGMUtil.waitEgitJobs;
import java.io.File;
import java.io.IOException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.mapping.RemoteResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber;
import org.eclipse.egit.core.synchronize.GitSubscriberResourceMappingContext;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.git.pgm.Returns;
import org.eclipse.emf.compare.git.pgm.internal.ProgressPageLog;
import org.eclipse.emf.compare.git.pgm.internal.args.CmdLineParserRepositoryBuilder;
import org.eclipse.emf.compare.git.pgm.internal.args.GitDirHandler;
import org.eclipse.emf.compare.git.pgm.internal.args.SetupFileHandler;
import org.eclipse.emf.compare.git.pgm.internal.exception.Die;
import org.eclipse.emf.compare.git.pgm.internal.exception.Die.DeathType;
import org.eclipse.emf.compare.git.pgm.internal.exception.Die.DiesOn;
import org.eclipse.emf.compare.git.pgm.internal.util.EMFCompareGitPGMUtil;
import org.eclipse.emf.compare.ide.ui.internal.logical.EMFModelProvider;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.equinox.p2.metadata.ILicense;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.oomph.base.provider.BaseEditUtil;
import org.eclipse.oomph.internal.setup.SetupPrompter;
import org.eclipse.oomph.resources.ResourcesFactory;
import org.eclipse.oomph.resources.SourceLocator;
import org.eclipse.oomph.setup.Project;
import org.eclipse.oomph.setup.SetupPackage;
import org.eclipse.oomph.setup.SetupTask;
import org.eclipse.oomph.setup.Trigger;
import org.eclipse.oomph.setup.internal.core.SetupContext;
import org.eclipse.oomph.setup.internal.core.SetupTaskPerformer;
import org.eclipse.oomph.setup.internal.core.util.ECFURIHandlerImpl;
import org.eclipse.oomph.setup.internal.core.util.SetupCoreUtil;
import org.eclipse.oomph.setup.projects.ProjectsFactory;
import org.eclipse.oomph.setup.projects.ProjectsImportTask;
import org.eclipse.oomph.util.Confirmer;
import org.eclipse.oomph.util.IOUtil;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.Option;
/**
* Abstract class for any logical application.
*
* @author <a href="mailto:axel.richard@obeo.fr">Axel Richard</a>
*/
@SuppressWarnings("restriction")
public abstract class AbstractLogicalApplication implements IApplication {
/**
* Logs any message from oomph.
*/
protected ProgressPageLog progressPageLog;
/**
* Git repository for this command to be executed in.
*/
protected Repository repo;
/**
* Holds git directory location.
*/
@Argument(index = 0, metaVar = "gitFolderPath", usage = "Path to the .git folder of your repository.", handler = GitDirHandler.class)
protected String gitdir;
/**
* Holds the Oomph model setup file.
*/
@Argument(index = 1, metaVar = "<setup>", required = true, usage = "Path to the setup file. The setup file is a Oomph model.", handler = SetupFileHandler.class)
protected File setupFile;
/**
* Holds true if the java stack trace should be displayed in the console if any.
*/
@Option(name = SHOW_STACK_TRACE_OPT, usage = "Use this option to display java stack trace in console on error.")
private boolean showStackTrace;
/**
* Instance of {@link Git} from {@link #repo}.
*/
private Git git;
/**
* {@inheritDoc}.
*/
@Override
public Object start(IApplicationContext context) throws Exception {
Integer code;
// Prevents VM args if the application exits on something different that 0
System.setProperty(IApplicationContext.EXIT_DATA_PROPERTY, EMPTY_STRING);
final Map<?, ?> args = context.getArguments();
final String[] appArgs = (String[])args.get("application.args"); //$NON-NLS-1$
// This time it creates the repository using EGit code in order to add the repository to the EGit
// cache
final CmdLineParserRepositoryBuilder clp = CmdLineParserRepositoryBuilder
.newEGitRepoBuilderCmdParser(this);
try {
clp.parseArgument(appArgs);
repo = clp.getRepo();
git = new Git(repo);
} catch (CmdLineException err) {
if (showStackTrace) {
err.printStackTrace();
}
System.err.println(err.getMessage());
dispose();
return Returns.ERROR;
}
// CHECKSTYLE.OFF: IllegalCatch - We want to hide the strack trace if the showStackTrace option does
// not holds true.
try {
performStartup();
code = performGitCommand();
} catch (Die e) {
code = EMFCompareGitPGMUtil.handleDieError(e, showStackTrace);
} catch (Exception e) {
System.err.println(e.getMessage());
if (showStackTrace) {
e.printStackTrace();
}
code = Returns.ERROR.code();
} finally {
dispose();
}
// CHECKSTYLE.ON: IllegalCatch
return code;
}
/**
* {@inheritDoc}.
*/
public void stop() {
// Nothing to do.
}
/**
* Performs the logical git command (diff or merge).
*
* @return a {@link org.eclipse.emf.compare.git.pgm.Returns}.
* @throws Die
* e
*/
protected abstract Integer performGitCommand() throws Die;
/**
* Creates and configure the setup task performer to execute the imports of projects referenced in the
* user setup model. Then call the {@link #performGitCommand()}.
*
* @throws Die
* e
*/
protected void performStartup() throws Die {
ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(BaseEditUtil
.createAdapterFactory());
ResourceSet rs = SetupCoreUtil.createResourceSet();
rs.eAdapters().add(
new AdapterFactoryEditingDomain.EditingDomainProvider(new AdapterFactoryEditingDomain(
adapterFactory, null, rs)));
rs.getLoadOptions().put(ECFURIHandlerImpl.OPTION_CACHE_HANDLING,
ECFURIHandlerImpl.CacheHandling.CACHE_WITHOUT_ETAG_CHECKING);
URI startupSetupURI = URI.createFileURI(setupFile.getAbsolutePath());
Resource startupSetupResource = rs.getResource(startupSetupURI, true);
Project startupSetupProject = (Project)EcoreUtil.getObjectByType(startupSetupResource.getContents(),
SetupPackage.Literals.PROJECT);
SetupContext setupContext = SetupContext.create(rs);
// CHECKSTYLE.OFF: IllegalCatch - No choice since Oomph launch such an exception. We want to handle
// exception ourself
try {
Trigger triggerStartup = Trigger.STARTUP;
URIConverter uriConverter = rs.getURIConverter();
SetupTaskPerformer performerStartup = SetupTaskPerformer.create(uriConverter,
SetupPrompter.CANCEL, triggerStartup, setupContext, false);
Confirmer confirmer = Confirmer.ACCEPT;
performerStartup.put(ILicense.class, confirmer);
performerStartup.put(Certificate.class, confirmer);
progressPageLog = new ProgressPageLog(System.out);
performerStartup.setProgress(progressPageLog);
cleanWorkspace();
handleImportProjects(startupSetupProject, performerStartup);
performerStartup.perform(new NullProgressMonitor());
validatePerform(performerStartup);
waitEgitJobs();
} catch (Die e) {
throw e;
} catch (Exception e) {
final String message;
if (e instanceof CoreException) {
message = EMFCompareGitPGMUtil.getStatusMessage(((CoreException)e).getStatus());
} else {
message = "Error during Oomph operation";
}
throw new DiesOn(DeathType.FATAL).duedTo(e).displaying(message).ready();
}
// CHECKSTYLE.ON: IllegalCatch
}
/**
* Returns a {@link Git} from the current {@link Repository}.
*
* @return a {@link Git} from the current {@link Repository}.
*/
protected Git getGit() {
return git;
}
/**
* Close the repository and the log.
*/
protected void dispose() {
if (git != null) {
git.close();
}
if (repo != null) {
repo.close();
}
if (progressPageLog != null) {
progressPageLog.setTerminating();
}
}
/**
* Check if the file to test is EMFCompare compliant.
*
* @see org.eclipse.egit.ui.internal.CompareUtils#canDirectlyOpenInCompare(IFile)
* @param mergeContext
* a resource mapping context.
* @param file
* the file to test.
* @return true if the file to test is EMFCompare compliant, false otherwise.
*/
protected boolean isEMFCompareCompliantFile(RemoteResourceMappingContext mergeContext, IFile file) {
try {
EMFModelProvider modelProvider = new EMFModelProvider();
ResourceMapping[] modelMappings = modelProvider.getMappings(file, mergeContext,
new NullProgressMonitor());
if (modelMappings.length > 0) {
return true;
}
} catch (CoreException e) {
e.printStackTrace();
}
return false;
}
/**
* Gets the tree iterator of the id located in the repository.
*
* @param repository
* the repository containing the id.
* @param id
* the id for which we want the tree iterator.
* @return the tree iterator of the id located in the repository.
* @throws IOException
* e
*/
protected AbstractTreeIterator getTreeIterator(Repository repository, ObjectId id) throws IOException {
final CanonicalTreeParser p = new CanonicalTreeParser();
try (ObjectReader or = repository.newObjectReader()) {
p.reset(or, new RevWalk(repository).parseTree(id));
return p;
}
}
/**
* Returns <code>true</code> if the user has required to display error stack trace in the console.
*
* @return <code>true</code> if the user has required to display error stack trace in the console,
* <code>false</code> otherwise.
*/
protected boolean isShowStackTrace() {
return showStackTrace;
}
/**
* Simulate a comparison between the two given references and returns back the subscriber that can provide
* all computed synchronization information.
*
* @param repository
* the current repository.
* @param sourceRef
* Source reference (i.e. "left" side of the comparison).
* @param targetRef
* Target reference (i.e. "right" side of the comparison).
* @param comparedFile
* The file we are comparing (that would be the file right-clicked into the workspace).
* @return The created subscriber.
* @throws IOException
* e
*/
protected RemoteResourceMappingContext createSubscriberForComparison(Repository repository,
ObjectId sourceRef, ObjectId targetRef, IFile comparedFile) throws IOException {
final GitSynchronizeData data = new GitSynchronizeData(repository, sourceRef.getName(), targetRef
.getName(), false);
final GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(dataSet);
subscriber.init(new NullProgressMonitor());
return new GitSubscriberResourceMappingContext(subscriber, dataSet);
}
/**
* This will query all model providers for those that are enabled on the given file and list all mappings
* available for that file.
*
* @param mergeContext
* a resource mapping context.
* @param file
* The file for which we need the associated resource mappings.
* @return All mappings available for that file.
*/
protected ResourceMapping[] getResourceMappings(RemoteResourceMappingContext mergeContext, IFile file) {
final Set<ResourceMapping> mappings = new LinkedHashSet<ResourceMapping>();
try {
EMFModelProvider modelProvider = new EMFModelProvider();
ResourceMapping[] modelMappings = modelProvider.getMappings(file, mergeContext,
new NullProgressMonitor());
for (ResourceMapping mapping : modelMappings) {
mappings.add(mapping);
}
} catch (CoreException e) {
e.printStackTrace();
}
return mappings.toArray(new ResourceMapping[mappings.size()]);
}
/**
* Create copy of the projects import task with all root folders of source locators convert in absolute
* paths.
*
* @param task
* the {@link ProjectsImportTask} to copy.
* @param resourceBasePath
* the resourceBasePath needed to compute absolute paths.
* @return copy of the projects import task with all root folders of source locators convert in absolute
* paths.
*/
private ProjectsImportTask createCopyWithAbsolutePath(ProjectsImportTask task,
final String resourceBasePath) {
ProjectsImportTask importTask = EcoreUtil.copy(task);
EList<SourceLocator> sourceLocators = importTask.getSourceLocators();
for (SourceLocator sourceLocator : sourceLocators) {
String projectAbsolutePath = toFileWithAbsolutePath(resourceBasePath,
sourceLocator.getRootFolder()).toString();
sourceLocator.setRootFolder(projectAbsolutePath);
}
return importTask;
}
/**
* Validate that the perform operation has been successfully executed for the given
* {@link SetupTaskPerformer}.
*
* @param performerStartup
* the given {@link SetupTaskPerformer}.
* @throws Die
* e
*/
private void validatePerform(SetupTaskPerformer performerStartup) throws Die {
if (!performerStartup.hasSuccessfullyPerformed()) {
throw new DiesOn(DeathType.FATAL).displaying("Error during Oomph operation: Failure").ready();
}
if (performerStartup.isCanceled()) {
throw new DiesOn(DeathType.FATAL).displaying("Error during Oomph operation: Canceled").ready();
}
if (performerStartup.isRestartNeeded()) {
throw new DiesOn(DeathType.FATAL).displaying("Error during Oomph operation: Need restart")
.ready();
}
}
/**
* Clean the workspace.
*
* @throws Die
* e
*/
private void cleanWorkspace() throws Die {
final IWorkspace workspace = org.eclipse.core.resources.ResourcesPlugin.getWorkspace();
try {
workspace.run(new WorkspaceCleaner(workspace), null);
} catch (CoreException e) {
throw new DiesOn(DeathType.FATAL).duedTo(e).ready();
}
}
/**
* Handle ProjectsImport tasks.
*
* @param startupSetupProject
* the root of the setup model.
* @param performerStartup
* the SetupTaskPerformer.
*/
private void handleImportProjects(Project startupSetupProject, SetupTaskPerformer performerStartup) {
List<ProjectsImportTask> projectToImport = new ArrayList<ProjectsImportTask>();
// Import Projects & execute other startup tasks.
final String resourcePath = startupSetupProject.eResource().getURI().toFileString();
final String resourceBasePath = resourcePath.substring(0, resourcePath.lastIndexOf(SEP));
for (SetupTask setupTask : startupSetupProject.getSetupTasks()) {
if (setupTask instanceof ProjectsImportTask) {
// Convert locations of projects to absolute paths.
ProjectsImportTask importTask = createCopyWithAbsolutePath((ProjectsImportTask)setupTask,
resourceBasePath);
performerStartup.getTriggeredSetupTasks().add(importTask);
projectToImport.add(importTask);
} else {
performerStartup.getTriggeredSetupTasks().add(setupTask);
}
}
// If no ProjectsImportTask found, import all projects in repo
if (projectToImport.isEmpty()) {
ProjectsImportTask importTask = ProjectsFactory.eINSTANCE.createProjectsImportTask();
SourceLocator sourceLocator = ResourcesFactory.eINSTANCE.createSourceLocator(repo.getWorkTree()
.getAbsolutePath(), false);
importTask.getSourceLocators().add(sourceLocator);
performerStartup.getTriggeredSetupTasks().add(importTask);
}
}
/**
* An workspace action that removes all projects from the workspace. It also deletes
* ".plugins/org.eclipse.oomph.setup.projects/import-history.properties"file since it contains references
* of projects imported by Oomph.
*
* @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a>
*/
private final class WorkspaceCleaner implements IWorkspaceRunnable {
/** Workspace to clean. */
private final IWorkspace workspace;
/**
* Constructor.
*
* @param workspace
* Workspace to clean
*/
private WorkspaceCleaner(IWorkspace workspace) {
this.workspace = workspace;
}
@Override
public void run(IProgressMonitor monitor) throws CoreException {
IWorkspaceRoot root = workspace.getRoot();
for (IProject project : root.getProjects()) {
project.delete(false, true, monitor);
}
for (File file : root.getLocation().toFile().listFiles()) {
if (file.isDirectory()) {
if (".metadata".equals(file.getName())) { //$NON-NLS-1$
// Deletes the Oomph import-history.properties to force new import
File importHistory = new File(file.getAbsolutePath() + SEP
+ ".plugins/org.eclipse.oomph.setup.projects/import-history.properties"); //$NON-NLS-1$
if (importHistory.exists()) {
IOUtil.deleteBestEffort(importHistory);
try {
importHistory.createNewFile();
} catch (IOException e) {
throw new CoreException(
new Status(IStatus.ERROR, "org.eclipse.emf.compare.git.pgm",
"Unable to delete the file .plugins/org.eclipse.oomph.setup.projects/import-history.properties"));
}
}
}
}
}
}
}
}