Advanced Collaborative Use Cases

Define your own lock strategies

It is possible do define custom Lock Strategies for determining the elements to lock when a modification is made.

Viewpoint provides a default lock strategy based on common sense :

This default lock strategy allows to avoid any conflict. However, you may want to define your own lock strategy, specific to your models and business rule.

Create your own ViewpointCDOLockStrategy

To provide your own lock strategy, you must define an implementation of the ViewpointCDOLockStrategy interface :

Returns the list of elements to lock according to the given modification.

Indicates if the Lock Strategy can be applied considering the given notification

If you want to define a custom lock strategy that will lock all Cars contained in a Garage any time the name of a Car is modified, you can create the following lock strategy :

// We extends the Vewpoint default Lock strategy
public class MyCustomLockStrategy extends DefaultLockStrategy implements ViewpointCDOLockStrategy {

  public boolean isRelevantNotification(Notification notification) {
        boolean isRelevantNotification = false;
        if (notification.getNotifier() instanceof EObject) {
            Option<CDOObject> notifier = CDOViewpointUtil.getCDOObject((EObject) notification.getNotifier());
            if (notifier.some()) {
            	// We only want to consider EObjects that are related
            	// to a certain Package (here my 'CarsPackage').
               isRelevantNotification = CarsPackage.eINSTANCE.equals(notifier.get().eClass().getEPackage());
            }
        }
        return isRelevantNotification;
    }
    
    
	public Collection<? extends CDOObject> getElementsToLock(Notification notification) {
		// Here we already know that the notifier is related
		// to the CarsPackage 
		 Collection<CDOObject> elementsToLock = Sets.newHashSet();
		 Option<CDOObject> notifier = CDOViewpointUtil.getCDOObject((EObject)notification.getNotifier());
		 // If we just modified the name of a car
		 boolean isConcerningACar = notification.getNotifier() instanceof Car;
		 boolean isConcerningTheNameFeature = (notification.getEventType() == Notification.SET) && (notification.getFeatureID() == CarsPackage.CAR__NAME);
		 if (isConcerningACar && isConcerningTheNameFeature){
		 	// then we lock all the cars of contained in this car's garage.
		 	elementsToLock.addAll(((Car)notification.getNotifier()).getGarage().getCars());
		 } else {
		 	// Otherwise, we delegate the calculation of elements to lock
		 	// to the Viewpoint default lock strategy
		 	super.getElementsToLock(notification);
		 }	
	}
}

Registering a custom Lock Strategy inside Viewpoint

Once you have defined a new custom lock strategy, you will have to register it through the fr.obeo.dsl.viewpoint.collab.lockstrategy extension point.

In our example, we want the lock strategy we defined to be applied instead of all other lock strategies when isRelevantNotification() returns true :

<extension point="fr.obeo.dsl.viewpoint.collab.lockstrategy">
	<lockStrategy	overrideDefaultStrategy="WHEN_CAN_APPLY"
             	strategy="com.my.plugin.lock.MyCustomLockStrategy"
             strategyID="com.my.plugin.lock.customLockStrategy">
   </lockStrategy>
</extension>

Customize Authentication

Viewpoint Collaborative API allows you to customize authentication

To do so, you have to implement your own fr.obeo.dsl.viewpoint.collab.api.CDOAuthenticationManager and register it through the CDOAuthenticationManagerRegistry :

// Registering my authentication manager for the CDO Repository located at some IP Adress
CDOAuthenticationManagerRegistry.registerAuthenticationManager("191.XXX.YY.ZZZ", new CustomAuthenticationProvider());

// Registering my authentication manager for all CDO Repositories
CDOAuthenticationManagerRegistry.registerAuthenticationManager("*", new CustomAuthenticationProvider());


To implement your own CDOAuthenticationManage, you can check the fr.obeo.dsl.viewpoint.collab.ui.credentials.DefaultCredentialsProvider , that stores the user login and password inside Eclipse’s secture storage, and open dialogs allowing the user to enter these informations.

