EMFStore - API Hello World Example


This code example shows how to use the basic API of the EMFStore. In this tutorial you will learn how to use the API to integrate the EMF Store into your own application. This example demonstrates, how to programmatically merge a conflict occuring if two clients have overlapping changes. This is useful, if you create a custom client for the EMFStore and you want to implement custom merging algorithms. Please note that the EMFStore provides a default conflict detector and a default merging algorithm. It also provides a default Merging UI. Therefore EMFStore supports interactive merging on models without any adaptation. If you just want to make yourself familiar with the features of EMFStore and the existing merging algorithm and UI, you can also use the default UI of the EMFStore provided by the EMF client Platform, see here. All code example can be installed using our examples manager (starting from version 0.9.0) or be checked out from the repository.
In particular, this example demonstrates how to:
/**
 *  Copyright (c) 2008-2012 Jonas Helming, Maximilian Koegel. 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 
 */
package org.eclipse.emf.emfstore.example.merging;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.emfstore.bowling.BowlingFactory;
import org.eclipse.emf.emfstore.bowling.BowlingPackage;
import org.eclipse.emf.emfstore.bowling.League;
import org.eclipse.emf.emfstore.bowling.Player;
import org.eclipse.emf.emfstore.client.model.ProjectSpace;
import org.eclipse.emf.emfstore.client.model.Usersession;
import org.eclipse.emf.emfstore.client.model.Workspace;
import org.eclipse.emf.emfstore.client.model.WorkspaceManager;
import org.eclipse.emf.emfstore.client.model.controller.callbacks.UpdateCallback;
import org.eclipse.emf.emfstore.client.model.exceptions.ChangeConflictException;
import org.eclipse.emf.emfstore.client.model.observers.ConflictResolver;
import org.eclipse.emf.emfstore.client.model.util.EMFStoreClientUtil;
import org.eclipse.emf.emfstore.common.model.ModelElementId;
import org.eclipse.emf.emfstore.common.model.Project;
import org.eclipse.emf.emfstore.common.model.util.ModelUtil;
import org.eclipse.emf.emfstore.server.exceptions.AccessControlException;
import org.eclipse.emf.emfstore.server.exceptions.BaseVersionOutdatedException;
import org.eclipse.emf.emfstore.server.exceptions.EmfStoreException;
import org.eclipse.emf.emfstore.server.model.ProjectInfo;
import org.eclipse.emf.emfstore.server.model.versioning.ChangePackage;
import org.eclipse.emf.emfstore.server.model.versioning.LogMessage;
import org.eclipse.emf.emfstore.server.model.versioning.PrimaryVersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.VersionSpec;
import org.eclipse.emf.emfstore.server.model.versioning.VersioningFactory;
import org.eclipse.emf.emfstore.server.model.versioning.operations.AbstractOperation;
import org.eclipse.emf.emfstore.server.startup.ConsoleProgressMonitor;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;

public class Application implements IApplication {
	private ProjectSpace project1;
	private League league1;
	private ProjectSpace project2;
	private League league2;
	private LogMessage logMessage;

	public Object start(IApplicationContext context) throws Exception {
		WorkspaceManager.init();

		// Run a client that shows the merging feature of the EMFstore
		// Please note: this is the programmatic way of merging
		// EMFStore also provides a default UI for merging
		// If there is a problem with the connection to the server
		// e.g. a network, a specific EMFStoreException will be thrown
		try {
			runClient();
		} catch (EmfStoreException e) {
			System.out.println("No connection to server.");
			System.out.println("Did you start the server? :-)");
			e.printStackTrace();

		}

		return IApplication.EXIT_OK;
	}

