Stardust provides you with the option to implement your own repository connection extension points. This chapter covers the following topics:
For each connection you have to implement at least two extensions: a handler for the repository connection and one (ore more) property page(s) for the connection.
The handler is defined as following:
<extension point="org.eclipse.stardust.modeling.repository.common.connections">
<connection
handler="<implementation class name>"
icon="<the icon of the connection type>"
id="<unique identifier>"
name="< connection type name>"/>
</extension>
The handler attribute represents the fully qualified class name of an
org.eclipse.stardust.modeling.repository.common.ConnectionHandler interface implementation, for example
org.eclipse.stardust.modeling.repository.file.FileConnectionHandler.
The implementation class must have a public default constructor to allow instantiation in the OSGI bundle context.
The icon attribute describe a path to a plug-in resource of an actual image file, for example
icons/draw_transition.gif.
The id attribute is the unique identifier of the connection type. To ensure uniqueness, a good practice
is to qualify the id with the plug-in id. An example would be
org.eclipse.stardust.modeling.repository.file.connection.
The name attribute is the human readable name of the connection type. It is used in all the labels presented to the end user.
The property page is defined as below:
<extension point="org.eclipse.ui.propertyPages">
<page
class="<page class name>"
id="<unique id>"
name="<property page name>"
objectClass="org.eclipse.stardust.modeling.repository.common.Connection">
<filter
name="feature:type"
value="<id of the connection type>"/>
</page>
</extension>
The class attribute represents the fully qualified class name of a standard
org.eclipse.ui.IWorkbenchPropertyPage interface implementation. The implementation class
must have a public default constructor to allow instantiation in the OSGI bundle context.
The id attribute is the unique identifier of the property page. To ensure uniqueness, a good practice
is to qualify the id with the plug-in id. An example would be
org.eclipse.stardust.modeling.repository.file.fileConnection.
The name attribute is the label in the property pages tree and is used also as the title of the property page itself.
The objectClass attribute represents a fully qualified class or interface name that the element to
which the property page will be displayed must extend or implement. It must be
org.eclipse.stardust.modeling.repository.common.Connection or a subclass of it.
The filter tag is optional, but if it is missing, the property page will be displayed for all repository
connection types. To ensure that the property page is displayed only for a specific connection type, a
filter must be provided with the name feature:type and the value containing the id of the
connection type.
An example plugin.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="4.3"?>
<plugin>
<extension point="org.eclipse.stardust.modeling.repository.common.connections">
<connection
handler="org.eclipse.stardust.test.TestConnectionHandler"
icon="icons/draw_transition.gif"
id="org.eclipse.stardust.test"
name="Test Connection"/>
</extension>
<extension point="org.eclipse.ui.propertyPages">
<page
class="org.eclipse.stardust.test.TestConnectionPropertyPage"
id="org.eclipse.stardust.test.testConnection"
name="Test Connection Property Page"
objectClass="org.eclipse.stardust.modeling.repository.common.Connection">
<filter
name="feature:type"
value="org.eclipse.stardust.test"/>
</page>
</extension>
</plugin>
The property page must implement the org.eclipse.ui.IWorkbenchPropertyPage interface.
The scope of the property page is to allow the user to specify the set of attributes needed to open
the connection. When implementing the page, a good choice is to extend org.eclipse.ui.dialogs.PropertyPage
class.
The user must implement at least the Control createContents(Composite parent) method. The page
can get and set attributes to the corresponding Connection object. The Connection attached to the page can
be obtained via (Connection) getElement().getAdapter(EObject.class).
Once you have the Connection object, you can get or set the id, name, or type (although it is not recommended to change the type of the connection). Every connection has two sets of properties: persistent attributes that will be stored in the model file and transient properties that will be available in the context of the current eclipse session.
You can get or set the persistent attributes using List Connection.getAttributes().
For transient properties, getProperty, setProperty and removeProperty
methods are provided.
The actual connection handler must implement the following methods:
This method is responsible for actually opening a connection resource to the external repository. Failing of opening the connection must be signaled by throwing a CoreException. For opening the connection, the implementer may use both the persistent attributes and the transient properties. If necessary he can ask the user for further input via dialogs. No queries will be performed on the connection by the Stardust modeler if the connection is not successfully opened.
This method is responsible for closing a connection resource to the external repository and releasing any resources acquired since opening.
The method returns a list of IObjectDescriptors that will be used to generate the outline tree. The filters (if present) may be used to restrict the elements to be returned.
This method is responsible for creating and adding the model elements corresponding to the array of descriptors to the provided model. The parameter asLink specifies if the objects will be generated as links or will be fully imported into the model. It is possible and allowed that for a specific object descriptor more than one model element is added to the model.
The following shows an example implementation:
public abstract void createData(boolean asLink);
public void importObject(ModelType model, IObjectDescriptor[] descriptors,
boolean asLink)
{
for (int i = 0; i < descriptors.length; i++)
{
IObjectDescriptor descriptor = descriptors[i];
if ("process".equals(descriptor.getURI().segment(0)))
{
createData(asLink);
DataTypeType primitiveType = (DataTypeType)
ModelUtils.findIdentifiableElement(
model.getDataType(), "primitive");
data.setType(primitiveType);
model.getProcessDefinition().add(process);
model.getRole().add(role);
model.getData().add(data);
}
}
}
This method is responsible for replacing proxy objects that are created during model load with actual linked elements. This method is invoked only once per each proxy object at the end of a successful model load. The returned value is the resolved object or null if the proxy was not resolved. This method is supposed to perform all necessary changes in the model.
A typical implementation is provided below:
public abstract EObject findObjectByUri(URI uri);
public Object resolve(ModelType model, Object object)
{
URI uri = ((EObjectImpl) object).eProxyURI();
EObject element = findObjectByUri(uri);
if (element != null)
{
EcoreUtil.replace(eObject, element);
for (EContentsEList.FeatureIterator featureIterator =
(EContentsEList.FeatureIterator) eObject.eCrossReferences()
.iterator();featureIterator.hasNext(); )
{
EObject target = (EObject)featureIterator.next();
EReference eReference = (EReference)featureIterator.feature();
unset(eObject, target, eReference);
set(element, target, eReference);
}
}
return element;
}
private void set(EObject object, EObject target, EReference reference)
{
if (reference.isMany())
{
List list = (List) object.eGet(reference);
list.add(target);
}
else
{
object.eSet(reference, target);
}
}
private void unset(EObject object, EObject target, EReference reference)
{
if (reference.isMany())
{
List list = (List) object.eGet(reference);
list.remove(target);
}
else if (reference.isUnsettable())
{
object.eUnset(reference);
}
else
{
object.eSet(reference, null);
}
}
There are situations where the developer may want to give the user the possibility to interact with the outline nodes. This is possible via two extension points:
org.eclipse.ui.propertyPages: allows the developer to provide property pages for the
outline nodes.org.eclipse.stardust.modeling.core.contextMenuAction: allows the developer to provide
context menu actions.The property pages must be implemented in the exact way as described for the connections, with the only change that the objectClass attribute to match the class of the implementation node and the filter removed or adapted to the specific class.
Context menu actions are specified in the plugin.xml file as follows:
<extension point="org.eclipse.stardust.modeling.core.contextMenuAction">
<contextMenuAction
actionClass="<class name of the action>"
group="rest"
id="<unique identifier>"
targetEObjectType="<class name or superclass/interface of the node>"/>
</extension>
The action class must implement IAction interface, but it is recommended to implement ISpiAction
interface too to get access to the actual selected element, configuration element of the action
(the data described in the plugin.xml for that action) and the WorkflowModelEditor that have invoked the
action.
If the result of the action has generated a change in the node data (either label/icon or children) the developer must ensure that a corresponding notification is sent by the node to all adapters so that the change will be reflected in the outline tree.
Example action:
public class GetCategoryDetailsAction extends Action implements ISpiAction
{
private IStructuredSelection selection;
public boolean isEnabled()
{
return !selection.isEmpty() && isMyOwnCategory();
}
private boolean isMyOwnCategory ()
{
Object selection = this.selection.getFirstElement();
if (selection instanceof IAdaptable)
{
Object model = ((IAdaptable) selection).getAdapter(EObject.class);
if (model instanceof CategoryDescriptor)
{
return !((CategoryDescriptor) model).isFetched();
}
}
return false;
}
public void run()
{
Object selection = this.selection.getFirstElement();
CategoryDescriptor descriptor = (CategoryDescriptor)
((IAdaptable) selection).getAdapter(EObject.class);
descriptor.fetchChildren();
}
public void setConfiguration(IConfigurationElement config,
WorkflowModelEditor editor, IStructuredSelection selection)
{
setId(config.getAttribute(SpiConstants.ID));
setText("Get Details");
this.selection = selection;
}
}
The CategoryDescriptor is the actual implementation class of the node. The fetchChildren()
method is responsible for sending the change notification, as below:
public void fetchChildren()
{
doAction();
if (eNotificationRequired())
{
if (labelOrIconChanged())
{
eNotify(new ENotificationImpl(this, Notification.SET, null, null, null));
}
if (childrenChanged())
{
eNotify(new ENotificationImpl(this, Notification.ADD_MANY, null, null, null));
}
}
}
If the plug-in developer has already the elements that he wants to expose in the form of XPDL documents, he can reuse the Stardust loading functionality to populate the outline tree. The XPDL documents can be retrieved either from the file system, a database or generated on the fly.
On connection open(), the developer should prepare the connection handler to be able to
retrieve the XPDL document and parse it into the relevant model elements. In the example below, we will
read the XPDL document and store the contained model for later usage:
public void open(Connection connection) throws CoreException
{
InputStream is = getInputStreamOfTheXpdlDocument(connection);
if (is != null)
{
connectionUri = ConnectionManager.makeURI(connection);
CarnotWorkflowModelPackageImpl.init();
Resource.Factory factory =
new CarnotWorkflowModelResourceFactoryImpl();
Resource resource = factory.createResource(
URI.createFileURI("dummy.xpdl"));
Map options = new HashMap();
options.put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, Boolean.TRUE);
try
{
resource.load(is, options);
}
catch (IOException e)
{
throw new CoreException(new Status(IStatus.ERROR,
ObjectRepositoryActivator.PLUGIN_ID, 0,
"Error parsing input stream.", null));
}
finally
{
try
{
is.close();
}
catch (IOException e)
{
}
}
EList l = resource.getContents();
Iterator i = l.iterator();
while (i.hasNext())
{
Object o = i.next();
if (o instanceof DocumentRoot)
{
model = ((DocumentRoot) o).getModel();
break;
}
}
}
else
{
throw new CoreException(new Status(IStatus.ERROR,
ObjectRepositoryActivator.PLUGIN_ID, 0,
"Cannot access resource.", null));
}
}
After successful execution of open()
we will have two values stored in the handler fields:
The outline nodes are retrieved via the select method. In this example we will ignore the filters and
provide nodes for participants and applications found in the model. We will create two categories, one for
the participants and one for the applications, and for each category we will have to add the
IObjectDescriptors corresponding to each model element added.
In order to keep the example simple, we will reuse another Stardust class - EObjectDescriptor - that
works as an adapter between IObjectDescriptors and EObjects.
public List select(IFilter[] filters) throws CoreException
{
ArrayList participants = new ArrayList();
addElements(participants, "role", model.getRole());
addElements(participants, "organization", model.getOrganization());
ArrayList applications = new ArrayList();
addElements(applications, "application", model.getApplication());
ArrayList result = new ArrayList();
result.add(createCategory("participants", "Participants",
(IObjectDescriptor[]) participants.toArray(
new IObjectDescriptor[participants.size()])));
result.add(createCategory("applications", "Applications",
(IObjectDescriptor[]) applications.toArray(
new IObjectDescriptor[applications.size()])));
return Collections.unmodifiableList(result);
}
private IObjectDescriptor createCategory(String id, String name,
final IObjectDescriptor[] children)
{
return new EObjectDescriptor(connectionUri.appendSegment(id),
null, id, name, null, null, null)
{
public IObjectDescriptor[] getChildren()
{
return children;
}
public boolean hasChildren()
{
return children.length > 0;
}
};
}
private void addElements(List descriptors, String type, List elements)
{
for (int i = 0; i < elements.size(); i++)
{
IIdentifiableModelElement identifiable =
(IIdentifiableModelElement) elements.get(i);
URI elementUri = connectionUri.appendSegment(type)
.appendSegment(identifiable.getId());
EObjectDescriptor descriptor = new EObjectDescriptor(elementUri,
// classifier
((EObject) identifiable).eClass(),
// id, name, description
identifiable.getId(),
identifiable.getName(),
ModelUtils.getDescriptionText(identifiable.getDescription()),
// Icon
null,
null);
descriptors.add(descriptor);
}
}
For importing, we create a command for each imported model element. First we search the actual model elements that we want to link. For each model element we must set the proxy URI so that Stardust modeler and the EMF framework will know that the new model element is a linked element. After that we add the element to the parent using the SetValueCmd that has as arguments the parent, the EMF containing feature of the element to add and the last argument is the element we want to add.
public void importObject(ModelType origmodel, IObjectDescriptor[]
descriptors, boolean asLink)
{
for (int i = 0; i < descriptors.length; i++)
{
IObjectDescriptor descriptor = descriptors[i];
URI uri = descriptor.getURI();
IIdentifiableModelElement identifiable = findIdentifiableByUri(model,
Uri, asLink);
if (identifiable != null)
{
if (identifiable != null)
{
new SetValueCmd(origmodel,
identifiable.eContainingFeature(), identifiable).execute();
}
}
}
}
public IIdentifiableModelElement findIdentifiableByUri(ModelType model,
URI uri, boolean asLink)
{
String type = uri.segment(0);
String id = uri.segment(1);
List searchList = null;
if ("role".equals(type))
{
searchList = model.getRole();
}
else if ("organization".equals(type))
{
searchList = model.getOrganization();
}
else if ("application".equals(type))
{
searchList = model.getApplication();
}
if (searchList != null)
{
IIdentifiableModelElement identifiable = (IIdentifiableModelElement)
ModelUtils.findIdentifiableElement(searchList, id);
if (asLink)
{
// the proxy uri is needed for resolving
((EObjectImpl) identifiable).eSetProxyURI(uri);
}
else
{
AttributeUtil.setAttribute(identifiable,
ConnectionManager.URI_ATTRIBUTE_NAME,
uri);
}
return identifiable;
}
return null;
}
There are cases when the plug-in developers do not have XPDL documents but some other proprietary solution management that cannot be easily converted to Stardust formats. In this case, the developer may want to create "on the fly" the model elements that he wants to link based on the information that he has.
Creating Stardust model elements is done always via the Stardust factory. The factory is a singleton
and has methods to create each of the Stardust model elements (createXXXType()). For example, if
you want to create a Role and an Application, you can use:
CarnotWorkflowModelFactory factory = CarnotWorkflowModelFactory.eINSTANCE; RoleType role = factory.createRoleType(); ApplicationType application = factory.createApplicationType();
The full set of Stardust model elements is comprised of the following classes:
Each of this classes follows the java bean pattern, so that for each property there are setters and
getters with the exception of aggregate structures that have only a getter that retrieves a java.util.List
where the developer can directly add or remove elements.
We will describe below how to create a Process Definition containing two activities, a transition, one role and a diagram. This process definition can be presented, linked and resolved as described in the previous sections.
For the beginning, we will create the process and both activities:
CarnotWorkflowModelFactory factory = CarnotWorkflowModelFactory.eINSTANCE;
ProcessDefinitionType process = factory.createProcessDefinitionType();
process.setId("sample");
process.setName("Sample Process");
process.setDescription(ModelUtils.createDescription(
"Sample process created programmatically"));
((EObjectImpl) process).eSetProxyURI(
connectionUri.appendSegment("process"));
ActivityType route = factory.createActivityType();
route.setId("route");
route.setName("Route Activity");
route.setImplementation(ActivityImplementationType.ROUTE_LITERAL);
process.getActivity().add(route);
ActivityType manual = factory.createActivityType();
manual.setId("manual");
manual.setName("Manual Activity");
manual.setImplementation(ActivityImplementationType.MANUAL_LITERAL);
process.getActivity().add(manual);
The manual activity, needs a performer, so we will create a new role and set it as the activity performer.
RoleType role = factory.createRoleType();
role.setId("role");
role.setName("Role ");
((EObjectImpl) role).eSetProxyURI(connectionUri.appendSegment("role"));
manual.setPerformer(role);
Next, we will create a Data object and a data mapping to the manual activity. The Data object will have
the Stardust meta type primitive. Since the meta type should be normally retrieved from the model,
we will not set yet the type of the data, and will defer that to the time of linking or resolving. As a
primitive data, we must set two dynamic attributes that defines the specifics of the data object:
DataType data = factory.createDataType();
data.setId("primitive_string");
data.setName("Primitive String Data");
AttributeUtil.setAttribute(data, "carnot:engine:type",
"org.eclipse.stardust.engine.core.pojo.Type", "String");
AttributeUtil.setAttribute(data, "carnot:engine:defaultValue", "Hello!");
((EObjectImpl) data).eSetProxyURI(connectionUri.appendSegment("data"));
DataMappingType mapping = factory.createDataMappingType();
mapping.setId("in_mapping");
mapping.setDirection(DirectionType.IN_LITERAL);
mapping.setContext("default");
mapping.setData(data);
manual.getDataMapping().add(mapping);
Now we create a transition between the route and manual activities:
TransitionType transition = factory.createTransitionType();
transition.setId("transition");
transition.setName("Transition");
transition.setCondition("TRUE");
transition.setFrom(route);
transition.setTo(manual);
process.getTransition().add(transition);
This process can be used already as it is, but we want to add a diagram too:
DiagramType diagram = factory.createDiagramType();
diagram.setName("Diagram");
diagram.setOrientation(OrientationType.HORIZONTAL_LITERAL);
process.getDiagram().add(diagram);
ActivitySymbolType routeSymbol = factory.createActivitySymbolType();
routeSymbol.setActivity(route);
routeSymbol.setXPos(300);
routeSymbol.setYPos(200);
diagram.getActivitySymbol().add(routeSymbol);
ActivitySymbolType manualSymbol = factory.createActivitySymbolType();
manualSymbol.setActivity(manual);
manualSymbol.setXPos(100);
manualSymbol.setYPos(400);
diagram.getActivitySymbol().add(manualSymbol);
RoleSymbolType roleSymbol = factory.createRoleSymbolType();
roleSymbol.setRole(role);
roleSymbol.setXPos(450);
roleSymbol.setYPos(300);
diagram.getRoleSymbol().add(roleSymbol);
DataSymbolType dataSymbol = factory.createDataSymbolType();
dataSymbol.setData(data);
dataSymbol.setXPos(400);
dataSymbol.setYPos(50);
diagram.getDataSymbol().add(dataSymbol);
TransitionConnectionType transitionConnection = factory.createTransitionConnectionType();
transitionConnection.setTransition(transition);
transitionConnection.setSourceAnchor("center");
transitionConnection.setTargetAnchor("center");
transitionConnection.setSourceActivitySymbol(routeSymbol);
transitionConnection.setTargetActivitySymbol(manualSymbol);
diagram.getTransitionConnection().add(transitionConnection);
PerformsConnectionType performsConnection = factory.createPerformsConnectionType();
performsConnection.setActivitySymbol(manualSymbol);
performsConnection.setParticipantSymbol(roleSymbol);
diagram.getPerformsConnection().add(performsConnection);
DataMappingConnectionType mappingConnection = factory.createDataMappingConnectionType();
mappingConnection.setActivitySymbol(manualSymbol);
mappingConnection.setDataSymbol(dataSymbol);
diagram.getDataMappingConnection().add(mappingConnection);
For our example the presentation will deliver a single element, the process definition. For that, the select method looks fairly simple:
public List select(IFilter[] filters) throws CoreException
{
EObjectDescriptor descriptor = new EObjectDescriptor(
connectionUri.appendSegment("process"), CarnotWorkflowModelPackage.eINSTANCE
.getProcessDefinitionType(),
"sample", "Sample Process", null, null, null);
return Collections.singletonList(descriptor);
}
In contrast, the import operation is a composite, because we need to add not only the process, but the role and the data too, then again we must set the proper type of the data.
public void importObject(ModelType model, IObjectDescriptor[]
descriptors, boolean asLink)
{
for (int i = 0; i < descriptors.length; i++)
{
IObjectDescriptor descriptor = descriptors[i];
if ("process".equals(descriptor.getURI().segment(0)))
{
updateOids(model);
DataTypeType primitiveType = (DataTypeType)
ModelUtils.findIdentifiableElement(model.getDataType(),
"primitive");
model.getProcessDefinition().add(process);
model.getRole().add(role);
model.getData().add(data);
data.setType(primitiveType);
}
}
}
private void updateOids(ModelType model)
{
long oid = ModelUtils.getElementOid(process, model);
process.setElementOid(oid++);
route.setElementOid(oid++);
manual.setElementOid(oid++);
role.setElementOid(oid++);
data.setElementOid(oid++);
mapping.setElementOid(oid++);
transition.setElementOid(oid++);
diagram.setElementOid(oid++);
routeSymbol.setElementOid(oid++);
manualSymbol.setElementOid(oid++);
roleSymbol.setElementOid(oid++);
dataSymbol.setElementOid(oid++);
transitionConnection.setElementOid(oid++);
performsConnection.setElementOid(oid++);
mappingConnection.setElementOid(oid++);
}