User Tools

Site Tools


en:models2013

MODELS 2013 - Companion Web Page

This page is a companion web page following the article “SpineFM: A Domain-Driven Approach to Support Multiple Software Product Lines” submitted at the conference MODELS 2013 by Simon Urli, Mireille Blay-Fornarino, Philippe Collet and Sébastien Mosser.

We intend to give in this page more precise information about our approach and our casestudy. Then we provide in the following the complete metamodel used in SpineFM (Fig. 2 and Fig. 6 of the paper), the functions used in the propagation algorithm (presented in Section 3 of the paper) and more information about the generation of inverse rules (mentioned at this end of the Section 2.2 of the paper). Concerning the casestudy, one can find a more explicit descriptions of concepts used in YourCast, the entire domain-model and the feature models used by SpineFM.

On SpineFM

We give in this section more information about our approach, more precisely on the propagation and the generation of inverse rules.

Complete Metamodel

The following figure represents the entire metamodel used in SpineFM in order to design a Multiple Software Product Line (packages FMModel, MSPLModel and ActionModel) and to allow the configuration of the MSPL (packages ConfigurationModel and ProcessModel).

Propagation

The propagation algorithm consists of two main steps. Given a processing configuration and the associated context, it first computes the restrictions to be applied on the neighbor configurations. Doing so implies to navigate in the domain model and to get the restriction functions and rules. But it also uses the contexts in order to retrieve the right configurations to impact. Then the second step consists in applying the propagation on the configurations which have been impacted.

All functions presented below have been implemented in Java and uses classes generated from the EMF metamodel described above.

Class ContextManager

public void propagate(ConfigurationProcessStep CPS, Context context) throws FMEngineException {
	Collection<ConfigurationProcessStep> CPSSet;
	CPSSet = this.immediateRestriction(CPS, context);
	this.recursePropagation(CPSSet, context);
}

The propagate function is the entry point of the propagation. It takes as inputs the context in which user actions that triggered the propagation have been done and the altered ConfigurationProcessStep (CPS): actions made by users to configure a product are directly applied on CPSs. This function realizes two actions: first, it computes actions to be done on neighbor CPSs; then from the list of resulting impacted CPSs, the propagation is launched recursively.

private Collection<ConfigurationProcessStep> immediateRestriction(ConfigurationProcessStep CPSSource, Context ContextSource) throws FMEngineException {
	List<ConfigurationProcessStep> result = new ArrayList<ConfigurationProcessStep>();
	DomainElement deSource = CPSSource.getDomainElement();
	for (DEAssociation asso : deSource.getSourcedAssociation()) {
		DomainElement deTarget = asso.getTarget().getApply_on();
		ConfigurationProcessStep CPSTarget;
		if (deTarget.getMultiplicityElement().isExactlyOne()) {
			CPSTarget = this.globalContext.getCPSOfDE(deTarget);
		} else {
			CPSTarget = ContextSource.getCPSOfDE(deTarget);
		}
		EList<Action> actionsToDo = asso.computeActionsToDo(CPSSource, CPSTarget);
		if (!actionsToDo.isEmpty()) {
			if (CPSTarget.getActionsToDo().isEmpty()) {
				result.add(CPSTarget);
			}
			CPSTarget.getActionsToDo().addAll(actionsToDo);
		}
	}
	return result;
}

The function immediateRestriction defines the way actions on the neighbor CPSs are computed. The function takes as inputs the CPS that is responsible for the propagation and its associated context. To ease reading in the following, we call this context the propagation context. The function loops on associations whom the DE related to the CPS is the source. Then it computes the list of actions to apply on targeted DEs regarding the associations. Finally it builds the list of impacted CPSs. It gets the right CPS regarding the multiplicity of the DE: if a DE is local then the CPS is taken from propagation context; otherwise, the CPS is taken directly from the GlobalContext. The function computeActionsTodo, to get the actions to be done, is detailed in the class DEAssociation.