	private void runClient() throws AccessControlException, EmfStoreException {
		System.out.println("Client starting...");

		// Sets up the workspace by cleaning all contents
		// Create a project with some EObjects and two
		// identical copies. See hello world code example
		// for more details
		setupWorkspace();

		// Change the value of the league in project 1 and commit
		// the change
		league1.setName("New Name 1");
		league1.getPlayers().add(createPlayer("Player no. 4"));
		project1.commit(logMessage, null, new ConsoleProgressMonitor());

		// Changing the same value without calling
		// project2.update() will cause a conflict
		// also add one change which is non-conflicting
		league2.setName("New Name 2");
		league2.getPlayers().remove(0);

		try {
			project2.commit(logMessage, null, new ConsoleProgressMonitor());
		} catch (BaseVersionOutdatedException e) {

			System.out.println("\nCommit of project 2 failed.");

			// define a conflict resolver to handle the conflicting changes on
			// update
			// the resolver will accept the name change of the league of project
			// 2 to "New Name 2"
			// reject the name change of project 1 and accept all other changes
			final ConflictResolver conflictResolver = new ConflictResolver() {

				private ChangePackage myChangePackage;
				private List theirChangePackages;

				public boolean resolveConflicts(Project project,
						List theirChangePackages,
						ChangePackage myChangePackage,
						PrimaryVersionSpec baseVersion,
						PrimaryVersionSpec targetVersion) {

					this.theirChangePackages = theirChangePackages;
					this.myChangePackage = myChangePackage;
					// declare that resolver will be able to resolve all
					// conflicts
					return true;
				}

				public List getRejectedTheirs() {
					// reject the first change in the change package of project
					// 1 from the server, since it is the name change of the
					// league to "New Name 1"
					return Arrays.asList(theirChangePackages.get(0)
							.getOperations().get(0));
				}

				public List getAcceptedMine() {
					// accept all my operations in project 2, including the name
					// change to "New Name 2"
					return myChangePackage.getOperations();
				}
			};

			// create a callback object to drive the update and use our conflict
			// resolver
			UpdateCallback updateCallback = new UpdateCallback() {

				public void noChangesOnServer() {
					// do nothing if there are no changes on the server (we know
					// there are changes anyway)
				}

				public boolean inspectChanges(ProjectSpace projectSpace,
						List changes) {
					// accept incoming changes (we could cancel the update by
					// returning false here)
					return true;
				}

				public boolean conflictOccurred(
						ChangeConflictException changeConflictException) {

					// resolve conflicts by merging with our conflict resolver
					try {
						project2.merge(project2
								.resolveVersionSpec(VersionSpec.HEAD_VERSION),
								conflictResolver);
					} catch (EmfStoreException e) {
						// on any exceptions, declare conflicts as non-resolved
						return false;
					}

					// conflicts have been resolved
					return true;
				}
			};

			// run update in project 2 with our callback
			System.out
					.println("\nUpdate of project 2 with conflict resolver...");
			project2.update(VersionSpec.HEAD_VERSION, updateCallback,
					new ConsoleProgressMonitor());

			// commit merge result in project 2
			System.out.println("\nCommit of project 2 with merge result.");
			project2.commit(logMessage, null, new ConsoleProgressMonitor());
		}

		// update project 1
		System.out.println("\nUpdate of project 1 with merge result.");
		project1.update();

		System.out.println("\nLeague name in project 1 is now:"
				+ league1.getName());

		System.out.println("Client run completed.");

	}

	/**
	 * Creates a default workspace and deletes all remote projects.
	 * 
	 * @throws AccessControlException
	 * @throws EmfStoreException
	 */
	private void setupWorkspace() throws AccessControlException,
			EmfStoreException {
		// The workspace is the core controler to access
		// local and remote projects
		Workspace workspace = WorkspaceManager.getInstance()
				.getCurrentWorkspace();

		// A user session stores credentials for login
		// Creates a user session with the default credentials
		Usersession usersession = EMFStoreClientUtil.createUsersession();
		usersession.logIn();

		// Retrieves a list of existing (and accessible) projects
		// on the sever and deletes them permanently (to have a
		// clean set-up)
		List projectList;
		projectList = workspace.getRemoteProjectList(usersession);
		for (ProjectInfo projectInfo : projectList) {
			workspace.deleteRemoteProject(usersession,
					projectInfo.getProjectId(), true);
		}

		// Create a project, share it with the server
		project1 = workspace.createLocalProject("projectNo1", "My project");
		project1.shareProject(usersession, new ConsoleProgressMonitor());

		// Create some EObjects and add them to the project
		// (To the projects containment tree)
		league1 = BowlingFactory.eINSTANCE.createLeague();
		league1.setName("league");
		league1.getPlayers().add(createPlayer("no. 1"));
		league1.getPlayers().add(createPlayer("no. 2"));
		project1.getProject().addModelElement(league1);

		logMessage = VersioningFactory.eINSTANCE.createLogMessage();
		logMessage.setMessage("My Message");
		project1.commit(logMessage, null, new ConsoleProgressMonitor());
		System.out.println("Project 1 committed!");

		// Check-out a second, independent copy of the project
		// (simulating a second client)
		project2 = workspace.checkout(usersession, project1.getProjectInfo());
		league2 = (League) project2.getProject().getModelElements().get(0);
	}

