/**
* Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET
* (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije
* informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE
* COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp.,
* INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM
* ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC))
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.societies.privacytrust.trust.impl.engine;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.societies.api.privacytrust.trust.TrustException;
import org.societies.api.privacytrust.trust.evidence.TrustEvidenceType;
import org.societies.api.privacytrust.trust.model.TrustValueType;
import org.societies.api.privacytrust.trust.model.TrustedEntityId;
import org.societies.api.privacytrust.trust.model.TrustedEntityType;
import org.societies.privacytrust.trust.api.engine.IDirectTrustEngine;
import org.societies.privacytrust.trust.api.engine.TrustEngineException;
import org.societies.privacytrust.trust.api.event.ITrustEventMgr;
import org.societies.privacytrust.trust.api.event.ITrustEvidenceUpdateEventListener;
import org.societies.privacytrust.trust.api.event.TrustEventTopic;
import org.societies.privacytrust.trust.api.event.TrustEvidenceUpdateEvent;
import org.societies.privacytrust.trust.api.evidence.model.ITrustEvidence;
import org.societies.privacytrust.trust.api.model.IDirectTrust;
import org.societies.privacytrust.trust.api.model.ITrust;
import org.societies.privacytrust.trust.api.model.ITrustedCis;
import org.societies.privacytrust.trust.api.model.ITrustedCss;
import org.societies.privacytrust.trust.api.model.ITrustedEntity;
import org.societies.privacytrust.trust.api.repo.TrustRepositoryException;
import org.societies.privacytrust.trust.api.util.MathUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author <a href="mailto:nicolas.liampotis@cn.ntua.gr">Nicolas Liampotis</a> (ICCS)
* @since 0.0.8
*/
@Service
public class DirectTrustEngine extends TrustEngine implements IDirectTrustEngine {
/** The logging facility. */
private static final Logger LOG = LoggerFactory.getLogger(DirectTrustEngine.class);
public static final Map<TrustEvidenceType, Double> EVIDENCE_SCORE_MAP;
static {
final Map<TrustEvidenceType, Double> aMap = new HashMap<TrustEvidenceType, Double>();
aMap.put(TrustEvidenceType.SHARED_CONTEXT, +1.0d);
aMap.put(TrustEvidenceType.WITHHELD_CONTEXT, -10.0d);
aMap.put(TrustEvidenceType.FRIENDED_USER, +10.0d);
aMap.put(TrustEvidenceType.UNFRIENDED_USER, -50.0d);
aMap.put(TrustEvidenceType.JOINED_COMMUNITY, +10.0d);
aMap.put(TrustEvidenceType.LEFT_COMMUNITY, -50.0d);
aMap.put(TrustEvidenceType.USED_SERVICE, +1.0d);
EVIDENCE_SCORE_MAP = Collections.unmodifiableMap(aMap);
}
@Autowired
public DirectTrustEngine(ITrustEventMgr trustEventMgr) throws Exception {
super(trustEventMgr);
LOG.info("{} instantiated", this.getClass());
try {
LOG.info("Registering for trust evidence updates...");
super.trustEventMgr.registerEvidenceUpdateListener(
new DirectTrustEvidenceUpdateListener(),
new String[] { TrustEventTopic.TRUST_EVIDENCE_UPDATED });
} catch (Exception e) {
LOG.error(this.getClass() + " could not be initialised: "
+ e.getLocalizedMessage(), e);
throw e;
}
}
/*
* @see org.societies.privacytrust.trust.api.engine.IDirectTrustEngine#evaluate(org.societies.api.privacytrust.trust.model.TrustedEntityId, org.societies.privacytrust.trust.api.evidence.model.ITrustEvidence)
*/
@Override
public Set<ITrustedEntity> evaluate(final TrustedEntityId trustorId,
final ITrustEvidence evidence) throws TrustEngineException {
if (trustorId == null) {
throw new NullPointerException("trustorId can't be null");
}
if (evidence == null) {
throw new NullPointerException("evidence can't be null");
}
LOG.debug("evaluate: trustorId={}, evidence={}", trustorId, evidence);
Set<ITrustedEntity> resultSet = new HashSet<ITrustedEntity>();
if (!this.areRelevant(trustorId, evidence)) {
return resultSet;
}
try {
// Retrieve all TrustedEntities DIRECTLY trusted by the trustor
// having the same type as the object referenced in the specified TrustEvidence
resultSet = this.trustRepo.retrieveEntities(
trustorId, evidence.getObjectId().getEntityType(), TrustValueType.DIRECT);
ITrustedEntity trustee = null;
// 1. Obtain reference to trustee (if already contained in the result set)
// 2. Remove myCss from result set
final Iterator<ITrustedEntity> resultIter = resultSet.iterator();
while (resultIter.hasNext()) {
final ITrustedEntity entity = resultIter.next();
// 1. Obtain reference to trustee
if (entity.getTrusteeId().equals(evidence.getObjectId())) {
trustee = entity;
continue;
}
// 2. Remove myCss from result set
if (entity.getTrusteeId().equals(trustorId)) {
resultIter.remove();
}
}
// Create trustee if not available
if (trustee == null) {
trustee = super.createEntityIfAbsent(trustorId, evidence.getObjectId());
resultSet.add(trustee);
}
LOG.debug("evaluate: trustee={}, resultSet={}", trustee, resultSet);
// Indicates whether direct trust score has been updated
@SuppressWarnings("unused")
boolean scoreUpdated = false;
switch (evidence.getType()) {
// Update rating
case RATED:
// Ignore rating if there is no prior direct trust relationship
if (trustee.getDirectTrust().getValue() == null) {
LOG.debug("evaluate: Ignoring rating for trustee '{}' by trustor '{}'"
+ " - No prior direct trust relationship", trustee.getTrusteeId(), trustorId);
return new HashSet<ITrustedEntity>();
}
trustee.getDirectTrust().setRating((Double) evidence.getInfo());
scoreUpdated = true;
break;
// Update score
case SHARED_CONTEXT:
case WITHHELD_CONTEXT:
case FRIENDED_USER:
case UNFRIENDED_USER:
case USED_SERVICE:
final Double oldScore = trustee.getDirectTrust().getScore();
if (!(evidence.getInfo() instanceof Double)) {
trustee.getDirectTrust().setScore(oldScore + EVIDENCE_SCORE_MAP.get(evidence.getType()));
} else {
trustee.getDirectTrust().setScore(oldScore + (Double) evidence.getInfo());
}
scoreUpdated = true;
break;
// 1. Update score if subject is me
// 2. Update membership association
case JOINED_COMMUNITY:
case LEFT_COMMUNITY:
// Create TrustedCss representing the user that joined the community
final ITrustedCss user;
if (trustorId.equals(evidence.getSubjectId())) {
user = this.createMyCssIfAbsent(evidence.getSubjectId());
// 1. Update score
final Double oldCommunityScore = trustee.getDirectTrust().getScore();
if (!(evidence.getInfo() instanceof Double)) {
trustee.getDirectTrust().setScore(
oldCommunityScore + EVIDENCE_SCORE_MAP.get(evidence.getType()));
} else {
trustee.getDirectTrust().setScore(
oldCommunityScore + (Double) evidence.getInfo());
}
scoreUpdated = true;
} else {
user = (ITrustedCss) super.createEntityIfAbsent(trustorId, evidence.getSubjectId());
}
// 2. Update membership association
final ITrustedCis community = (ITrustedCis) trustee;
if (TrustEvidenceType.JOINED_COMMUNITY == evidence.getType()) {
user.addCommunity(community);
} else {
user.removeCommunity(community);
}
if (LOG.isDebugEnabled()) {
LOG.debug("user '" + user + "' " + evidence.getType() + " community '" + community + "'");
LOG.debug("user is member of " + user.getCommunities().size() + " communities");
LOG.debug("community has " + community.getMembers().size() + " members");
}
this.trustRepo.updateEntity(user);
break;
default:
throw new TrustEngineException("Unsupported type: "
+ evidence.getType());
}
// Add related evidence to trustee
trustee.addEvidence(evidence);
//if (scoreUpdated) {
// Estimate new trust values based on updated scores
evaluateScores(resultSet);
//}
if (TrustedEntityType.CIS == trustee.getTrusteeId().getEntityType()) {
// if (scoreUpdated) {
evaluateCommunityMembers(resultSet);
// } else {
// final Set<ITrustedEntity> entities = new HashSet<ITrustedEntity>();
// entities.add(trustee);
// evaluateCommunityMembers(entities);
// }
}
// Persist updated entities in the Trust Repository
for (final ITrustedEntity entity : resultSet) {
this.trustRepo.updateEntity(entity);
}
// If result set contains users, update affected communities
final Set<ITrustedCss> userSet = new HashSet<ITrustedCss>(resultSet.size());
for (final ITrustedEntity entity : resultSet) {
if (entity instanceof ITrustedCss) {
userSet.add((ITrustedCss) entity);
}
}
if (!userSet.isEmpty()) {
final Set<ITrustedEntity> allCommunities = this.trustRepo.retrieveEntities(
trustorId, TrustedEntityType.CIS, TrustValueType.DIRECT);
if (!allCommunities.isEmpty()) {
final Set<ITrustedEntity> affectedCommunities =
new HashSet<ITrustedEntity>(allCommunities.size());
for (final ITrustedEntity entity : allCommunities) {
if (entity instanceof ITrustedCis) {
final ITrustedCis community = (ITrustedCis) entity;
for (final ITrustedCss user : userSet) {
if (community.getMembers().contains(user)) {
affectedCommunities.add(community);
break;
}
}
}
}
if (!affectedCommunities.isEmpty()) {
evaluateCommunityMembers(affectedCommunities);
// Persist updated communities in the Trust Repository
for (final ITrustedEntity entity : affectedCommunities) {
this.trustRepo.updateEntity(entity);
}
resultSet.addAll(affectedCommunities);
}
}
}
} catch (Exception e) {
throw new TrustEngineException(e.getLocalizedMessage(), e);
}
return resultSet;
}
/*
* @see org.societies.privacytrust.trust.api.engine.IDirectTrustEngine#evaluate(org.societies.api.privacytrust.trust.model.TrustedEntityId, java.util.Set)
*/
@Override
public Set<ITrustedEntity> evaluate(final TrustedEntityId trustorId,
final Set<ITrustEvidence> evidenceSet)
throws TrustEngineException {
if (trustorId == null) {
throw new NullPointerException("trustorId can't be null");
}
if (evidenceSet == null) {
throw new NullPointerException("evidenceSet can't be null");
}
LOG.debug("evaluate: trustorId={}, evidenceSet={}", trustorId, evidenceSet);
final Set<ITrustedEntity> resultSet = new HashSet<ITrustedEntity>();
// create sorted evidence set based on the evidence timestamps
final SortedSet<ITrustEvidence> sortedEvidenceSet =
new TreeSet<ITrustEvidence>(evidenceSet);
LOG.debug("evaluate: sortedEvidenceSet={}", sortedEvidenceSet);
for (final ITrustEvidence evidence : sortedEvidenceSet) {
final Set<ITrustedEntity> newResultSet = this.evaluate(trustorId, evidence);
resultSet.removeAll(newResultSet);
resultSet.addAll(newResultSet);
}
return resultSet;
}
private ITrustedCss createMyCssIfAbsent(final TrustedEntityId myTrustorId)
throws TrustRepositoryException {
ITrustedCss myCss = (ITrustedCss) this.trustRepo.retrieveEntity(
myTrustorId, myTrustorId);
if (myCss == null) {
myCss = (ITrustedCss) this.trustRepo.createEntity(myTrustorId, myTrustorId);
myCss.getDirectTrust().setRating(IDirectTrust.MAX_RATING);
myCss.getDirectTrust().setScore(IDirectTrust.MAX_SCORE);
myCss.getDirectTrust().setValue(ITrust.MAX_VALUE);
myCss = (ITrustedCss) this.trustRepo.updateEntity(myCss);
}
return myCss;
}
/**
* Checks if the specified piece of evidence is relevant for the supplied
* trustor. More specifically, a piece of evidence is relevant for direct
* trust evaluation if:
* <ol>
* <li>trustorId != evidence.objectId, i.e. ignore evidence about trustor</li>
* <li>trustorId == evidence.subjectId</li>
* <ol>
* <li>type == {@link TrustEvidenceType#SHARED_CONTEXT SHARED_CONTEXT}</li>
* <li>type == {@link TrustEvidenceType#WITHHELD_CONTEXT WITHHELD_CONTEXT}</li>
* <li>type == {@link TrustEvidenceType#RATED RATED}</li>
* <li>type == {@link TrustEvidenceType#FRIENDED_USER FRIENDED_USER}</li>
* <li>type == {@link TrustEvidenceType#UNFRIENDED_USER UNFRIENDED_USER}</li>
* <li>type == {@link TrustEvidenceType#USED_SERVICE USED_SERVICE}</li>
* </ol>
* <li>type == {@link TrustEvidenceType#JOINED_COMMUNITY JOINED_COMMUNITY}</li>
* <li>type == {@link TrustEvidenceType#LEFT_COMMUNITY LEFT_COMMUNITY}</li>
* </ol>
*
* @param trustorId
* @param evidence
* @return <code>true</code> if the specified piece of evidence is relevant
* for the supplied trustor; <code>false</code> otherwise.
*/
private boolean areRelevant(final TrustedEntityId trustorId,
final ITrustEvidence evidence) {
boolean result = false;
if (!trustorId.equals(evidence.getObjectId())) {
switch (evidence.getType()) {
case SHARED_CONTEXT:
case WITHHELD_CONTEXT:
case RATED:
case FRIENDED_USER:
case UNFRIENDED_USER:
case USED_SERVICE:
if (trustorId.equals(evidence.getSubjectId())) {
result = true;
}
break;
case JOINED_COMMUNITY:
case LEFT_COMMUNITY:
result = true;
break;
default:
// NOP
}
}
LOG.debug("areRelevant: trustorId={}, evidence={}, result={}",
new Object[] { trustorId, evidence, result });
return result;
}
private static void evaluateScores(final Set<ITrustedEntity> entitySet)
throws TrustEngineException {
LOG.debug("evaluateScores: entitySet={}", entitySet);
final double[] rawTrustScores = new double[entitySet.size()+1];
final ITrustedEntity[] entityArray = entitySet.toArray(new ITrustedEntity[0]);
for (int i = 0; i < entityArray.length; ++i) {
rawTrustScores[i] = entityArray[i].getDirectTrust().getScore();
}
rawTrustScores[entitySet.size()] = IDirectTrust.INIT_SCORE;
final double[] stanineTrustScores = MathUtils.stanine(rawTrustScores);
for (int i = 0; i < entityArray.length; ++i) {
final Double rating = entityArray[i].getDirectTrust().getRating();
final Double stanineScore = stanineTrustScores[i];
//final Double oldValue = entityArray[i].getDirectTrust().getValue();
final Double newValue = estimateValue(rating, stanineScore);
//if ((oldValue == null && newValue != null) || (oldValue != null && newValue == null)
// || (oldValue != null && newValue != null && Double.compare(oldValue, newValue) != 0)) {
entityArray[i].getDirectTrust().setValue(newValue);
//}
}
}
private static void evaluateCommunityMembers(final Set<ITrustedEntity> entitySet)
throws TrustEngineException {
LOG.debug("evaluateCommunityMembers: entitySet={}", entitySet);
for (final ITrustedEntity entity : entitySet) {
if (entity instanceof ITrustedCis) {
final Double oldValue = entity.getDirectTrust().getValue();
final Double minMemberValue = getMinMemberValue((ITrustedCis) entity);
LOG.debug("evaluateCommunityMembers: trusteeId={}, oldValue={}, minMemberValue={}",
new Object[] { entity.getTrusteeId(), oldValue, minMemberValue });
if ((oldValue == null && minMemberValue != null)
|| (oldValue != null && minMemberValue != null && minMemberValue < oldValue)) {
entity.getDirectTrust().setValue(minMemberValue);
}
}
}
}
/**
* { RATING_WEIGHT * rating + (1 - RATING_WEIGHT) * 0.1 * stanineScore, if rating && stanineScore != null
* value = { rating, if staninceScore == null
* { 0.1 * stanineScore, if rating == null
*
* @param rating
* @param stanineScore
* @return
*/
private static Double estimateValue(final Double rating, final Double stanineScore) {
if (rating == null && stanineScore == null) {
return null;
}
if (stanineScore == null) {
return rating;
}
final Double normalisedScore = 0.1d * stanineScore;
if (rating == null) {
return normalisedScore;
}
return (RATING_WEIGHT * rating + (1.0d - RATING_WEIGHT) * normalisedScore);
}
private static Double getMinMemberValue(final ITrustedCis community) throws TrustEngineException {
LOG.debug("getMinMemberValue: communtiy={}", community);
if (community.getMembers().size() == 0) {
return null;
}
final ITrustedCss[] members = community.getMembers().toArray(new ITrustedCss[0]);
final double[] memberTrustValues = new double[members.length];
for (int i = 0; i < memberTrustValues.length; ++i) {
if (members[i].getDirectTrust().getValue() != null) {
memberTrustValues[i] = members[i].getDirectTrust().getValue();
} else {
return null;
}
}
return MathUtils.min(memberTrustValues);
}
private class DirectTrustEvidenceHandler implements Runnable {
private final ITrustEvidence evidence;
private DirectTrustEvidenceHandler(final ITrustEvidence evidence) {
this.evidence = evidence;
}
/*
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
LOG.debug("Handling evidence {}", this.evidence);
try {
for (final TrustedEntityId myId : DirectTrustEngine.super.trustNodeMgr.getMyIds()) {
evaluate(myId, evidence);
}
} catch (TrustException te) {
LOG.error("Could not handle evidence "
+ evidence + ": " + te.getLocalizedMessage(), te);
}
}
}
private class DirectTrustEvidenceUpdateListener implements ITrustEvidenceUpdateEventListener {
/*
* @see org.societies.privacytrust.trust.api.event.ITrustEvidenceUpdateEventListener#onNew(org.societies.privacytrust.trust.api.event.TrustEvidenceUpdateEvent)
*/
@Override
public void onNew(TrustEvidenceUpdateEvent evt) {
LOG.debug("Received TrustEvidenceUpdateEvent {}", evt);
if (!(evt.getSource() instanceof ITrustEvidence)) {
LOG.error("TrustEvidenceUpdateEvent source is not instance of ITrustEvidence");
return;
}
final ITrustEvidence evidence = (ITrustEvidence) evt.getSource();
executorService.execute(new DirectTrustEvidenceHandler(evidence));
}
}
}