private void recursePropagation(Collection<ConfigurationProcessStep> CPSSet, Context context) throws FMEngineException {
	for (ConfigurationProcessStep cps : CPSSet) {
		DomainElement deSource = cps.getDomainElement();
		if (deSource.getMultiplicityElement().isExactlyOne() || context.equals(this.globalContext)) {
			this.propagate(cps, this.getGlobalContext());
			if (!deSource.getMultiplicityElement().isExactlyOne()) {
				for (Context c : this.getLocalContexts()) {
					ConfigurationProcessStep cpsLocal = c.getCPSOfDE(deSource);
					c.mergeExternalCPS(cps);
					propagate(cpsLocal, c);
				}
			}
		} else {
			propagate(cps, context);
		}
	}
}

The function recursePropagation performs the recursion during propagation. It takes as inputs the set of CPS which have been impacted by restrictions and the propagation context. Then the function iterates on each CPS. There are two possibilities: either the CPS refers to a local DE and the context given as input is local, then the propagation is just launched with this CPS; or one of the previous condition is not met, i.e., the CPS refers to a global DE or the changing context corresponds to the global context. Then the propagation must be global. However, if a CPS is changed directly in the global context, all CPSs referring the same DE in all local contexts must belong to the same impacts and the propagation must be replayed from these local contexts. The function mergeExternalCPS (presented in Class Context) retrieves the CPSs with the same DE than the input CPS and performs a merge of actions lists.

Class DEAssociation

public EList<Action> computeActionsToDo(ConfigurationProcessStep CPSSource, ConfigurationProcessStep CPSTarget) throws FMEngineException {
	EList<Action> result = new BasicEList<Action>();
	ConfigurationState sourceState;
	try {
		sourceState = CPSSource.getState();
		for (RestrictionFunction rf : this.getRestrictionFunction()) {
			for (Rule r : rf.getRules()) {
				if (r.getState().matchWith(sourceState)) {
					if (!CPSTarget.alreadyHaveAction(r.getActions()))
						result.add(r.getActions());
				}
			}
		}
	} catch (IllegalCallException e) {
		e.printStackTrace();
	}
 
	return result;
}

This function aims at comparing a list of rules with a configuration state and at selecting which actions to apply on a CPS. Then it takes as inputs two CPSs: the source and the target of the impacts. In order to compute the actions to apply, the function starts by getting the ConfigurationState (CS) corresponding to the CPS source. This operation triggers the application of all actions of the CPS on its associated feature model and gets the list of selected and deselected features to build the CS. Then the function iterates on each rule of each restriction functions of the association and checks whether a CS of a rule matches with the CS returned from the CPS: a match means that a rule can be fired. Moreover, as actions on a feature model are idempotent, a check is done before adding an action in the resulting list to avoid redundancy.

Class ConfigurationProcessStep

public boolean alreadyHaveAction(Action a) throws FMEngineException {
	if (this.mapActionsDone.containsKey(a.getFeature())) {
		Action alreadyDone = this.mapActionsDone.get(a.getFeature());
		if (!alreadyDone.equals(a))
                        // if this occurs, the MSPL is not consistent.
			throw new FMEngineException("Contradictory actions on feature "+a.getFeature()+" in CPS "+this.id);
		else
			return true;
	} else
		return false;
}

This function aims to test if an action have already been done in order to avoid redundancy and to ensure the termination of the propagation algorithm. It takes as input an action and verify in the a map if the feature referred by the action has already been involved in another action. If the previous action is different then an exception is raised.

