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.
ViewpointCDOLockStrategy
To provide your own lock strategy, you must define an implementation of the
ViewpointCDOLockStrategy
interface :
Collection<? extends CDOObject> getElementsToLock(Notification notification)
Returns the list of elements to lock according to the given modification.
boolean isRelevantNotification(Notification notification)
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);
}
}
}
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.
ALWAYS
: no other lock strategies are considered ;
WHEN_CAN_APPLY
: when the contributed lock strategy can be applied (i.e. if
isRelevantNotification()
returns
true
), no other lock strategies are considered. Otherwise, default lock strategies are applied ;
NEVER
: default strategies are applied, and if the contributed lock strategy defines elements to lock that are not defined by the default lock strategies, the elements are also locked
OVERRIDE_EXISTING_STRATEGY
: this lock Strategy will override the Strategy having the given ID
OVERRIDE_EXISTING_STRATEGY
).
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>
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
fr.dsl.viewpoint.tests.collab.support
and
fr.dsl.viewpoint.tests.collab
plugins provide API for writing collaborative tests.
As we cannot directly test collaborative behavior with 2 Eclipses, each feature must be tested twice :
For example, if we have to test the lock acquiring mechanism :
Our collaborative Test Framework :
You can define your own test cases by extending :
fr.obeo.dsl.viewpoint.tests.collab.swtbot.utils.AbstractCollaborativeSWTBotGefTestCase
if you want to write an SWTBot test
fr.obeo.dsl.viewpoint.tests.collab.utils.AbstractDiagramCollaborativeTest
if you wrant to write a JUnit test related to diagrams
fr.obeo.dsl.viewpoint.tests.collab.utils.AbstractTableCollaborativeTest
if you wrant to write a JUnit test related to tables
fr.obeo.dsl.viewpoint.tests.collab.utilsAbstractTreeCollaborativeTest
if you wrant to write a JUnit test related to trees
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.
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.
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.
At each setUp of any 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.
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 :
In this first part, we will determine the fundamental feature provide by the test API.
CDOTransaction getActiveTransaction()
: returns the CDOTransaction to use for manipulating data on the Remote User side
void terminateSession()
: disposes up the RemoteUser by closing the CDOTransaction and the CDOSessions
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.
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.
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.
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.
void setVariable(String key, Object value);
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).
Object getVariable(String key);
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,...)
).
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());
}
}
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 :
void assertIsExpectedLockStatus(boolean shouldBeLockedByMe, boolean shouldBeLockedByOthers, String... elementNames)
: checks that the elements corresponding to the given names (that must have been register through a remoteUser.setVariable(elementName, element) have the expected lock status.
void lockElements(String... elementsToLock)
locks the elements with the given name
void unlockElements(String... elementsToUnlock)
unlocks the elements with the given name
void saveSession()
: simply commits all changes