diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleLogger.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleLogger.java
new file mode 100644
index 000000000..57a1cc6fd
--- /dev/null
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleLogger.java
@@ -0,0 +1,229 @@
+package cz.cuni.mff.d3s.deeco.task;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+import cz.cuni.mff.d3s.deeco.knowledge.ReadOnlyKnowledgeManager;
+import cz.cuni.mff.d3s.deeco.logging.Log;
+import cz.cuni.mff.d3s.deeco.model.runtime.api.EnsembleController;
+import cz.cuni.mff.d3s.deeco.scheduler.Scheduler;
+
+/**
+ * Main usage of this class: logging of the changes in ensemble membership
+ * @see {@link EnsembleLogger#logEvent(EnsembleController, ReadOnlyKnowledgeManager, Scheduler, boolean)}
+ * @author Tomas Filipek
+ */
+class EnsembleLogger {
+
+ /**
+ * Encapsulates three pieces of data: ensemble name, coordinator ID and member ID.
+ * It is used to index the information whether the membership condition holds for the
+ * given triplet.
+ * @author Tomas Filipek
+ */
+ private static class MembershipRecord {
+
+ /**
+ * Identification of the ensemble
+ */
+ private final String ensembleName;
+
+ /**
+ * Identification of the coordinator
+ */
+ private final String coordinatorID;
+
+ /**
+ * Identification of the member
+ */
+ private final String memberID;
+
+ /**
+ * @param ensembleName Identification of the ensemble
+ * @param coordinatorID Identification of the coordinator
+ * @param memberID Identification of the member
+ */
+ public MembershipRecord(String ensembleName, String coordinatorID, String memberID) {
+ this.ensembleName = ensembleName;
+ this.coordinatorID = coordinatorID;
+ this.memberID = memberID;
+ }
+
+ /**
+ * The produced hash code depends on precisely the following fields:
+ * {@link MembershipRecord#ensembleName} ,
+ * {@link MembershipRecord#coordinatorID} ,
+ * {@link MembershipRecord#memberID}
+ */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime
+ * result
+ + ((coordinatorID == null) ? 0 : coordinatorID
+ .hashCode());
+ result = prime
+ * result
+ + ((ensembleName == null) ? 0 : ensembleName.hashCode());
+ result = prime * result
+ + ((memberID == null) ? 0 : memberID.hashCode());
+ return result;
+ }
+
+ /**
+ * The equality holds if and only if the following fields are equal amid the two instances:
+ * {@link MembershipRecord#ensembleName} ,
+ * {@link MembershipRecord#coordinatorID} ,
+ * {@link MembershipRecord#memberID}
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof EnsembleLogger.MembershipRecord)) {
+ return false;
+ }
+ EnsembleLogger.MembershipRecord other = (EnsembleLogger.MembershipRecord) obj;
+ if (coordinatorID == null) {
+ if (other.coordinatorID != null) {
+ return false;
+ }
+ } else if (!coordinatorID.equals(other.coordinatorID)) {
+ return false;
+ }
+ if (ensembleName == null) {
+ if (other.ensembleName != null) {
+ return false;
+ }
+ } else if (!ensembleName.equals(other.ensembleName)) {
+ return false;
+ }
+ if (memberID == null) {
+ if (other.memberID != null) {
+ return false;
+ }
+ } else if (!memberID.equals(other.memberID)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /**
+ * The event log will be saved to this file.
+ */
+ private final File ensembleEventLog = new File("logs/ensembles.xml");
+
+ /**
+ * An instance of {@link Writer} that writes into {@link EnsembleLogger#ensembleEventLog}.
+ * @see {@link EnsembleLogger#ensembleEventLog}
+ */
+ private final OutputStreamWriter out;
+
+ /**
+ *
Used to remember the last encountered value of the membership condition, computed on the
+ * input objects which are given as an instance of {@link MembershipRecord}.
+ * Example:
+ * key = triplet of (ensemble E, coordinator C, member M)
+ * value = true (meaning that when last checked, M was in ensemble E, where C was the coordinator)
+ *
+ */
+ private final Map membershipRecords = new HashMap<>();
+
+ /**
+ * Initializes the output streams
+ */
+ private EnsembleLogger() {
+ OutputStreamWriter osw;
+ try {
+ FileOutputStream fos = new FileOutputStream(ensembleEventLog);
+ osw = new OutputStreamWriter(fos);
+ } catch (Exception ex) {
+ osw = null;
+ }
+ out = osw;
+ }
+
+ /**
+ * The singleton instance of this class
+ */
+ private static final EnsembleLogger INSTANCE = new EnsembleLogger();
+
+ /**
+ * @return The singleton instance of this class
+ * @see {@link EnsembleLogger#INSTANCE}
+ */
+ public static EnsembleLogger getInstance() {
+ return INSTANCE;
+ }
+
+ /**
+ * Logs the information about ensemble membership to the output file. It remembers the
+ * previous value, so the output is produced only if it has changed from the last time.
+ * @param ensembleController Controller of the {@link EnsembleTask} where this method is called.
+ * @param shadowKnowledgeManager Knowledge of the other component
+ * @param scheduler Used to get the current time
+ * @param membership The current value of the membership condition
+ */
+ public void logEvent(EnsembleController ensembleController, ReadOnlyKnowledgeManager shadowKnowledgeManager,
+ Scheduler scheduler, boolean membership){
+ long timeSeconds = scheduler.getCurrentMilliseconds() / 1000L;
+ String ensembleName = ensembleController.getEnsembleDefinition().getName();
+ String coordinatorID = ensembleController.getComponentInstance().getKnowledgeManager().getId();
+ String memberID = shadowKnowledgeManager.getId();
+ EnsembleLogger.MembershipRecord mr = new MembershipRecord(ensembleName, coordinatorID, memberID);
+ boolean oldMembership = membershipRecords.get(mr) == null ? false : membershipRecords.get(mr);
+ if (oldMembership != membership){
+ membershipRecords.put(mr, Boolean.valueOf(membership));
+ String[] attrNames = new String[] {"coordinator", "member", "membership", "ensemble", "time"};
+ String[] attrValues = new String[] {coordinatorID, memberID, Boolean.toString(membership), ensembleName, Long.toString(timeSeconds)};
+ String elementText = getElementText("event", attrNames, attrValues);
+ try {
+ out.append(elementText);
+ out.flush();
+ } catch (IOException | NullPointerException ex) {
+ Log.e("Could not log an ensemble event to " + ensembleEventLog.getAbsolutePath().toString());
+ }
+ }
+ }
+
+ /**
+ * Builds a textual representation of an XML element defined by the parameters.
+ * A newline is added to the end of the element.
+ * @param name Name of the element
+ * @param attributeNames Names of the attributes, in the order as they will appear
+ * @param attributeValues Values of the attributes, in the same order as the names
+ * @return A textual representation of an XML element defined by the parameters
+ */
+ private String getElementText(String name, String[] attributeNames, String[] attributeValues){
+ if ((name == null) || name.isEmpty() || (attributeNames == null) ||
+ (attributeValues == null) || (attributeNames.length != attributeValues.length)){
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append("<");
+ sb.append(name);
+ sb.append(" ");
+ for (int i = 0; i < attributeNames.length; i++){
+ String attrName = attributeNames[i];
+ String attrValue = attributeValues[i];
+ sb.append(attrName);
+ sb.append("=\"");
+ sb.append(attrValue);
+ sb.append("\" ");
+ }
+ sb.append("/>");
+ sb.append("\n");
+ return sb.toString();
+ }
+}
diff --git a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java
index 610f91986..1bc1a2d48 100644
--- a/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java
+++ b/jdeeco-core/src/cz/cuni/mff/d3s/deeco/task/EnsembleTask.java
@@ -9,10 +9,10 @@
import cz.cuni.mff.d3s.deeco.knowledge.ChangeSet;
import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeManager;
-import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeUpdateException;
-import cz.cuni.mff.d3s.deeco.knowledge.ShadowKnowledgeManagerRegistry;
import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeNotFoundException;
+import cz.cuni.mff.d3s.deeco.knowledge.KnowledgeUpdateException;
import cz.cuni.mff.d3s.deeco.knowledge.ReadOnlyKnowledgeManager;
+import cz.cuni.mff.d3s.deeco.knowledge.ShadowKnowledgeManagerRegistry;
import cz.cuni.mff.d3s.deeco.knowledge.ShadowsTriggerListener;
import cz.cuni.mff.d3s.deeco.knowledge.TriggerListener;
import cz.cuni.mff.d3s.deeco.knowledge.ValueSet;
@@ -25,8 +25,8 @@
import cz.cuni.mff.d3s.deeco.model.runtime.api.ParameterDirection;
import cz.cuni.mff.d3s.deeco.model.runtime.api.TimeTrigger;
import cz.cuni.mff.d3s.deeco.model.runtime.api.Trigger;
-import cz.cuni.mff.d3s.deeco.model.runtime.meta.RuntimeMetadataFactory;
import cz.cuni.mff.d3s.deeco.model.runtime.impl.TriggerImpl;
+import cz.cuni.mff.d3s.deeco.model.runtime.meta.RuntimeMetadataFactory;
import cz.cuni.mff.d3s.deeco.runtime.ArchitectureObserver;
import cz.cuni.mff.d3s.deeco.scheduler.Scheduler;
import cz.cuni.mff.d3s.deeco.task.KnowledgePathHelper.KnowledgePathAndRoot;
@@ -495,14 +495,20 @@ public void invoke(Trigger trigger) throws TaskInvocationException {
}
}
}
-
+
private void evaluateMembershipAndPerformExchange(ReadOnlyKnowledgeManager shadowKnowledgeManager) throws TaskInvocationException {
// Invoke the membership condition and if the membership condition returned true, invoke the knowledge exchange
+ boolean membership;
if (checkMembership(PathRoot.COORDINATOR, shadowKnowledgeManager)) {
architectureObserver.ensembleFormed(ensembleController.getEnsembleDefinition(), ensembleController.getComponentInstance(),
ensembleController.getComponentInstance().getKnowledgeManager().getId(),shadowKnowledgeManager.getId());
performExchange(PathRoot.COORDINATOR, shadowKnowledgeManager);
+ membership = true;
+ } else {
+ membership = false;
}
+ EnsembleLogger.getInstance().logEvent(ensembleController, shadowKnowledgeManager, scheduler, membership);
+
// Do the same with the roles exchanged
if (checkMembership(PathRoot.MEMBER, shadowKnowledgeManager)) {
architectureObserver.ensembleFormed(ensembleController.getEnsembleDefinition(), ensembleController.getComponentInstance(),