public ConfigurationState getState() throws IllegalCallException {
        // check if the engin to reason on feature models is assigned
	if (this.fma == null) {
		throw new IllegalCallException("FMA must be assigned before calling this method");
	}
	if (!this.getActionsToDo().isEmpty()) {
		try {
			this.apply();
			List<Feature> selectedFeatures = this.fma.getSelectedFeatures(this.getConfName(), this.domainElement.getRefers_on());
			if (!selectedFeatures.isEmpty())
				this.lastState.getSelectedFeatures().addAll(selectedFeatures);
 
			List<Feature> deselectedFeatures = this.fma.getDeselectedFeatures(this.getConfName(), this.domainElement.getRefers_on());
			if (!deselectedFeatures.isEmpty())
				this.lastState.getDeselectedFeatures().addAll(deselectedFeatures);
 
			this.captureImplicitActions();
		} catch (FMEngineException e) {
			e.printStackTrace();
		}
	}
	return this.lastState;
}

This function aims to return the ConfigurationState of the CPS. It's a lazy function: if there is no new actions to do, the last state is returned. Otherwise, actions to do are applied and a new state is computed.

protected boolean apply() throws FMEngineException {
	List<Action> reallyDone = new ArrayList<Action>();
	for (Action a : this.getActionsToDo()) {
		if (!this.alreadyHaveAction(a)) {
			a.applyAction(this.fma, this.getConfName());
			Debug.trace("Apply action {"+a+"} on CPS "+this.id);
			this.mapActionsDone.put(a.getFeature(), a);
			reallyDone.add(a);
		}
	}
	this.getActionsDone().addAll(reallyDone);
	this.getActionsToDo().clear();
	return true;
}

This function describes the way actions are applied on the feature model: it iterates on actions to do, check if the actions have already been done, and if not apply the action.

private void captureImplicitActions() {
	Action a;
	for (Feature f : this.lastState.getSelectedFeatures()) {
		if (!this.mapActionsDone.containsKey(f)) {
			a = ProcessModelFactory.eINSTANCE.createActionSelect();
			this.recordPreviouslyActionDone(a, f);
		}
	}
	for (Feature f : this.lastState.getDeselectedFeatures()) {
		if (!this.mapActionsDone.containsKey(f)) {
			a = ProcessModelFactory.eINSTANCE.createActionDeselect();
			this.recordPreviouslyActionDone(a, f);
		}
	}
}
 
private void recordPreviouslyActionDone(Action a, Feature f) {
	a.setFeature(f);
	a.setFm(this.domainElement.getRefers_on());
	this.mapActionsDone.put(f,a);
	this.getActionsDone().add(a);
	Debug.trace("Already applied action {"+a+"} on CPS "+this.id);
}

These two functions are used to capture implicit actions in a configuration: if some actions have been done in a configuration by applying a cross-tree constraint or a group semantic, it creates the corresponding action in the system in order to avoid contradictory actions and to ensure consistency of the configurations.

Class Context

public void mergeExternalCPS(ConfigurationProcessStep externalCPS) {
	ConfigurationProcessStep sourceCPS = this.getCPSOfDE(externalCPS.getDomainElement());
	for (Action aNew : externalCPS.getActionsDone()) {
		boolean exist = false;
		Action aOld;
		Iterator<Action> it = externalCPS.getActionsDone().iterator();
                // iterate on actions until it found the same one
		while (it.hasNext() && !exist) {
			aOld = it.next();
			if (aOld.equals(aNew)) {
				exist = true;
			}
		}
                // if the action has not been found, it is a new action to apply
		if (!exist) {
			sourceCPS.getActionsToDo().add(aNew);
		}
	}
	sourceCPS.getActionsToDo().addAll(externalCPS.getActionsToDo());
}

The function aims to merge a CPS inside a context with another CPS. It checks the list of actions done in the external CPS and add in the list of actions to do of the internal CPS all actions it has not.

Generation of Inverse Rules

Here is the prolog code, used to prototype the generation of inverse rules.

%%%%
%% Reverse a Rule
%%%%
 
 
%% reverse_rule(+OneRule,-RuleList): reverse one rule returning a set of rules.
%Reverse Conditions
%Second reverse selection part
%Third distribute selection parts
reverse_rule(rule(Selected, Deselected,ActionSet), Rules) :-
	reverse_conditions(Selected, Deselected,ActionList),
        reverse_actions(ActionSet,[],[],Res_Selected,Res_Deselected),
        distributesConditions(Res_Selected,Res_Deselected, ActionList,Rules).
 
 