The Collaborative Test Framework

The fr.dsl.viewpoint.tests.collab.support and fr.dsl.viewpoint.tests.collab plugins provide API for writing collaborative tests.

Proving correctness with a Collaborative Test Case

As we cannot directly test collaborative behavior with 2 Eclipses, each feature must be tested twice :

  1. Remote to Local : ensure that Viewpoint reacts correctly to repository modifications.
  2. Local to Remote : ensure that Viewpoint makes the correct modification on the repository.

For example, if we have to test the lock acquiring mechanism :

  1. Remote to Local : Ensure that when CDOObjects are locked ( Repository)
  1. Local to Remote : Ensure that when modifying a graphical element (or a semantic element) with the Local Viewpoint :

Test framework Architecture

Our collaborative Test Framework :

  1. allows to do everything you used to do in JUnit and SWTBots tests (simulation of the local user behavior)
  2. allows to check properties on the CDO Repository (to write Local To Remote tests)
  3. simulates behavior of a Remote User (to write Remote to Local tests)

You can define your own test cases by extending :

All this abstract test cases actually delegates all the collaborative simulation to the CollaborativeTestCase, that you can also extend. However, the CollaborativeTestCase does not provide API to manipulate local session.

If you have to do assertions concerning the CDO Repository state(like checking that an element has been deleted on the repository), standard Collaborative APIs like the CDORepositoryManager should be used.

Any CollaborativeTestCase is associated to a IRemoteUser, which purpose is to simulate the behavior of an end-user using Viewpoint on a remote computer.
The IRemoteUser interface provides APIs needed for simulating this behavior (lock acquiring, representations creation...).
It uses a different CDOSession than the one used by the local user, so that all the modifications he makes are considered as remote.

Test Servers

Local Server

All Collaborative tests need a CDO Repository set up. We have defined the fr.obeo.dsl.viewpoint.tests.collab.server plugin, which contains all logic needed for launching and disposing a CDO Server and Repository.

Closing and reopening this Test Server between each tests seems quite a burden while not adding any benefit.

That is why the Activator of the test server plugin launches the server when loaded, and set it down when disposed. The Test server is therefore loaded once, and closed when all tests are finished. Notice that you can also simulates network failures by stopping this server.

Remote Server

All the collaborative tests can also be launched on any CDO Repository located on a remote computer.
To do so, add the following VM arguments to your Test Suite :

-Dserver_location=191.XXX.YY.ZZZ@2036@repo1


You can therefore indicate the address of the repository to launch the tests on. If the variable is not defined in the launch config, then the local server will be automatically started.

Test data life cycle

At each setUp of any collaborative test :

Setting up a collaborative test

You can very easily set up a test that will upload a set of models on the repository and create a collaborative session referencing the uploaded resources.

Check the fr.obeo.dsl.viewpoint.tests.collab.utils.AbstractDiagramCollaborativeTest.genericSetUp() methods to get additional informations about setting up a Collaborative TestCase.

RemoteUser API description

The fr.obeo.dsl.viewpoint.tests.collab.support.api.remoteuser.IRemoteUser interface provides APIs allowing to describe easily the behavior of a Remote end-User. It is associated to a CDOSession and CDOTransaction.

The API is divided in two parts :

Key points of the API

In this first part, we will determine the fundamental feature provide by the test API.

CDO facilities : getActiveTransaction() and dispose()

Asynchronous execution of any Remote User action

All actions made by Remote User are executed in a separated Thread, so that remote and local end-users can do action simultaneously, and all loaded CDOResources and CDOObjects does not interfere with the local ones.

RemoteUserActions

The AbstractRemoteUserAction class implements Runnable. It is taking a RemoteUser in parameter.

Any RemoteUserAction have an execute() method, called inside its run() method. The actions made by the remote user will be described inside this method.

When the RemoteUserAction is finished, the method isFinished() returns true (false otherwise). Notice that if an error occurs during the execution of the RemoteUserAction, a RemoteUserException will be thrown.

