It is possible do define custom Lock Strategies for determining the elements to lock when a modification is made.
Sirius 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. Note that it is your responsibility to ensure that your strategy can not lead to conflicts or that it does whatever is needed to resolve them. In any case, using a lock strategy that locks less elements than the default one is out of scope and the behavior in such a case is undefined. Implement it at your own risks. See
org.eclipse.emf.cdo.transaction.CDOTransaction.Options.setConflictResolvers()
to use your own
CDOConflictResolver
to have conflicts resolved, by default
CDOMergingConflictResolver
is used.
SiriusCDOLockStrategy
To provide your own lock strategy, you must define an implementation of the
SiriusCDOLockStrategy
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 SiriusCDOLockStrategy {
public boolean isRelevantNotification(Notification notification) {
boolean isRelevantNotification = false;
if (notification.getNotifier() instanceof EObject) {
Option<CDOObject> notifier = CDOSiriusUtil.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 = CDOSiriusUtil.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 Sirius 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>
Sirius 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 Sirius 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.
As the collaborative framework handles connections to the CDO server using the TCP protocol or the SSL protocol, collaborative tests can run using one of these protocols. By default, the TCP protocol will be used. To launch tests using the SSL protocol, the following arguments need to be added to the launch configuration:
-DconnectionType=SSL
-Dorg.eclipse.net4j.tcp.ssl.passphrase=secret
-Dorg.eclipse.net4j.tcp.ssl.key=file:///${PATH_TO_SERVER_CERTIFICATE}/server.ks
-Dorg.eclipse.net4j.tcp.ssl.trust=file:///${PATH_TO_Client_CERTIFICATE}/trusted.ks
To run a test with a SSL connection, the server and the client require each a certificate. The certificates used in the Sirius Test suite are available in the SSL folder of the project fr.obeo.dsl.viewpoint.tests.collab.support. The server and client certificates are respectively named server.ks and trusted.ks.
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
A new Representation Lazy Loading mode allowing lazy loading of representations is available in Sirius Collaborative Mode, for remote project representations. It is not activated by default but this might be done in your product (check its own documentation or release notes).
This mode is currently experimental in Sirius for local project. For more details about the Sirius part, please see Representations Lazy Loading (Experimental) .
This mode means two things:
DRepresentationDescriptor
that contains the minimum of information (Representation name, Representation Description, Representation path to load it). From a developer point of view, the representation will be loaded as soon as
DRepresentationDescriptor.getRepresentation()
is called or is retrieved from its CDOID.
It is recommended to get the representation by using the
DRepresentationDescriptor.getRepresentation()
method.
Please see the
Cross Referencer
section for more details about this point.
Existing shared projects will not be automatically migrated toward one resource per representation. Only new created representations will be stored in their own resource. To migrate existing projects, you have to import them locally and export them again to the server (after activation of the mode). See the next section
Import / Export of Modeling Project
for more details.
Note: Even though passing from one mode to the other requires to clean the database, the application will work properly with a mix of split or not split representations.
The
CDOImporter
and the
CDOExporter
update the target representations locations according to the active location rule. When the
Representation Lazy Loading mode is activated, the default rule for remote representations is one representation per resource. This behavior can be deactivated by changing the
CDOSiriusPreferenceKeys.PREF_CREATE_SHARED_REP_IN_SEPARATE_RESOURCE
value. See the
Activating the Representation Lazy Loading
section for more details.
As a result, when exporting a project from local to remote, the CDOImporter will automatically separate representations in different resources. When importing a project from remote to local, the representations will be relocated in the aird resource. This is the default behavior if the split of representations is deactivated for local projects.
Applications that will leverage the
Representation Lazy Loading mode are applications that have been improved to load representations only if needed.
The mode is disabled by default.
To activate it, you need to set
CDOSiriusPreferenceKeys.PREF_CREATE_SHARED_REP_IN_SEPARATE_RESOURCE
to true. The preference default value can also be set by a product or by the users using the plugin customization mechanism and the following customization:
fr.obeo.dsl.viewpoint.collab/PREF_CREATE_SHARED_REP_IN_SEPARATE_RESOURCE=true
.
For testing purpose, you may force the behavior (and then ignore the preference value) with the system property
forceCreateSharedRepresentationInSeparateResource
.
That preference will, in fact, enable the default remote representation location rule provided by the Sirius Collaborative Mode. This rule uses the
fr.obeo.dsl.viewpoint.collab.api.preferences.CDOSiriusPreferenceKeys.PREF_CREATE_SHARED_REP_IN_SEPARATE_RESOURCE
preference to split or not representations in separate resources. By setting the preference value to false, remote representations will be held in the same aird resource than the
DRepresentationDescriptor
, as before.
This preference allows to activate this new behavior without implementing any specific location rule. However, it is possible to implement your own location rule. See the next section
Override the Shared Representation location rule
for more details.
As described in
Representations Lazy Loading
, the default representation location rule can be overridden by implementing the interface
org.eclipse.sirius.business.api.session.danalysis.DRepresentationLocationRule
and declaring it in the
org.eclipse.sirius.dRepresentationLocationRule
extension point.
If this rule is specific to shared representations, you have to make sure that the current representation
DView
is held in a
CDOResource
:
@Override
public boolean providesURI(DRepresentation representation, Resource dViewResource) {
return CDOProtocolConstants.PROTOCOL_NAME.equals(dViewResource.getURI().scheme());
}
The Cross Referencer is installed only on loaded resources. That means if you try to retrieve inverse references toward an element of a representation which is not yet loaded, the Inverse Cross Referencer will not return them.
In addition, it is very
important to keep in mind that the CrossReferencer will not be installed on representations loaded without using
DRepresentationDescriptor.getRepresentation()
method. Indeed, this method will automatically install the Sirius Session CrossReferencer on the representation resource if it has not been installed before. Retrieving the representation by using
CDOView.getObject(CDOID)
will load the representation without installing the CrossReferencer. That will probably cause instability, mainly because of the
DRepresentationDescriptor.representation()
inverse reference that will be missing. This inverse reference is used in several places by Sirius to retrieve the
DRepresentationDescriptor
of a given
DRepresentation
.