%% reverse_conditions(+SelectedFeatures,+DeselectedFeatures, -ActionList) : Reverse the conditions of a rule as a set of actions
reverse_conditions( [Selected], [], SelectActionList) :- !,
            reverse_conditions_Selected([Selected],SelectActionList).           
reverse_conditions( [], [Deselected], DeselectActionList) :- !,
            reverse_conditions_Deselected([Deselected],DeselectActionList).
reverse_conditions( Selected, Deselected, [addConstraintOr(Deselected, Selected)] ).
 
reverse_conditions_Selected([S|Selected],[deselect(S)|SelectActionList]) :-
      reverse_conditions_Selected(Selected,SelectActionList).
reverse_conditions_Selected([],[]).
reverse_conditions_Deselected([S|Selected],[select(S)|SelectActionList]) :-
      reverse_conditions_Deselected(Selected,SelectActionList).
reverse_conditions_Deselected([],[]).
 
 
%% Warning : we don't reverse "addConstraintOr" action; this action can be used by end-user 
%% reverse_actions(+ActionList,+SelectedFeatures, +DeselectedFeatures, -FeaturesToSelect, -FeaturesToDeselected) : Reverse the actions of a rule as a condition
reverse_actions([],Selected,Deselected,Selected,Deselected).
reverse_actions([select(B)|ActionList],Selected,Deselected,Res_Selected,Res_Deselected) :-
    reverse_actions(ActionList,Selected,[B|Deselected],Res_Selected,Res_Deselected).
reverse_actions([deselect(B)|ActionList],Selected,Deselected,Res_Selected,Res_Deselected) :-
    reverse_actions(ActionList,[B|Selected],Deselected,Res_Selected,Res_Deselected).
 
 
%% distributesConditions(+SelectedFeatures, +DeselectedFeatures,+ActionList, -RuleList) : distribute the conditions to create a set of rules
distributesConditions([S|Res_Selected],Res_Deselected, ActionList,[rule([S],[],ActionList)|Rules]) :-
            distributesConditions(Res_Selected,Res_Deselected, ActionList,Rules).
distributesConditions([],[D|Res_Deselected], ActionList,[rule([],[D],ActionList)|Rules]) :-
            distributesConditions([],Res_Deselected, ActionList,Rules).
distributesConditions([],[],_,[]).

On the Case Study

Concepts and Domain Model

We use in YourCast several concepts. We give here a quick description of each of them:

  • Source: a source of information is a service called by the digital signage to get some information. The sources can be as diverse as Picasa, FlickR, Twitter, Calendars, etc.
  • Renderer: a renderer is a rendering mechanism to display the information. We can use, for example, mosaic of picture albums, slideshow, calendar by day, calendar in table, etc.
  • Zone: a zone is a specific area in which information are displayed. It can support few features like displaying a logo, a title, etc.
  • Layout: a layout contains a specific disposition of zones. The look and feel of the layout is very important. A layout is generally creating to a specific target.
  • Behaviour: a behaviour is associated to a zone and it is responsible for the scrolling of information in the zone. Different kind of behaviours exists like a simple appear/disappear, a scrolling of all information, a scrolling of a specific amount of information, etc.
  • Non-Functional Properties: it concerns all the non functional properties of the digital signage. Like the targeted people, the device used to display information, the kind of connection, etc.

Below we present the domain-model used in YourCast with all these concepts and the links between them.

Feature Models

Source

Number of features: 41

Number of configurations: 61

Renderer

Number of features: 34

Number of configurations: 22

Layout

Number of features: 24

Number of configurations: 8

Zone

Number of features: 15

Number of configurations: 5

Behaviour

Number of features: 28

Number of configurations: 9

Non-Function Properties

Number of features: 17

Number of configurations: 189

en/models2013.txt · Last modified: 2013/04/12 16:46 by collet