Executing RemoteUserActions

The IRemoteUser interface allows to execute any RemoteUserAction.

void doAction(boolean shouldWait, RemoteUserAction remoteUserAction)

If shouldWait is true, then the doAction method will wait until the remoteUserAction is finished.

End Users’s datas storage

Let us consider the following example :

public class CreateElementAction extends AbstractRemoteUserAction {

	public CreateElementAction(RemoteUser remoteUser){
		super(remoteUser);
	}
	
	@Override
	protected void execute() {
	
		// Step 1 : Getting the resource in which to create the element
	 	CDOResource r1 = actionRemoteUser.getActiveTransaction().getResource("resourcePath");
	 
	 	// Step 2 : Create the element and add it to the CDOResource
	 	CDOObject myCreatedElement = MyFactory.createElement();
	 	r1.getContents().add(myCreatedElement);	 
	}
}


public class LockingTest extends AbstractDiagramCollaborativeTest() {

	public void testLockRemoteToLocal() {
	
		// The remote user creates a CDOObject
		remoteUser.doAction(true, new CreateElementAction(remoteUser));
		
		// Check 1 : as the remoteUser has committed, the 
		// created element should be visible for localUser
		checkIsVisibleElement(XXX);
		
		// The remote user locks the created CDOObject
		remoteUser.doAction(false, new LockElementAction(XXX));
	}
	


In this example, you can see that the element created in the CreateElementAction has to be accessed by localUser (to check that it is correctly visible) and by remoteUser (to lock it).

As explained previously, we should not directly store the created CDOObject. That is why you can define variables for a Remote User.

Stores the given value with the given key as identifier. If the value has to be accessed in the main thread, then it should not be a CDOObject (store the CDOID instead and use the local transaction to load locally the CDOObject).

Returns the value associated to the given variable name.

Let us go back to the example presented above, assuming that the CreateElementAction is now built with a String corresponding to the name to use for registering the CDOID of the created element just after Step 2 :


	protected void execute() {
		...
	 // Step 3 : registering created element
	 getRemoteUser().setVariable(this.nameOfElementToCreate, myCreatedElement.cdoID());
	}


Now it can be used for loading the created element inside the main thread (local user) :

public void testLockRemoteToLocal() {
		
	// The remote user creates a CDOObject
	remoteUser.doAction(true, new CreateElementAction(remoteUser, "idOfCreatedElement"));
	
	// Loading the created CDOObject
	CDOObject created = activeTransaction.getCDOObject((CDOID) remoteUser.getVariable("idOfCreatedElement"), true);

	// Making any check on this object
	assertTrue(CDOLockManager.isLockedByOthers(created));
	
	// Using the created element variable to launch the lock operation
	remoteUser.doAction(false, new LockElementAction("idOfCreatedElement"));
 


The lock action is now defined with a String corresponding to the name of the variable representing the element to lock.

We obviously want to wait for the end of the CreateElementAction before accessing to the created element (hence we call doAction(true,...)). If the LockElementAction has no direct effect on Local side, we can continue before it is ended(hence we call doAction(false,...)).

Providing facilities for Remote Checks

You will often need to check that the Repository State from the Remote User viewpoint is conform to expected (Local To Remote Tests).

That is why we defined the AbstractRemoteCheck class. Always launched synchronously and returning a boolean, it makes Remote Checks easier to write :

	remoteUser.assertRemote("Message if the test fails", new AbstractRemoteCheck(remoteUser) {
	@Override
    protected boolean checkCondition() {
    	CDOObject elementToCheck = (CDOObject) remoteUser.getVariable("myElementToCheck");
    	return "expectedResourcePath".equals(elementToCheck.cdoResource().getPath());
    }
	}
 

Shortcuts

In order to make Collaborative test writing as simple as possible, we provided a set of utility methods. This following list is not complete, we invite you to study the IRemoteUser interface for additional behavior :