	/**
	 * Handles the conflicting project2.
	 * 
	 * @param conflictingProject2
	 */
	protected void handleConflict(ProjectSpace conflictingProject2) {
		try {
			// inspect all changes
			inspectChanges(conflictingProject2);

			// merge the project2 with the current version
			// and reject all changes from the server!
			// Only changes from project2 are accepted!
			conflictingProject2.merge(
					ModelUtil.clone(project1.getBaseVersion()),
					new ConflictResolver() {

						private ArrayList acceptedMine;
						private ArrayList rejectedTheirs;

						public boolean resolveConflicts(Project project,
								List theirChangePackages,
								ChangePackage myChangePackage,
								PrimaryVersionSpec baseVersion,
								PrimaryVersionSpec targetVersion) {

							// all local projects for project2 are accepted
							acceptedMine = new ArrayList();
							acceptedMine.addAll(myChangePackage.getOperations());

							// reject all operations executed on project1
							rejectedTheirs = new ArrayList();
							for (ChangePackage change : theirChangePackages) {
								rejectedTheirs.addAll(change.getOperations());
							}
							return true;
						}

						public List getRejectedTheirs() {
							return rejectedTheirs;
						}

						public List getAcceptedMine() {
							return acceptedMine;
						}
					});

		} catch (EmfStoreException e) {
			ModelUtil.logException(e);
		}
	}

	/**
	 * Inspects the changes occured.
	 * 
	 * @param conflictingProject
	 * @throws EmfStoreException
	 */
	private void inspectChanges(ProjectSpace conflictingProject)
			throws EmfStoreException {
		// access and list all changes occured
		List changes = conflictingProject.getChanges(
				conflictingProject.getBaseVersion(), project1.getBaseVersion());

		for (ChangePackage change : changes) {
			System.out.println(change.getLogMessage().toString());

			// use the elementId of the change to access the leagues of each
			// local project
			for (ModelElementId elementId : change
					.getAllInvolvedModelElements()) {
				EObject element = project1.getProject().getModelElement(
						elementId);
				if (element == null) {
					element = project2.getProject().getModelElement(elementId);
				}
				switch (element.eClass().getClassifierID()) {
				case BowlingPackage.PLAYER:
					Player playerOfProject1 = (Player) project1.getProject()
							.getModelElement(elementId);
					Player playerOfProject2 = (Player) project2.getProject()
							.getModelElement(elementId);
					if (playerOfProject1 != null) {
						System.out.println(String.format(
								"Player of project1. Name is %s",
								playerOfProject1.getName()));
					}
					if (playerOfProject2 != null) {
						System.out.println(String.format(
								"Player of project2. Name is %s",
								playerOfProject2.getName()));
					}
					break;
				case BowlingPackage.LEAGUE:
					League leagueOfProject1 = (League) project1.getProject()
							.getModelElement(elementId);
					League leagueOfProject2 = (League) project2.getProject()
							.getModelElement(elementId);
					System.out.println(String.format(
							"League of project1. Name is %s",
							leagueOfProject1.getName()));
					System.out.println(String.format(
							"League of project2. Name is %s",
							leagueOfProject2.getName()));
					break;
				}
			}
		}
	}

	/**
	 * Creates a new instance of a player.
	 * 
	 * @param name
	 * @return
	 */
	private Player createPlayer(String name) {
		Player player = BowlingFactory.eINSTANCE.createPlayer();
		player.setName(String.format("Player %s", name));
		player.setEMail(String.format("%s@emfstore.org", name));
		return player;
	}

	public void stop() {
	}
}