From ff91b199987d1ed9934400c5271017f9573362f4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Wed, 3 Jun 2015 20:43:44 +0200 Subject: [PATCH] Add Freenet plugin and WoT subprojects --- build.gradle | 18 +- ide.gradle | 10 +- maven.gradle | 17 + plugin/build.gradle | 7 + .../freenet/plugin/PluginConnector.java | 138 +++++ .../freenet/plugin/PluginException.java | 66 +++ .../freenet/plugin/event/ReceivedReplyEvent.java | 119 ++++ settings.gradle | 3 + wot/build.gradle | 10 + .../net/pterodactylus/freenet/wot/Context.java | 38 ++ .../pterodactylus/freenet/wot/DefaultIdentity.java | 155 ++++++ .../freenet/wot/DefaultOwnIdentity.java | 66 +++ .../net/pterodactylus/freenet/wot/Identity.java | 169 ++++++ .../freenet/wot/IdentityChangeDetector.java | 169 ++++++ .../freenet/wot/IdentityChangeEventSender.java | 111 ++++ .../pterodactylus/freenet/wot/IdentityLoader.java | 81 +++ .../pterodactylus/freenet/wot/IdentityManager.java | 21 + .../freenet/wot/IdentityManagerImpl.java | 120 ++++ .../net/pterodactylus/freenet/wot/OwnIdentity.java | 40 ++ .../java/net/pterodactylus/freenet/wot/Trust.java | 85 +++ .../freenet/wot/WebOfTrustConnector.java | 607 +++++++++++++++++++++ .../freenet/wot/WebOfTrustException.java | 67 +++ .../freenet/wot/event/IdentityAddedEvent.java | 34 ++ .../freenet/wot/event/IdentityEvent.java | 63 +++ .../freenet/wot/event/IdentityRemovedEvent.java | 34 ++ .../freenet/wot/event/IdentityUpdatedEvent.java | 34 ++ .../freenet/wot/event/OwnIdentityAddedEvent.java | 33 ++ .../freenet/wot/event/OwnIdentityEvent.java | 55 ++ .../freenet/wot/event/OwnIdentityRemovedEvent.java | 33 ++ .../java/net/pterodactylus/freenet/Matchers.java | 47 ++ .../freenet/wot/DefaultIdentityTest.java | 152 ++++++ .../freenet/wot/DefaultOwnIdentityTest.java | 42 ++ .../net/pterodactylus/freenet/wot/Identities.java | 47 ++ .../freenet/wot/IdentityChangeDetectorTest.java | 170 ++++++ .../freenet/wot/IdentityChangeEventSenderTest.java | 93 ++++ .../freenet/wot/IdentityLoaderTest.java | 161 ++++++ .../freenet/wot/IdentityManagerTest.java | 48 ++ .../freenet/wot/event/IdentityEventTest.java | 54 ++ .../freenet/wot/event/OwnIdentityEventTest.java | 48 ++ 39 files changed, 3256 insertions(+), 9 deletions(-) create mode 100644 maven.gradle create mode 100644 plugin/build.gradle create mode 100644 plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginConnector.java create mode 100644 plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginException.java create mode 100644 plugin/src/main/java/net/pterodactylus/freenet/plugin/event/ReceivedReplyEvent.java create mode 100644 wot/build.gradle create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/Context.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/DefaultIdentity.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/DefaultOwnIdentity.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/Identity.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeDetector.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeEventSender.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/IdentityLoader.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManager.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManagerImpl.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/OwnIdentity.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/Trust.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustConnector.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustException.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityAddedEvent.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityEvent.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityRemovedEvent.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityUpdatedEvent.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityAddedEvent.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityEvent.java create mode 100644 wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityRemovedEvent.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/Matchers.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/DefaultIdentityTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/DefaultOwnIdentityTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/Identities.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeDetectorTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeEventSenderTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/IdentityLoaderTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/IdentityManagerTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/event/IdentityEventTest.java create mode 100644 wot/src/test/java/net/pterodactylus/freenet/wot/event/OwnIdentityEventTest.java diff --git a/build.gradle b/build.gradle index a84625f..151b9c0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,17 @@ -apply plugin: 'java' +subprojects { + apply plugin: "java" + sourceCompatibility = "1.8" -sourceCompatibility = "1.8" + repositories { + mavenCentral() + maven { + url "http://maven.pterodactylus.net/" + } + } -repositories { - mavenCentral() -} - -dependencies { - testCompile "junit:junit:4.12" } apply from: "ide.gradle" +apply from: "maven.gradle" /* vim: set ts=4 sw=4 et: */ diff --git a/ide.gradle b/ide.gradle index 7f76f61..8df40d5 100644 --- a/ide.gradle +++ b/ide.gradle @@ -1,3 +1,11 @@ -apply plugin: "idea" +allprojects { + apply plugin: "idea" +} + +idea { + project { + languageLevel = "1.8" + } +} /* vim: set ts=4 sw=4 et: */ diff --git a/maven.gradle b/maven.gradle new file mode 100644 index 0000000..0902fd3 --- /dev/null +++ b/maven.gradle @@ -0,0 +1,17 @@ +apply plugin: "maven" + +group = "net.pterodactylus" +version = "0.0.1" + +subprojects { + task sourcesJar(type: Jar, dependsOn: classes) { + classifier "sources" + from sourceSets.main.allSource + } + + artifacts { + archives sourcesJar + } +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/plugin/build.gradle b/plugin/build.gradle new file mode 100644 index 0000000..918177c --- /dev/null +++ b/plugin/build.gradle @@ -0,0 +1,7 @@ +dependencies { + compile "org.freenetproject:fred:0.7.5.1467.99.3" + compile "com.google.guava:guava:18.0" + compile "javax.inject:javax.inject:1" +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginConnector.java b/plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginConnector.java new file mode 100644 index 0000000..73de0d4 --- /dev/null +++ b/plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginConnector.java @@ -0,0 +1,138 @@ +/* + * fplugin - PluginConnector.java - Copyright © 2010–2015 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.plugin; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import net.pterodactylus.freenet.plugin.PluginException; +import net.pterodactylus.freenet.plugin.event.ReceivedReplyEvent; + +import freenet.pluginmanager.FredPluginTalker; +import freenet.pluginmanager.PluginNotFoundException; +import freenet.pluginmanager.PluginRespirator; +import freenet.pluginmanager.PluginTalker; +import freenet.support.SimpleFieldSet; +import freenet.support.api.Bucket; + +import com.google.common.eventbus.EventBus; + +/** + * Interface for talking to other plugins. Other plugins are identified by their + * name and a unique connection identifier. + * + * @author David ‘Bombe’ Roden + */ +@Singleton +public class PluginConnector implements FredPluginTalker { + + /** The event bus. */ + private final EventBus eventBus; + + /** The plugin respirator. */ + private final PluginRespirator pluginRespirator; + + /** + * Creates a new plugin connector. + * + * @param eventBus + * The event bus + * @param pluginRespirator + * The plugin respirator + */ + @Inject + public PluginConnector(EventBus eventBus, PluginRespirator pluginRespirator) { + this.eventBus = eventBus; + this.pluginRespirator = pluginRespirator; + } + + // + // ACTIONS + // + + /** + * Sends a request to the given plugin. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param fields + * The fields of the message + * @throws PluginException + * if the plugin can not be found + */ + public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields) throws PluginException { + sendRequest(pluginName, identifier, fields, null); + } + + /** + * Sends a request to the given plugin. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @param fields + * The fields of the message + * @param data + * The payload of the message (may be null) + * @throws PluginException + * if the plugin can not be found + */ + public void sendRequest(String pluginName, String identifier, SimpleFieldSet fields, Bucket data) + throws PluginException { + getPluginTalker(pluginName, identifier).send(fields, data); + } + + // + // PRIVATE METHODS + // + + /** + * Returns the plugin talker for the given plugin connection. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the connection + * @return The plugin talker + * @throws PluginException + * if the plugin can not be found + */ + private PluginTalker getPluginTalker(String pluginName, String identifier) throws PluginException { + try { + return pluginRespirator.getPluginTalker(this, pluginName, identifier); + } catch (PluginNotFoundException pnfe1) { + throw new PluginException(pnfe1); + } + } + + // + // INTERFACE FredPluginTalker + // + + /** + * {@inheritDoc} + */ + @Override + public void onReply(String pluginName, String identifier, SimpleFieldSet params, Bucket data) { + eventBus.post(new ReceivedReplyEvent(this, pluginName, identifier, params, data)); + } + +} diff --git a/plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginException.java b/plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginException.java new file mode 100644 index 0000000..9bd2afe --- /dev/null +++ b/plugin/src/main/java/net/pterodactylus/freenet/plugin/PluginException.java @@ -0,0 +1,66 @@ +/* + * fplugin - PluginException.java - Copyright © 2010–2015 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.plugin; + +/** + * Exception that signals an error when communicating with a plugin. + * + * @author David ‘Bombe’ Roden + */ +public class PluginException extends Exception { + + /** + * Creates a new plugin exception. + */ + public PluginException() { + super(); + } + + /** + * Creates a new plugin exception. + * + * @param message + * The message of the exception + */ + public PluginException(String message) { + super(message); + } + + /** + * Creates a new plugin exception. + * + * @param cause + * The cause of the exception + */ + public PluginException(Throwable cause) { + super(cause); + } + + /** + * Creates a new plugin exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public PluginException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/plugin/src/main/java/net/pterodactylus/freenet/plugin/event/ReceivedReplyEvent.java b/plugin/src/main/java/net/pterodactylus/freenet/plugin/event/ReceivedReplyEvent.java new file mode 100644 index 0000000..8e66e99 --- /dev/null +++ b/plugin/src/main/java/net/pterodactylus/freenet/plugin/event/ReceivedReplyEvent.java @@ -0,0 +1,119 @@ +/* + * fplugin - ReceivedReplyEvent.java - Copyright © 2013–2015 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.plugin.event; + +import net.pterodactylus.freenet.plugin.PluginConnector; + +import freenet.support.SimpleFieldSet; +import freenet.support.api.Bucket; + +/** + * Event that signals that a plugin reply was received. + * + * @author David ‘Bombe’ Roden + */ +public class ReceivedReplyEvent { + + /** The connector that received the reply. */ + private final PluginConnector pluginConnector; + + /** The name of the plugin that sent the reply. */ + private final String pluginName; + + /** The identifier of the initial request. */ + private final String identifier; + + /** The fields containing the reply. */ + private final SimpleFieldSet fieldSet; + + /** The optional reply data. */ + private final Bucket data; + + /** + * Creates a new “reply received” event. + * + * @param pluginConnector + * The connector that received the event + * @param pluginName + * The name of the plugin that sent the reply + * @param identifier + * The identifier of the initial request + * @param fieldSet + * The fields containing the reply + * @param data + * The optional data of the reply + */ + public ReceivedReplyEvent(PluginConnector pluginConnector, String pluginName, String identifier, + SimpleFieldSet fieldSet, Bucket data) { + this.pluginConnector = pluginConnector; + this.pluginName = pluginName; + this.identifier = identifier; + this.fieldSet = fieldSet; + this.data = data; + } + + // + // ACCESSORS + // + + /** + * Returns the plugin connector that received the reply. + * + * @return The plugin connector that received the reply + */ + public PluginConnector pluginConnector() { + return pluginConnector; + } + + /** + * Returns the name of the plugin that sent the reply. + * + * @return The name of the plugin that sent the reply + */ + public String pluginName() { + return pluginName; + } + + /** + * Returns the identifier of the initial request. + * + * @return The identifier of the initial request + */ + public String identifier() { + return identifier; + } + + /** + * Returns the fields containing the reply. + * + * @return The fields containing the reply + */ + public SimpleFieldSet fieldSet() { + return fieldSet; + } + + /** + * Returns the optional data of the reply. + * + * @return The optional data of the reply (may be {@code null}) + */ + public Bucket data() { + return data; + } + +} diff --git a/settings.gradle b/settings.gradle index 4aaa41c..902a6c3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ rootProject.name = 'fwot' + +include "plugin" +include "wot" diff --git a/wot/build.gradle b/wot/build.gradle new file mode 100644 index 0000000..17954a7 --- /dev/null +++ b/wot/build.gradle @@ -0,0 +1,10 @@ +dependencies { + compile(project(":plugin")) + compile "com.google.inject:guice:4.0" + + testCompile "junit:junit:4.12" + testCompile "org.hamcrest:hamcrest-all:1.3" + testCompile "org.mockito:mockito-core:1.10.19" +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/Context.java b/wot/src/main/java/net/pterodactylus/freenet/wot/Context.java new file mode 100644 index 0000000..1c6fa71 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/Context.java @@ -0,0 +1,38 @@ +/* + * Sone - Context.java - Copyright © 2014 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +/** + * Custom container for the Web of Trust context. This allows easier + * configuration of dependency injection. + * + * @author David ‘Bombe’ Roden + */ +public class Context { + + private final String context; + + public Context(String context) { + this.context = context; + } + + public String getContext() { + return context; + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/DefaultIdentity.java b/wot/src/main/java/net/pterodactylus/freenet/wot/DefaultIdentity.java new file mode 100644 index 0000000..dcae0da --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/DefaultIdentity.java @@ -0,0 +1,155 @@ +/* + * Sone - DefaultIdentity.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * A Web of Trust identity. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultIdentity implements Identity { + + private final String id; + private final String nickname; + private final String requestUri; + private final Set contexts = Collections.synchronizedSet(new HashSet<>()); + private final Map properties = Collections.synchronizedMap(new HashMap<>()); + private final Map trustCache = Collections.synchronizedMap(new HashMap<>()); + + public DefaultIdentity(String id, String nickname, String requestUri) { + this.id = id; + this.nickname = nickname; + this.requestUri = requestUri; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getNickname() { + return nickname; + } + + @Override + public String getRequestUri() { + return requestUri; + } + + @Override + public Set getContexts() { + return Collections.unmodifiableSet(contexts); + } + + @Override + public boolean hasContext(String context) { + return contexts.contains(context); + } + + @Override + public void setContexts(Collection contexts) { + this.contexts.clear(); + this.contexts.addAll(contexts); + } + + @Override + public Identity addContext(String context) { + contexts.add(context); + return this; + } + + @Override + public Identity removeContext(String context) { + contexts.remove(context); + return this; + } + + @Override + public Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + @Override + public void setProperties(Map properties) { + this.properties.clear(); + this.properties.putAll(properties); + } + + @Override + public String getProperty(String name) { + return properties.get(name); + } + + @Override + public Identity setProperty(String name, String value) { + properties.put(name, value); + return this; + } + + @Override + public Identity removeProperty(String name) { + properties.remove(name); + return this; + } + + @Override + public Trust getTrust(OwnIdentity ownIdentity) { + return trustCache.get(ownIdentity); + } + + @Override + public Identity setTrust(OwnIdentity ownIdentity, Trust trust) { + trustCache.put(ownIdentity, trust); + return this; + } + + @Override + public Identity removeTrust(OwnIdentity ownIdentity) { + trustCache.remove(ownIdentity); + return this; + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Identity)) { + return false; + } + Identity identity = (Identity) object; + return Objects.equals(getId(), identity.getId()); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[id=" + id + ",nickname=" + nickname + ",contexts=" + contexts + ",properties=" + properties + "]"; + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/DefaultOwnIdentity.java b/wot/src/main/java/net/pterodactylus/freenet/wot/DefaultOwnIdentity.java new file mode 100644 index 0000000..4d94e98 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/DefaultOwnIdentity.java @@ -0,0 +1,66 @@ +/* + * Sone - DefaultOwnIdentity.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * An own identity is an identity that the owner of the node has full control + * over. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity { + + private final String insertUri; + + public DefaultOwnIdentity(String id, String nickname, String requestUri, String insertUri) { + super(id, nickname, requestUri); + this.insertUri = checkNotNull(insertUri); + } + + // + // ACCESSORS + // + + @Override + public String getInsertUri() { + return insertUri; + } + + @Override + public OwnIdentity addContext(String context) { + return (OwnIdentity) super.addContext(context); + } + + @Override + public OwnIdentity removeContext(String context) { + return (OwnIdentity) super.removeContext(context); + } + + @Override + public OwnIdentity setProperty(String name, String value) { + return (OwnIdentity) super.setProperty(name, value); + } + + @Override + public OwnIdentity removeProperty(String name) { + return (OwnIdentity) super.removeProperty(name); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/Identity.java b/wot/src/main/java/net/pterodactylus/freenet/wot/Identity.java new file mode 100644 index 0000000..f6afcf9 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/Identity.java @@ -0,0 +1,169 @@ +/* + * Sone - Identity.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * Interface for web of trust identities, defining all functions that can be + * performed on an identity. An identity is only a container for identity data + * and will not perform any updating in the WebOfTrust plugin itself. + * + * @author David ‘Bombe’ Roden + */ +public interface Identity { + + /** + * Returns the ID of the identity. + * + * @return The ID of the identity + */ + String getId(); + + /** + * Returns the nickname of the identity. + * + * @return The nickname of the identity + */ + String getNickname(); + + /** + * Returns the request URI of the identity. + * + * @return The request URI of the identity + */ + String getRequestUri(); + + /** + * Returns all contexts of this identity. + * + * @return All contexts of this identity + */ + Set getContexts(); + + /** + * Returns whether this identity has the given context. + * + * @param context + * The context to check for + * @return {@code true} if this identity has the given context, + * {@code false} otherwise + */ + boolean hasContext(String context); + + /** + * Adds the given context to this identity. + * + * @param context + * The context to add + */ + Identity addContext(String context); + + /** + * Sets all contexts of this identity. + * + * @param contexts + * All contexts of the identity + */ + void setContexts(Collection contexts); + + /** + * Removes the given context from this identity. + * + * @param context + * The context to remove + */ + Identity removeContext(String context); + + /** + * Returns all properties of this identity. + * + * @return All properties of this identity + */ + Map getProperties(); + + /** + * Returns the value of the property with the given name. + * + * @param name + * The name of the property + * @return The value of the property + */ + String getProperty(String name); + + /** + * Sets the property with the given name to the given value. + * + * @param name + * The name of the property + * @param value + * The value of the property + */ + Identity setProperty(String name, String value); + + /** + * Sets all properties of this identity. + * + * @param properties + * The new properties of this identity + */ + void setProperties(Map properties); + + /** + * Removes the property with the given name. + * + * @param name + * The name of the property to remove + */ + Identity removeProperty(String name); + + /** + * Retrieves the trust that this identity receives from the given own + * identity. If this identity is not in the own identity’s trust tree, a + * {@link Trust} is returned that has all its elements set to {@code null}. + * If the trust can not be retrieved, {@code null} is returned. + * + * @param ownIdentity + * The own identity to get the trust for + * @return The trust assigned to this identity, or {@code null} if the trust + * could not be retrieved + */ + Trust getTrust(OwnIdentity ownIdentity); + + /** + * Sets the trust given by an own identity to this identity. + * + * @param ownIdentity + * The own identity that gave trust to this identity + * @param trust + * The trust given by the given own identity + */ + Identity setTrust(OwnIdentity ownIdentity, Trust trust); + + /** + * Removes trust assignment from the given own identity for this identity. + * + * @param ownIdentity + * The own identity that removed the trust assignment for this + * identity + */ + Identity removeTrust(OwnIdentity ownIdentity); + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeDetector.java b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeDetector.java new file mode 100644 index 0000000..6e78035 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeDetector.java @@ -0,0 +1,169 @@ +/* + * Sone - IdentityChangeDetector.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static com.google.common.base.Optional.absent; +import static com.google.common.base.Optional.fromNullable; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.FluentIterable.from; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableMap; + +/** + * Detects changes between two lists of {@link Identity}s. The detector can find + * added and removed identities, and for identities that exist in both list + * their contexts and properties are checked for added, removed, or (in case of + * properties) changed values. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityChangeDetector { + + private final Map oldIdentities; + private Optional onNewIdentity = absent(); + private Optional onRemovedIdentity = absent(); + private Optional onChangedIdentity = absent(); + private Optional onUnchangedIdentity = absent(); + + public IdentityChangeDetector(Collection oldIdentities) { + this.oldIdentities = convertToMap(oldIdentities); + } + + public void onNewIdentity(IdentityProcessor onNewIdentity) { + this.onNewIdentity = fromNullable(onNewIdentity); + } + + public void onRemovedIdentity(IdentityProcessor onRemovedIdentity) { + this.onRemovedIdentity = fromNullable(onRemovedIdentity); + } + + public void onChangedIdentity(IdentityProcessor onChangedIdentity) { + this.onChangedIdentity = fromNullable(onChangedIdentity); + } + + public void onUnchangedIdentity(IdentityProcessor onUnchangedIdentity) { + this.onUnchangedIdentity = fromNullable(onUnchangedIdentity); + } + + public void detectChanges(Collection newIdentities) { + notifyForRemovedIdentities(from(oldIdentities.values()).filter(notContainedIn(newIdentities))); + notifyForNewIdentities(from(newIdentities).filter(notContainedIn(oldIdentities.values()))); + notifyForChangedIdentities(from(newIdentities).filter(containedIn(oldIdentities)).filter(hasChanged(oldIdentities))); + notifyForUnchangedIdentities(from(newIdentities).filter(containedIn(oldIdentities)).filter(not(hasChanged(oldIdentities)))); + } + + private void notifyForRemovedIdentities(Iterable identities) { + notify(onRemovedIdentity, identities); + } + + private void notifyForNewIdentities(FluentIterable newIdentities) { + notify(onNewIdentity, newIdentities); + } + + private void notifyForChangedIdentities(FluentIterable identities) { + notify(onChangedIdentity, identities); + } + + private void notifyForUnchangedIdentities(FluentIterable identities) { + notify(onUnchangedIdentity, identities); + } + + private void notify(Optional identityProcessor, Iterable identities) { + if (!identityProcessor.isPresent()) { + return; + } + for (Identity identity : identities) { + identityProcessor.get().processIdentity(identity); + } + } + + private static Predicate hasChanged(Map oldIdentities) { + return identity -> identityHasChanged(oldIdentities.get(identity.getId()), identity); + } + + private static boolean identityHasChanged(Identity oldIdentity, Identity newIdentity) { + return identityHasNewContexts(oldIdentity, newIdentity) + || identityHasRemovedContexts(oldIdentity, newIdentity) + || identityHasNewProperties(oldIdentity, newIdentity) + || identityHasRemovedProperties(oldIdentity, newIdentity) + || identityHasChangedProperties(oldIdentity, newIdentity); + } + + private static boolean identityHasNewContexts(Identity oldIdentity, Identity newIdentity) { + return from(newIdentity.getContexts()).anyMatch(notAContextOf(oldIdentity)); + } + + private static boolean identityHasRemovedContexts(Identity oldIdentity, Identity newIdentity) { + return from(oldIdentity.getContexts()).anyMatch(notAContextOf(newIdentity)); + } + + private static boolean identityHasNewProperties(Identity oldIdentity, Identity newIdentity) { + return from(newIdentity.getProperties().entrySet()).anyMatch(notAPropertyOf(oldIdentity)); + } + + private static boolean identityHasRemovedProperties(Identity oldIdentity, Identity newIdentity) { + return from(oldIdentity.getProperties().entrySet()).anyMatch(notAPropertyOf(newIdentity)); + } + + private static boolean identityHasChangedProperties(Identity oldIdentity, Identity newIdentity) { + return from(oldIdentity.getProperties().entrySet()).anyMatch(hasADifferentValueThanIn(newIdentity)); + } + + private static Predicate containedIn(Map identities) { + return identity -> identities.containsKey(identity.getId()); + } + + private static Predicate notAContextOf(Identity identity) { + return context -> (identity != null) && !identity.getContexts().contains(context); + } + + private static Predicate notContainedIn(Collection newIdentities) { + return identity -> (identity != null) && !newIdentities.contains(identity); + } + + private static Predicate> notAPropertyOf(Identity identity) { + return property -> (property != null) && !identity.getProperties().containsKey(property.getKey()); + } + + private static Predicate> hasADifferentValueThanIn(Identity newIdentity) { + return property -> + (property != null) && !newIdentity.getProperty(property.getKey()).equals(property.getValue()); + } + + private static Map convertToMap(Collection identities) { + ImmutableMap.Builder mapBuilder = ImmutableMap.builder(); + for (Identity identity : identities) { + mapBuilder.put(identity.getId(), identity); + } + return mapBuilder.build(); + } + + public interface IdentityProcessor { + + void processIdentity(Identity identity); + + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeEventSender.java b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeEventSender.java new file mode 100644 index 0000000..3317ae0 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityChangeEventSender.java @@ -0,0 +1,111 @@ +/* + * Sone - IdentityChangeEventSender.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import java.util.Collection; +import java.util.Map; + +import net.pterodactylus.freenet.wot.IdentityChangeDetector.IdentityProcessor; +import net.pterodactylus.freenet.wot.event.IdentityAddedEvent; +import net.pterodactylus.freenet.wot.event.IdentityRemovedEvent; +import net.pterodactylus.freenet.wot.event.IdentityUpdatedEvent; +import net.pterodactylus.freenet.wot.event.OwnIdentityAddedEvent; +import net.pterodactylus.freenet.wot.event.OwnIdentityRemovedEvent; + +import com.google.common.eventbus.EventBus; + +/** + * Detects changes in {@link Identity}s trusted my multiple {@link OwnIdentity}s. + * + * @author David ‘Bombe’ Roden + * @see IdentityChangeDetector + */ +public class IdentityChangeEventSender { + + private final EventBus eventBus; + private final Map> oldIdentities; + + public IdentityChangeEventSender(EventBus eventBus, Map> oldIdentities) { + this.eventBus = eventBus; + this.oldIdentities = oldIdentities; + } + + public void detectChanges(Map> identities) { + IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(oldIdentities.keySet()); + identityChangeDetector.onNewIdentity(addNewOwnIdentityAndItsTrustedIdentities(identities)); + identityChangeDetector.onRemovedIdentity(removeOwnIdentityAndItsTrustedIdentities(oldIdentities)); + identityChangeDetector.onUnchangedIdentity(detectChangesInTrustedIdentities(identities, oldIdentities)); + identityChangeDetector.detectChanges(identities.keySet()); + } + + private IdentityProcessor addNewOwnIdentityAndItsTrustedIdentities(Map> newIdentities) { + return identity -> { + eventBus.post(new OwnIdentityAddedEvent((OwnIdentity) identity)); + for (Identity newIdentity : newIdentities.get(identity)) { + eventBus.post(new IdentityAddedEvent((OwnIdentity) identity, newIdentity)); + } + }; + } + + private IdentityProcessor removeOwnIdentityAndItsTrustedIdentities(Map> oldIdentities) { + return identity -> { + eventBus.post(new OwnIdentityRemovedEvent((OwnIdentity) identity)); + for (Identity removedIdentity : oldIdentities.get(identity)) { + eventBus.post(new IdentityRemovedEvent((OwnIdentity) identity, removedIdentity)); + } + }; + } + + private IdentityProcessor detectChangesInTrustedIdentities(Map> newIdentities, Map> oldIdentities) { + return new DefaultIdentityProcessor(oldIdentities, newIdentities); + } + + private class DefaultIdentityProcessor implements IdentityProcessor { + + private final Map> oldIdentities; + private final Map> newIdentities; + + public DefaultIdentityProcessor(Map> oldIdentities, Map> newIdentities) { + this.oldIdentities = oldIdentities; + this.newIdentities = newIdentities; + } + + @Override + public void processIdentity(Identity ownIdentity) { + IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(oldIdentities.get(ownIdentity)); + identityChangeDetector.onNewIdentity(notifyForAddedIdentities((OwnIdentity) ownIdentity)); + identityChangeDetector.onRemovedIdentity(notifyForRemovedIdentities((OwnIdentity) ownIdentity)); + identityChangeDetector.onChangedIdentity(notifyForChangedIdentities((OwnIdentity) ownIdentity)); + identityChangeDetector.detectChanges(newIdentities.get(ownIdentity)); + } + + private IdentityProcessor notifyForChangedIdentities(OwnIdentity ownIdentity) { + return identity -> eventBus.post(new IdentityUpdatedEvent(ownIdentity, identity)); + } + + private IdentityProcessor notifyForRemovedIdentities(OwnIdentity ownIdentity) { + return identity -> eventBus.post(new IdentityRemovedEvent(ownIdentity, identity)); + } + + private IdentityProcessor notifyForAddedIdentities(OwnIdentity ownIdentity) { + return identity -> eventBus.post(new IdentityAddedEvent(ownIdentity, identity)); + } + + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityLoader.java b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityLoader.java new file mode 100644 index 0000000..5e64a6a --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityLoader.java @@ -0,0 +1,81 @@ +/* + * Sone - IdentityLoader.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import net.pterodactylus.freenet.plugin.PluginException; + +import com.google.common.base.Optional; +import com.google.inject.Inject; + +/** + * Loads {@link OwnIdentity}s and the {@link Identity}s they trust. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityLoader { + + private final WebOfTrustConnector webOfTrustConnector; + private final Optional context; + + public IdentityLoader(WebOfTrustConnector webOfTrustConnector) { + this(webOfTrustConnector, Optional.absent()); + } + + @Inject + public IdentityLoader(WebOfTrustConnector webOfTrustConnector, Optional context) { + this.webOfTrustConnector = webOfTrustConnector; + this.context = context; + } + + public Map> loadIdentities() throws PluginException { + Collection currentOwnIdentities = webOfTrustConnector.loadAllOwnIdentities(); + return loadTrustedIdentitiesForOwnIdentities(currentOwnIdentities); + } + + private Map> loadTrustedIdentitiesForOwnIdentities( + Collection ownIdentities) throws PluginException { + Map> currentIdentities = new HashMap<>(); + + for (OwnIdentity ownIdentity : ownIdentities) { + if (identityDoesNotHaveTheCorrectContext(ownIdentity)) { + currentIdentities.put(ownIdentity, Collections.emptySet()); + continue; + } + + Set trustedIdentities = loadTrustedIdentities(ownIdentity); + currentIdentities.put(ownIdentity, trustedIdentities); + } + + return currentIdentities; + } + + private boolean identityDoesNotHaveTheCorrectContext(OwnIdentity ownIdentity) { + return context.isPresent() && !ownIdentity.hasContext(context.transform(Context::getContext).get()); + } + + private Set loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException { + return webOfTrustConnector.loadTrustedIdentities(ownIdentity, context.transform(Context::getContext)); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManager.java b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManager.java new file mode 100644 index 0000000..bed8dd4 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManager.java @@ -0,0 +1,21 @@ +package net.pterodactylus.freenet.wot; + +import java.util.Set; + +import com.google.common.eventbus.EventBus; +import com.google.common.util.concurrent.Service; +import com.google.inject.ImplementedBy; + +/** + * Connects to a {@link WebOfTrustConnector} and sends identity events to an + * {@link EventBus}. + * + * @author David ‘Bombe’ Roden + */ +@ImplementedBy(IdentityManagerImpl.class) +public interface IdentityManager extends Service { + + boolean isConnected(); + Set getAllOwnIdentities(); + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManagerImpl.java b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManagerImpl.java new file mode 100644 index 0000000..f9adca5 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/IdentityManagerImpl.java @@ -0,0 +1,120 @@ +/* + * Sone - IdentityManager.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static java.util.logging.Logger.getLogger; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import net.pterodactylus.freenet.plugin.PluginException; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.eventbus.EventBus; +import com.google.common.util.concurrent.AbstractScheduledService; + +/** + * The identity manager takes care of loading and storing identities, their + * contexts, and properties. It does so in a way that does not expose errors via + * exceptions but it only logs them and tries to return sensible defaults. + *

+ * It is also responsible for polling identities from the Web of Trust plugin + * and sending events to the {@link EventBus} when {@link Identity}s and + * {@link OwnIdentity}s are discovered or disappearing. + * + * @author David ‘Bombe’ Roden + */ +@Singleton +public class IdentityManagerImpl extends AbstractScheduledService implements IdentityManager { + + private static final Logger logger = getLogger("FWoT.Identities"); + + private final EventBus eventBus; + private final IdentityLoader identityLoader; + private final WebOfTrustConnector webOfTrustConnector; + + private final Set currentOwnIdentities = Sets.newHashSet(); + private final AtomicReference>> oldIdentities = + new AtomicReference<>(Maps.newHashMap()); + + @Inject + public IdentityManagerImpl(EventBus eventBus, WebOfTrustConnector webOfTrustConnector, + IdentityLoader identityLoader) { + this.eventBus = eventBus; + this.webOfTrustConnector = webOfTrustConnector; + this.identityLoader = identityLoader; + } + + @Override + protected String serviceName() { + return "Freenet-WoT Identity Manager"; + } + + @Override + public boolean isConnected() { + try { + webOfTrustConnector.ping(); + return true; + } catch (PluginException pe1) { + /* not connected, ignore. */ + return false; + } + } + + @Override + public Set getAllOwnIdentities() { + synchronized (currentOwnIdentities) { + return new HashSet<>(currentOwnIdentities); + } + } + + @Override + protected Scheduler scheduler() { + return Scheduler.newFixedDelaySchedule(60, 60, TimeUnit.SECONDS); + } + + @Override + protected void runOneIteration() { + try { + Map> currentIdentities = identityLoader.loadIdentities(); + + IdentityChangeEventSender identityChangeEventSender = + new IdentityChangeEventSender(eventBus, oldIdentities.get()); + identityChangeEventSender.detectChanges(currentIdentities); + + synchronized (currentOwnIdentities) { + currentOwnIdentities.clear(); + currentOwnIdentities.addAll(currentIdentities.keySet()); + } + oldIdentities.set(currentIdentities); + } catch (PluginException pe1) { + logger.log(Level.WARNING, "WoT has disappeared!", pe1); + } + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/OwnIdentity.java b/wot/src/main/java/net/pterodactylus/freenet/wot/OwnIdentity.java new file mode 100644 index 0000000..a0a5738 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/OwnIdentity.java @@ -0,0 +1,40 @@ +/* + * Sone - OwnIdentity.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + + +/** + * Defines a local identity, an own identity. + * + * @author David ‘Bombe’ Roden + */ +public interface OwnIdentity extends Identity { + + /** + * Returns the insert URI of the identity. + * + * @return The insert URI of the identity + */ + String getInsertUri(); + + OwnIdentity addContext(String context); + OwnIdentity removeContext(String context); + OwnIdentity setProperty(String name, String value); + OwnIdentity removeProperty(String name); + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/Trust.java b/wot/src/main/java/net/pterodactylus/freenet/wot/Trust.java new file mode 100644 index 0000000..4544c44 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/Trust.java @@ -0,0 +1,85 @@ +/* + * Sone - Trust.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import java.util.Objects; + +/** + * Container class for trust in the web of trust. + * + * @author David ‘Bombe’ Roden + */ +public class Trust { + + private final Integer explicit; + private final Integer implicit; + private final Integer distance; + + public Trust(Integer explicit, Integer implicit, Integer distance) { + this.explicit = explicit; + this.implicit = implicit; + this.distance = distance; + } + + /** + * Returns the trust explicitely assigned to an identity. + * + * @return The explicitely assigned trust, or {@code null} if the identity + * is not in the own identity’s trust tree + */ + public Integer getExplicit() { + return explicit; + } + + /** + * Returns the implicitely assigned trust, or the calculated trust. + * + * @return The calculated trust, or {@code null} if the identity is not in + * the own identity’s trust tree + */ + public Integer getImplicit() { + return implicit; + } + + /** + * Returns the distance of the trusted identity from the trusting identity. + * + * @return The distance from the own identity, or {@code null} if the + * identity is not in the own identity’s trust tree + */ + public Integer getDistance() { + return distance; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof Trust)) { + return false; + } + Trust trust = (Trust) object; + return Objects.equals(getExplicit(), trust.getExplicit()) + && Objects.equals(getImplicit(), trust.getImplicit()) + && Objects.equals(getDistance(), trust.getDistance()); + } + + @Override + public String toString() { + return getClass().getName() + "[explicit=" + explicit + ",implicit=" + implicit + ",distance=" + distance + "]"; + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustConnector.java b/wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustConnector.java new file mode 100644 index 0000000..119bada --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustConnector.java @@ -0,0 +1,607 @@ +/* + * Sone - WebOfTrustConnector.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static java.util.logging.Logger.getLogger; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import net.pterodactylus.freenet.plugin.PluginConnector; +import net.pterodactylus.freenet.plugin.PluginException; +import net.pterodactylus.freenet.plugin.event.ReceivedReplyEvent; + +import freenet.support.SimpleFieldSet; +import freenet.support.api.Bucket; + +import com.google.common.base.Optional; +import com.google.common.collect.MapMaker; +import com.google.common.eventbus.Subscribe; +import com.google.common.primitives.Ints; + +/** + * Connector for the Web of Trust plugin. + * + * @author David ‘Bombe’ Roden + */ +@Singleton +public class WebOfTrustConnector { + + private static final Logger logger = getLogger("Sone.WoT.Connector"); + private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust"; + + private final AtomicLong counter = new AtomicLong(); + private final PluginConnector pluginConnector; + private final Map replies = new MapMaker().makeMap(); + + @Inject + public WebOfTrustConnector(PluginConnector pluginConnector) { + this.pluginConnector = pluginConnector; + } + + public Set loadAllOwnIdentities() throws PluginException { + Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get()); + SimpleFieldSet fields = reply.getFields(); + int ownIdentityCounter = -1; + Set ownIdentities = new HashSet<>(); + while (true) { + String id = fields.get("Identity" + ++ownIdentityCounter); + if (id == null) { + break; + } + String requestUri = fields.get("RequestURI" + ownIdentityCounter); + String insertUri = fields.get("InsertURI" + ownIdentityCounter); + String nickname = fields.get("Nickname" + ownIdentityCounter); + DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri); + ownIdentity.setContexts(parseContexts("Contexts" + ownIdentityCounter + ".", fields)); + ownIdentity.setProperties(parseProperties("Properties" + ownIdentityCounter + ".", fields)); + ownIdentities.add(ownIdentity); + } + return ownIdentities; + } + + /** + * Loads all identities that the given identities trusts with a score of + * more than 0. + * + * @param ownIdentity + * The own identity + * @return All trusted identities + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public Set loadTrustedIdentities(OwnIdentity ownIdentity) throws PluginException { + return loadTrustedIdentities(ownIdentity, null); + } + + /** + * Loads all identities that the given identities trusts with a score of + * more than 0 and the (optional) given context. + * + * @param ownIdentity + * The own identity + * @param context + * The context to filter, or {@code null} + * @return All trusted identities + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public Set loadTrustedIdentities(OwnIdentity ownIdentity, Optional context) throws PluginException { + Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", context.or("")).put("WantTrustValues", "true").get()); + SimpleFieldSet fields = reply.getFields(); + Set identities = new HashSet<>(); + int identityCounter = -1; + while (true) { + String id = fields.get("Identity" + ++identityCounter); + if (id == null) { + break; + } + String nickname = fields.get("Nickname" + identityCounter); + String requestUri = fields.get("RequestURI" + identityCounter); + DefaultIdentity identity = new DefaultIdentity(id, nickname, requestUri); + identity.setContexts(parseContexts("Contexts" + identityCounter + ".", fields)); + identity.setProperties(parseProperties("Properties" + identityCounter + ".", fields)); + Integer trust = parseInt(fields.get("Trust" + identityCounter), null); + int score = parseInt(fields.get("Score" + identityCounter), 0); + int rank = parseInt(fields.get("Rank" + identityCounter), 0); + identity.setTrust(ownIdentity, new Trust(trust, score, rank)); + identities.add(identity); + } + return identities; + } + + private static Integer parseInt(String value, Integer defaultValue) { + return java.util.Optional.ofNullable(value).map(Ints::tryParse).orElse(defaultValue); + } + + /** + * Adds the given context to the given identity. + * + * @param ownIdentity + * The identity to add the context to + * @param context + * The context to add + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public void addContext(OwnIdentity ownIdentity, String context) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "AddContext").put("Identity", ownIdentity.getId()).put("Context", context).get()); + } + + /** + * Removes the given context from the given identity. + * + * @param ownIdentity + * The identity to remove the context from + * @param context + * The context to remove + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public void removeContext(OwnIdentity ownIdentity, String context) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveContext").put("Identity", ownIdentity.getId()).put("Context", context).get()); + } + + /** + * Returns the value of the property with the given name. + * + * @param identity + * The identity whose properties to check + * @param name + * The name of the property to return + * @return The value of the property, or {@code null} if there is no value + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public String getProperty(Identity identity, String name) throws PluginException { + Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get()); + return reply.getFields().get("Property"); + } + + /** + * Sets the property with the given name to the given value. + * + * @param ownIdentity + * The identity to set the property on + * @param name + * The name of the property to set + * @param value + * The value to set + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public void setProperty(OwnIdentity ownIdentity, String name, String value) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "SetProperty").put("Identity", ownIdentity.getId()).put("Property", name).put("Value", value).get()); + } + + /** + * Removes the property with the given name. + * + * @param ownIdentity + * The identity to remove the property from + * @param name + * The name of the property to remove + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public void removeProperty(OwnIdentity ownIdentity, String name) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveProperty").put("Identity", ownIdentity.getId()).put("Property", name).get()); + } + + /** + * Returns the trust for the given identity assigned to it by the given own + * identity. + * + * @param ownIdentity + * The own identity + * @param identity + * The identity to get the trust for + * @return The trust for the given identity + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public Trust getTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException { + Reply getTrustReply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentity").put("Truster", ownIdentity.getId()).put("Identity", identity.getId()).get()); + String trust = getTrustReply.getFields().get("Trust"); + String score = getTrustReply.getFields().get("Score"); + String rank = getTrustReply.getFields().get("Rank"); + Integer explicit = null; + Integer implicit = null; + Integer distance = null; + try { + explicit = Integer.valueOf(trust); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + try { + implicit = Integer.valueOf(score); + distance = Integer.valueOf(rank); + } catch (NumberFormatException nfe1) { + /* ignore. */ + } + return new Trust(explicit, implicit, distance); + } + + /** + * Sets the trust for the given identity. + * + * @param ownIdentity + * The trusting identity + * @param identity + * The trusted identity + * @param trust + * The amount of trust (-100 thru 100) + * @param comment + * The comment or explanation of the trust value + * @throws PluginException + * if an error occured talking to the Web of Trust plugin + */ + public void setTrust(OwnIdentity ownIdentity, Identity identity, int trust, String comment) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "SetTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).put("Value", String + .valueOf(trust)).put("Comment", comment).get()); + } + + /** + * Removes any trust assignment of the given own identity for the given + * identity. + * + * @param ownIdentity + * The own identity + * @param identity + * The identity to remove all trust for + * @throws WebOfTrustException + * if an error occurs + */ + public void removeTrust(OwnIdentity ownIdentity, Identity identity) throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "RemoveTrust").put("Truster", ownIdentity.getId()).put("Trustee", identity.getId()).get()); + } + + /** + * Pings the Web of Trust plugin. If the plugin can not be reached, a + * {@link PluginException} is thrown. + * + * @throws PluginException + * if the plugin is not loaded + */ + public void ping() throws PluginException { + performRequest(SimpleFieldSetConstructor.create().put("Message", "Ping").get()); + } + + // + // PRIVATE ACTIONS + // + + /** + * Parses the contexts from the given fields. + * + * @param prefix + * The prefix to use to access the contexts + * @param fields + * The fields to parse the contexts from + * @return The parsed contexts + */ + private static Set parseContexts(String prefix, SimpleFieldSet fields) { + Set contexts = new HashSet<>(); + int contextCounter = -1; + while (true) { + String context = fields.get(prefix + "Context" + ++contextCounter); + if (context == null) { + break; + } + contexts.add(context); + } + return contexts; + } + + /** + * Parses the properties from the given fields. + * + * @param prefix + * The prefix to use to access the properties + * @param fields + * The fields to parse the properties from + * @return The parsed properties + */ + private static Map parseProperties(String prefix, SimpleFieldSet fields) { + Map properties = new HashMap<>(); + int propertiesCounter = -1; + while (true) { + String propertyName = fields.get(prefix + "Property" + ++propertiesCounter + ".Name"); + if (propertyName == null) { + break; + } + String propertyValue = fields.get(prefix + "Property" + propertiesCounter + ".Value"); + properties.put(propertyName, propertyValue); + } + return properties; + } + + /** + * Sends a request containing the given fields and waits for the target + * message. + * + * @param fields + * The fields of the message + * @return The reply message + * @throws PluginException + * if the request could not be sent + */ + private Reply performRequest(SimpleFieldSet fields) throws PluginException { + return performRequest(fields, null); + } + + /** + * Sends a request containing the given fields and waits for the target + * message. + * + * @param fields + * The fields of the message + * @param data + * The payload of the message + * @return The reply message + * @throws PluginException + * if the request could not be sent + */ + private Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException { + String identifier = "FCP-Command-" + System.currentTimeMillis() + "-" + counter.getAndIncrement(); + Reply reply = new Reply(); + PluginIdentifier pluginIdentifier = new PluginIdentifier(WOT_PLUGIN_NAME, identifier); + replies.put(pluginIdentifier, reply); + + logger.log(Level.FINE, String.format("Sending FCP Request: %s", fields.get("Message"))); + synchronized (reply) { + try { + pluginConnector.sendRequest(WOT_PLUGIN_NAME, identifier, fields, data); + while (reply.getFields() == null) { + try { + reply.wait(); + } catch (InterruptedException ie1) { + logger.log(Level.WARNING, String.format("Got interrupted while waiting for reply on %s.", + fields.get("Message")), ie1); + } + } + } finally { + replies.remove(pluginIdentifier); + } + } + logger.log(Level.FINEST, String.format("Received FCP Response for %s: %s", fields.get("Message"), + (reply.getFields() != null) ? reply.getFields().get("Message") : null)); + if ((reply.getFields() == null) || "Error".equals(reply.getFields().get("Message"))) { + throw new PluginException("Could not perform request for " + fields.get("Message")); + } + return reply; + } + + /** + * Notifies the connector that a plugin reply was received. + * + * @param receivedReplyEvent + * The event + */ + @Subscribe + public void receivedReply(ReceivedReplyEvent receivedReplyEvent) { + PluginIdentifier pluginIdentifier = new PluginIdentifier(receivedReplyEvent.pluginName(), receivedReplyEvent.identifier()); + Reply reply = replies.remove(pluginIdentifier); + if (reply == null) { + return; + } + logger.log(Level.FINEST, String.format("Received Reply from Plugin: %s", + receivedReplyEvent.fieldSet().get("Message"))); + synchronized (reply) { + reply.setFields(receivedReplyEvent.fieldSet()); + reply.setData(receivedReplyEvent.data()); + reply.notify(); + } + } + + /** + * Container for the data of the reply from a plugin. + * + * @author David ‘Bombe’ Roden + */ + private static class Reply { + + /** The fields of the reply. */ + private SimpleFieldSet fields; + + /** The payload of the reply. */ + private Bucket data; + + /** Empty constructor. */ + public Reply() { + /* do nothing. */ + } + + /** + * Returns the fields of the reply. + * + * @return The fields of the reply + */ + public SimpleFieldSet getFields() { + return fields; + } + + /** + * Sets the fields of the reply. + * + * @param fields + * The fields of the reply + */ + public void setFields(SimpleFieldSet fields) { + this.fields = fields; + } + + /** + * Returns the payload of the reply. + * + * @return The payload of the reply (may be {@code null}) + */ + @SuppressWarnings("unused") + public Bucket getData() { + return data; + } + + /** + * Sets the payload of the reply. + * + * @param data + * The payload of the reply (may be {@code null}) + */ + public void setData(Bucket data) { + this.data = data; + } + + } + + /** + * Helper method to create {@link SimpleFieldSet}s with terser code. + * + * @author David ‘Bombe’ Roden + */ + private static class SimpleFieldSetConstructor { + + /** The field set being created. */ + private final SimpleFieldSet simpleFieldSet; + + /** + * Creates a new simple field set constructor. + * + * @param shortLived + * {@code true} if the resulting simple field set should be + * short-lived, {@code false} otherwise + */ + private SimpleFieldSetConstructor(boolean shortLived) { + simpleFieldSet = new SimpleFieldSet(shortLived); + } + + // + // ACCESSORS + // + + /** + * Returns the created simple field set. + * + * @return The created simple field set + */ + public SimpleFieldSet get() { + return simpleFieldSet; + } + + /** + * Sets the field with the given name to the given value. + * + * @param name + * The name of the fleld + * @param value + * The value of the field + * @return This constructor (for method chaining) + */ + public SimpleFieldSetConstructor put(String name, String value) { + simpleFieldSet.putOverwrite(name, value); + return this; + } + + // + // ACTIONS + // + + /** + * Creates a new simple field set constructor. + * + * @return The created simple field set constructor + */ + public static SimpleFieldSetConstructor create() { + return create(true); + } + + /** + * Creates a new simple field set constructor. + * + * @param shortLived + * {@code true} if the resulting simple field set should be + * short-lived, {@code false} otherwise + * @return The created simple field set constructor + */ + public static SimpleFieldSetConstructor create(boolean shortLived) { + return new SimpleFieldSetConstructor(shortLived); + } + + } + + /** + * Container for identifying plugins. Plugins are identified by their plugin + * name and their unique identifier. + * + * @author David Roden + */ + private static class PluginIdentifier { + + /** The plugin name. */ + private final String pluginName; + + /** The plugin identifier. */ + private final String identifier; + + /** + * Creates a new plugin identifier. + * + * @param pluginName + * The name of the plugin + * @param identifier + * The identifier of the plugin + */ + public PluginIdentifier(String pluginName, String identifier) { + this.pluginName = pluginName; + this.identifier = identifier; + } + + // + // OBJECT METHODS + // + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return pluginName.hashCode() ^ identifier.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (!(object instanceof PluginIdentifier)) { + return false; + } + PluginIdentifier pluginIdentifier = (PluginIdentifier) object; + return pluginName.equals(pluginIdentifier.pluginName) && identifier.equals(pluginIdentifier.identifier); + } + + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustException.java b/wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustException.java new file mode 100644 index 0000000..cd8aa71 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/WebOfTrustException.java @@ -0,0 +1,67 @@ +/* + * Sone - WebOfTrustException.java - Copyright © 2010–2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +/** + * Exception that signals an error processing web of trust identities, mostly + * when communicating with the web of trust plugin. + * + * @author David ‘Bombe’ Roden + */ +public class WebOfTrustException extends Exception { + + /** + * Creates a new web of trust exception. + */ + public WebOfTrustException() { + super(); + } + + /** + * Creates a new web of trust exception. + * + * @param message + * The message of the exception + */ + public WebOfTrustException(String message) { + super(message); + } + + /** + * Creates a new web of trust exception. + * + * @param cause + * The cause of the exception + */ + public WebOfTrustException(Throwable cause) { + super(cause); + } + + /** + * Creates a new web of trust exception. + * + * @param message + * The message of the exception + * @param cause + * The cause of the exception + */ + public WebOfTrustException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityAddedEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityAddedEvent.java new file mode 100644 index 0000000..4955e7c --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityAddedEvent.java @@ -0,0 +1,34 @@ +/* + * Sone - IdentityAddedEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import net.pterodactylus.freenet.wot.Identity; +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Event that signals that an {@link Identity} was added. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityAddedEvent extends IdentityEvent { + + public IdentityAddedEvent(OwnIdentity ownIdentity, Identity identity) { + super(ownIdentity, identity); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityEvent.java new file mode 100644 index 0000000..badf26c --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityEvent.java @@ -0,0 +1,63 @@ +/* + * Sone - IdentityEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import java.util.Objects; + +import net.pterodactylus.freenet.wot.Identity; +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Base class for {@link Identity} events. + * + * @author David ‘Bombe’ Roden + */ +public abstract class IdentityEvent { + + private final OwnIdentity ownIdentity; + private final Identity identity; + + protected IdentityEvent(OwnIdentity ownIdentity, Identity identity) { + this.ownIdentity = ownIdentity; + this.identity = identity; + } + + public OwnIdentity getOwnIdentity() { + return ownIdentity; + } + + public Identity getIdentity() { + return identity; + } + + @Override + public int hashCode() { + return Objects.hash(getOwnIdentity(), getIdentity()); + } + + @Override + public boolean equals(Object object) { + if ((object == null) || !object.getClass().equals(getClass())) { + return false; + } + IdentityEvent identityEvent = (IdentityEvent) object; + return Objects.equals(getOwnIdentity(), identityEvent.getOwnIdentity()) + && Objects.equals(getIdentity(), identityEvent.getIdentity()); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityRemovedEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityRemovedEvent.java new file mode 100644 index 0000000..b892367 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityRemovedEvent.java @@ -0,0 +1,34 @@ +/* + * Sone - IdentityRemovedEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import net.pterodactylus.freenet.wot.Identity; +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Event that signals that an {@link Identity} was removed. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityRemovedEvent extends IdentityEvent { + + public IdentityRemovedEvent(OwnIdentity ownIdentity, Identity identity) { + super(ownIdentity, identity); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityUpdatedEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityUpdatedEvent.java new file mode 100644 index 0000000..71f3ba1 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/IdentityUpdatedEvent.java @@ -0,0 +1,34 @@ +/* + * Sone - IdentityUpdatedEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import net.pterodactylus.freenet.wot.Identity; +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Event that signals that an {@link Identity} was updated. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityUpdatedEvent extends IdentityEvent { + + public IdentityUpdatedEvent(OwnIdentity ownIdentity, Identity identity) { + super(ownIdentity, identity); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityAddedEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityAddedEvent.java new file mode 100644 index 0000000..eb7a0f1 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityAddedEvent.java @@ -0,0 +1,33 @@ +/* + * Sone - OwnIdentityAddedEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Event that signals that an {@link OwnIdentity} was added. + * + * @author David ‘Bombe’ Roden + */ +public class OwnIdentityAddedEvent extends OwnIdentityEvent { + + public OwnIdentityAddedEvent(OwnIdentity ownIdentity) { + super(ownIdentity); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityEvent.java new file mode 100644 index 0000000..3bfa706 --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityEvent.java @@ -0,0 +1,55 @@ +/* + * Sone - OwnIdentityEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import java.util.Objects; + +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Base class for {@link OwnIdentity} events. + * + * @author David ‘Bombe’ Roden + */ +public abstract class OwnIdentityEvent { + + private final OwnIdentity ownIdentity; + + protected OwnIdentityEvent(OwnIdentity ownIdentity) { + this.ownIdentity = ownIdentity; + } + + public OwnIdentity getOwnIdentity() { + return ownIdentity; + } + + @Override + public int hashCode() { + return Objects.hash(getOwnIdentity()); + } + + @Override + public boolean equals(Object object) { + if ((object == null) || !object.getClass().equals(getClass())) { + return false; + } + OwnIdentityEvent ownIdentityEvent = (OwnIdentityEvent) object; + return Objects.equals(getOwnIdentity(), ownIdentityEvent.getOwnIdentity()); + } + +} diff --git a/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityRemovedEvent.java b/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityRemovedEvent.java new file mode 100644 index 0000000..f4a2a2f --- /dev/null +++ b/wot/src/main/java/net/pterodactylus/freenet/wot/event/OwnIdentityRemovedEvent.java @@ -0,0 +1,33 @@ +/* + * Sone - OwnIdentityRemovedEvent.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot.event; + +import net.pterodactylus.freenet.wot.OwnIdentity; + +/** + * Event that signals that an {@link OwnIdentity} was removed. + * + * @author David ‘Bombe’ Roden + */ +public class OwnIdentityRemovedEvent extends OwnIdentityEvent { + + public OwnIdentityRemovedEvent(OwnIdentity ownIdentity) { + super(ownIdentity); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/Matchers.java b/wot/src/test/java/net/pterodactylus/freenet/Matchers.java new file mode 100644 index 0000000..989e236 --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/Matchers.java @@ -0,0 +1,47 @@ +/* + * Sone - Matchers.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet; + +import static java.util.regex.Pattern.compile; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +/** + * Matchers used throughout the tests. + * + * @author David ‘Bombe’ Roden + */ +public class Matchers { + + public static Matcher matchesRegex(final String regex) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(String item) { + return compile(regex).matcher(item).matches(); + } + + @Override + public void describeTo(Description description) { + description.appendText("matches: ").appendValue(regex); + } + }; + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/DefaultIdentityTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/DefaultIdentityTest.java new file mode 100644 index 0000000..f7f13e6 --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/DefaultIdentityTest.java @@ -0,0 +1,152 @@ +/* + * Sone - DefaultIdentityTest.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static com.google.common.collect.ImmutableMap.of; +import static java.util.Arrays.asList; +import static net.pterodactylus.freenet.Matchers.matchesRegex; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.mockito.Mockito.mock; + +import java.util.Collections; + +import org.junit.Test; + +/** + * Unit test for {@link DefaultIdentity}. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultIdentityTest { + + protected final DefaultIdentity identity = createIdentity(); + + protected DefaultIdentity createIdentity() { + return new DefaultIdentity("Id", "Nickname", "RequestURI"); + } + + @Test + public void identityCanBeCreated() { + assertThat(identity.getId(), is("Id")); + assertThat(identity.getNickname(), is("Nickname")); + assertThat(identity.getRequestUri(), is("RequestURI")); + assertThat(identity.getContexts(), empty()); + assertThat(identity.getProperties(), is(Collections.emptyMap())); + } + + @Test + public void contextsAreAddedCorrectly() { + identity.addContext("Test"); + assertThat(identity.getContexts(), contains("Test")); + assertThat(identity.hasContext("Test"), is(true)); + } + + @Test + public void contextsAreRemovedCorrectly() { + identity.addContext("Test"); + identity.removeContext("Test"); + assertThat(identity.getContexts(), empty()); + assertThat(identity.hasContext("Test"), is(false)); + } + + @Test + public void contextsAreSetCorrectlyInBulk() { + identity.addContext("Test"); + identity.setContexts(asList("Test1", "Test2")); + assertThat(identity.getContexts(), containsInAnyOrder("Test1", "Test2")); + assertThat(identity.hasContext("Test"), is(false)); + assertThat(identity.hasContext("Test1"), is(true)); + assertThat(identity.hasContext("Test2"), is(true)); + } + + @Test + public void propertiesAreAddedCorrectly() { + identity.setProperty("Key", "Value"); + assertThat(identity.getProperties().size(), is(1)); + assertThat(identity.getProperties(), hasEntry("Key", "Value")); + assertThat(identity.getProperty("Key"), is("Value")); + } + + @Test + public void propertiesAreRemovedCorrectly() { + identity.setProperty("Key", "Value"); + identity.removeProperty("Key"); + assertThat(identity.getProperties(), is(Collections.emptyMap())); + assertThat(identity.getProperty("Key"), nullValue()); + } + + @Test + public void propertiesAreSetCorrectlyInBulk() { + identity.setProperty("Key", "Value"); + identity.setProperties(of("Key1", "Value1", "Key2", "Value2")); + assertThat(identity.getProperties().size(), is(2)); + assertThat(identity.getProperty("Key"), nullValue()); + assertThat(identity.getProperty("Key1"), is("Value1")); + assertThat(identity.getProperty("Key2"), is("Value2")); + } + + @Test + public void trustRelationshipsAreAddedCorrectly() { + OwnIdentity ownIdentity = mock(OwnIdentity.class); + Trust trust = mock(Trust.class); + identity.setTrust(ownIdentity, trust); + assertThat(identity.getTrust(ownIdentity), is(trust)); + } + + @Test + public void trustRelationshipsAreRemovedCorrectly() { + OwnIdentity ownIdentity = mock(OwnIdentity.class); + Trust trust = mock(Trust.class); + identity.setTrust(ownIdentity, trust); + identity.removeTrust(ownIdentity); + assertThat(identity.getTrust(ownIdentity), nullValue()); + } + + @Test + public void identitiesWithTheSameIdAreEqual() { + DefaultIdentity identity2 = new DefaultIdentity("Id", "Nickname2", "RequestURI2"); + assertThat(identity2, is(identity)); + assertThat(identity, is(identity2)); + } + + @Test + public void twoEqualIdentitiesHaveTheSameHashCode() { + DefaultIdentity identity2 = new DefaultIdentity("Id", "Nickname2", "RequestURI2"); + assertThat(identity.hashCode(), is(identity2.hashCode())); + } + + @Test + public void nullDoesNotMatchAnIdentity() { + assertThat(identity, not(is((Object) null))); + } + + @Test + public void toStringContainsIdAndNickname() { + String identityString = identity.toString(); + assertThat(identityString, matchesRegex(".*\\bId\\b.*")); + assertThat(identityString, matchesRegex(".*\\bNickname\\b.*")); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/DefaultOwnIdentityTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/DefaultOwnIdentityTest.java new file mode 100644 index 0000000..d82f13a --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/DefaultOwnIdentityTest.java @@ -0,0 +1,42 @@ +/* + * Sone - DefaultOwnIdentityTest.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import org.junit.Test; + +/** + * Unit test for {@link DefaultOwnIdentity}. + * + * @author David ‘Bombe’ Roden + */ +public class DefaultOwnIdentityTest extends DefaultIdentityTest { + + @Override + protected DefaultIdentity createIdentity() { + return new DefaultOwnIdentity("Id", "Nickname", "RequestURI", "InsertURI"); + } + + @Test + public void ownIdentityCanBeCreated() { + assertThat(((OwnIdentity) identity).getInsertUri(), is("InsertURI")); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/Identities.java b/wot/src/test/java/net/pterodactylus/freenet/wot/Identities.java new file mode 100644 index 0000000..92336c2 --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/Identities.java @@ -0,0 +1,47 @@ +/* + * Sone - Identities.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import java.util.Collection; +import java.util.Map; + +/** + * Creates {@link Identity}s and {@link OwnIdentity}s. + * + * @author David ‘Bombe’ Roden + */ +public class Identities { + + public static OwnIdentity createOwnIdentity(String id, Collection contexts, Map properties) { + DefaultOwnIdentity ownIdentity = new DefaultOwnIdentity(id, "Nickname" + id, "Request" + id, "Insert" + id); + setContextsAndPropertiesOnIdentity(ownIdentity, contexts, properties); + return ownIdentity; + } + + public static Identity createIdentity(String id, Collection contexts, Map properties) { + DefaultIdentity identity = new DefaultIdentity(id, "Nickname" + id, "Request" + id); + setContextsAndPropertiesOnIdentity(identity, contexts, properties); + return identity; + } + + private static void setContextsAndPropertiesOnIdentity(Identity identity, Collection contexts, Map properties) { + identity.setContexts(contexts); + identity.setProperties(properties); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeDetectorTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeDetectorTest.java new file mode 100644 index 0000000..a261a0f --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeDetectorTest.java @@ -0,0 +1,170 @@ +/* + * Sone - IdentityChangeDetectorTest.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static com.google.common.collect.ImmutableMap.of; +import static com.google.common.collect.Lists.newArrayList; +import static java.util.Arrays.asList; +import static net.pterodactylus.freenet.wot.Identities.createIdentity; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; + +import java.util.Collection; + +import net.pterodactylus.freenet.wot.IdentityChangeDetector.IdentityProcessor; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for {@link IdentityChangeDetector}. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityChangeDetectorTest { + + private final IdentityChangeDetector identityChangeDetector = new IdentityChangeDetector(createOldIdentities()); + private final Collection newIdentities = newArrayList(); + private final Collection removedIdentities = newArrayList(); + private final Collection changedIdentities = newArrayList(); + private final Collection unchangedIdentities = newArrayList(); + + @Before + public void setup() { + identityChangeDetector.onNewIdentity(newIdentities::add); + identityChangeDetector.onRemovedIdentity(removedIdentities::add); + identityChangeDetector.onChangedIdentity(changedIdentities::add); + identityChangeDetector.onUnchangedIdentity(unchangedIdentities::add); + } + + @Test + public void noDifferencesAreDetectedWhenSendingTheOldIdentitiesAgain() { + identityChangeDetector.detectChanges(createOldIdentities()); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, empty()); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3())); + } + + @Test + public void detectThatAnIdentityWasRemoved() { + identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity3())); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, containsInAnyOrder(createIdentity2())); + assertThat(changedIdentities, empty()); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())); + } + + @Test + public void detectThatAnIdentityWasAdded() { + identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4())); + assertThat(newIdentities, containsInAnyOrder(createIdentity4())); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, empty()); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2(), createIdentity3())); + } + + @Test + public void detectThatAContextWasRemoved() { + Identity identity2 = createIdentity2(); + identity2.removeContext("Context C"); + identityChangeDetector.detectChanges(asList(createIdentity1(), identity2, createIdentity3())); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, containsInAnyOrder(identity2)); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())); + } + + @Test + public void detectThatAContextWasAdded() { + Identity identity2 = createIdentity2(); + identity2.addContext("Context C1"); + identityChangeDetector.detectChanges(asList(createIdentity1(), identity2, createIdentity3())); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, containsInAnyOrder(identity2)); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity3())); + } + + @Test + public void detectThatAPropertyWasRemoved() { + Identity identity1 = createIdentity1(); + identity1.removeProperty("Key A"); + identityChangeDetector.detectChanges(asList(identity1, createIdentity2(), createIdentity3())); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, containsInAnyOrder(identity1)); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity2(), createIdentity3())); + } + + @Test + public void detectThatAPropertyWasAdded() { + Identity identity3 = createIdentity3(); + identity3.setProperty("Key A", "Value A"); + identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), identity3)); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, containsInAnyOrder(identity3)); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2())); + } + + @Test + public void detectThatAPropertyWasChanged() { + Identity identity3 = createIdentity3(); + identity3.setProperty("Key E", "Value F"); + identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), identity3)); + assertThat(newIdentities, empty()); + assertThat(removedIdentities, empty()); + assertThat(changedIdentities, containsInAnyOrder(identity3)); + assertThat(unchangedIdentities, containsInAnyOrder(createIdentity1(), createIdentity2())); + } + + @Test + public void noRemovedIdentitiesAreDetectedWithoutAnIdentityProcessor() { + identityChangeDetector.onRemovedIdentity(null); + identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity3())); + } + + @Test + public void noAddedIdentitiesAreDetectedWithoutAnIdentityProcessor() { + identityChangeDetector.onNewIdentity(null); + identityChangeDetector.detectChanges(asList(createIdentity1(), createIdentity2(), createIdentity3(), createIdentity4())); + } + + private static Collection createOldIdentities() { + return asList(createIdentity1(), createIdentity2(), createIdentity3()); + } + + private static Identity createIdentity1() { + return createIdentity("Test1", asList("Context A", "Context B"), of("Key A", "Value A", "Key B", "Value B")); + } + + private static Identity createIdentity2() { + return createIdentity("Test2", asList("Context C", "Context D"), of("Key C", "Value C", "Key D", "Value D")); + } + + private static Identity createIdentity3() { + return createIdentity("Test3", asList("Context E", "Context F"), of("Key E", "Value E", "Key F", "Value F")); + } + + private static Identity createIdentity4() { + return createIdentity("Test4", asList("Context G", "Context H"), of("Key G", "Value G", "Key H", "Value H")); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeEventSenderTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeEventSenderTest.java new file mode 100644 index 0000000..fd9fe04 --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityChangeEventSenderTest.java @@ -0,0 +1,93 @@ +/* + * Sone - IdentityChangeEventSenderTest.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static com.google.common.collect.ImmutableMap.of; +import static java.util.Arrays.asList; +import static net.pterodactylus.freenet.wot.Identities.createIdentity; +import static net.pterodactylus.freenet.wot.Identities.createOwnIdentity; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.pterodactylus.freenet.wot.event.IdentityAddedEvent; +import net.pterodactylus.freenet.wot.event.IdentityRemovedEvent; +import net.pterodactylus.freenet.wot.event.IdentityUpdatedEvent; +import net.pterodactylus.freenet.wot.event.OwnIdentityAddedEvent; +import net.pterodactylus.freenet.wot.event.OwnIdentityRemovedEvent; + +import com.google.common.eventbus.EventBus; +import org.junit.Test; + +/** + * Unit test for {@link IdentityChangeEventSender}. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityChangeEventSenderTest { + + private final EventBus eventBus = mock(EventBus.class); + private final List ownIdentities = asList( + createOwnIdentity("O1", Collections.singletonList("Test"), of("KeyA", "ValueA")), + createOwnIdentity("O2", Collections.singletonList("Test2"), of("KeyB", "ValueB")), + createOwnIdentity("O3", Collections.singletonList("Test3"), of("KeyC", "ValueC")) + ); + private final List identities = asList( + createIdentity("I1", Collections.emptyList(), Collections.emptyMap()), + createIdentity("I2", Collections.emptyList(), Collections.emptyMap()), + createIdentity("I3", Collections.emptyList(), Collections.emptyMap()), + createIdentity("I2", Collections.singletonList("Test"), Collections.emptyMap()) + ); + private final IdentityChangeEventSender identityChangeEventSender = new IdentityChangeEventSender(eventBus, createOldIdentities()); + + @Test + public void addingAnOwnIdentityIsDetectedAndReportedCorrectly() { + Map> newIdentities = createNewIdentities(); + identityChangeEventSender.detectChanges(newIdentities); + verify(eventBus).post(eq(new OwnIdentityRemovedEvent(ownIdentities.get(0)))); + verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(0), identities.get(0)))); + verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(0), identities.get(1)))); + verify(eventBus).post(eq(new OwnIdentityAddedEvent(ownIdentities.get(2)))); + verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(2), identities.get(1)))); + verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(2), identities.get(2)))); + verify(eventBus).post(eq(new IdentityRemovedEvent(ownIdentities.get(1), identities.get(0)))); + verify(eventBus).post(eq(new IdentityAddedEvent(ownIdentities.get(1), identities.get(2)))); + verify(eventBus).post(eq(new IdentityUpdatedEvent(ownIdentities.get(1), identities.get(1)))); + } + + private Map> createNewIdentities() { + Map> oldIdentities = new HashMap<>(); + oldIdentities.put(ownIdentities.get(1), asList(identities.get(3), identities.get(2))); + oldIdentities.put(ownIdentities.get(2), asList(identities.get(1), identities.get(2))); + return oldIdentities; + } + + private Map> createOldIdentities() { + Map> oldIdentities = new HashMap<>(); + oldIdentities.put(ownIdentities.get(0), asList(identities.get(0), identities.get(1))); + oldIdentities.put(ownIdentities.get(1), asList(identities.get(0), identities.get(1))); + return oldIdentities; + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityLoaderTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityLoaderTest.java new file mode 100644 index 0000000..0498edb --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityLoaderTest.java @@ -0,0 +1,161 @@ +/* + * Sone - IdentityLoaderTest.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.freenet.wot; + +import static com.google.common.base.Optional.of; +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Sets.newHashSet; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import net.pterodactylus.freenet.plugin.PluginException; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for {@link IdentityLoader}. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityLoaderTest { + + private final WebOfTrustConnector webOfTrustConnector = mock( + WebOfTrustConnector.class); + private final IdentityLoader identityLoader = new IdentityLoader(webOfTrustConnector, of(new Context("Test"))); + private final IdentityLoader identityLoaderWithoutContext = new IdentityLoader(webOfTrustConnector); + + @Before + public void setup() throws PluginException { + List ownIdentities = createOwnIdentities(); + when(webOfTrustConnector.loadAllOwnIdentities()).thenReturn(newHashSet(ownIdentities)); + when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(0)), any(Optional.class))).thenReturn(createTrustedIdentitiesForFirstOwnIdentity()); + when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(1)), any(Optional.class))).thenReturn(createTrustedIdentitiesForSecondOwnIdentity()); + when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(2)), any(Optional.class))).thenReturn(createTrustedIdentitiesForThirdOwnIdentity()); + when(webOfTrustConnector.loadTrustedIdentities(eq(ownIdentities.get(3)), any(Optional.class))).thenReturn(createTrustedIdentitiesForFourthOwnIdentity()); + } + + private List createOwnIdentities() { + return newArrayList( + createOwnIdentity("O1", "ON1", "OR1", "OI1", asList("Test", "Test2"), ImmutableMap.of("KeyA", "ValueA", + "KeyB", "ValueB")), + createOwnIdentity("O2", "ON2", "OR2", "OI2", Collections.singletonList("Test"), ImmutableMap.of("KeyC", "ValueC")), + createOwnIdentity("O3", "ON3", "OR3", "OI3", Collections.singletonList("Test2"), ImmutableMap.of("KeyE", "ValueE", "KeyD", + "ValueD")), + createOwnIdentity("O4", "ON4", "OR$", "OI4", Collections.singletonList("Test"), ImmutableMap.of("KeyA", "ValueA", "KeyD", + "ValueD")) + ); + } + + private Set createTrustedIdentitiesForFirstOwnIdentity() { + return newHashSet( + createIdentity("I11", "IN11", "IR11", Collections.singletonList("Test"), ImmutableMap.of("KeyA", "ValueA")) + ); + } + + private Set createTrustedIdentitiesForSecondOwnIdentity() { + return newHashSet( + createIdentity("I21", "IN21", "IR21", asList("Test", "Test2"), ImmutableMap.of("KeyB", "ValueB")) + ); + } + + private Set createTrustedIdentitiesForThirdOwnIdentity() { + return newHashSet( + createIdentity("I31", "IN31", "IR31", asList("Test", "Test3"), ImmutableMap.of("KeyC", "ValueC")) + ); + } + + private Set createTrustedIdentitiesForFourthOwnIdentity() { + return emptySet(); + } + + private OwnIdentity createOwnIdentity(String id, String nickname, String requestUri, String insertUri, List contexts, ImmutableMap properties) { + OwnIdentity ownIdentity = new DefaultOwnIdentity(id, nickname, requestUri, insertUri); + ownIdentity.setContexts(contexts); + ownIdentity.setProperties(properties); + return ownIdentity; + } + + private Identity createIdentity(String id, String nickname, String requestUri, List contexts, ImmutableMap properties) { + Identity identity = new DefaultIdentity(id, nickname, requestUri); + identity.setContexts(contexts); + identity.setProperties(properties); + return identity; + } + + @Test + public void loadingIdentities() throws PluginException { + List ownIdentities = createOwnIdentities(); + Map> identities = identityLoader.loadIdentities(); + verify(webOfTrustConnector).loadAllOwnIdentities(); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(0)), eq(of("Test"))); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(1)), eq(of("Test"))); + verify(webOfTrustConnector, never()).loadTrustedIdentities(eq(ownIdentities.get(2)), any(Optional.class)); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(3)), eq(of("Test"))); + assertThat(identities.keySet(), hasSize(4)); + assertThat(identities.keySet(), containsInAnyOrder(ownIdentities.get(0), ownIdentities.get(1), ownIdentities.get(2), ownIdentities.get(3))); + verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(0), createTrustedIdentitiesForFirstOwnIdentity()); + verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(1), createTrustedIdentitiesForSecondOwnIdentity()); + verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(2), Collections.emptySet()); + verifyIdentitiesForOwnIdentity(identities, ownIdentities.get(3), createTrustedIdentitiesForFourthOwnIdentity()); + } + + @Test + public void loadingIdentitiesWithoutContext() throws PluginException { + List ownIdentities = createOwnIdentities(); + Map> identities = identityLoaderWithoutContext.loadIdentities(); + verify(webOfTrustConnector).loadAllOwnIdentities(); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(0)), eq(Optional.absent())); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(1)), eq(Optional.absent())); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(2)), eq(Optional.absent())); + verify(webOfTrustConnector).loadTrustedIdentities(eq(ownIdentities.get(3)), eq(Optional.absent())); + assertThat(identities.keySet(), hasSize(4)); + OwnIdentity firstOwnIdentity = ownIdentities.get(0); + OwnIdentity secondOwnIdentity = ownIdentities.get(1); + OwnIdentity thirdOwnIdentity = ownIdentities.get(2); + OwnIdentity fourthOwnIdentity = ownIdentities.get(3); + assertThat(identities.keySet(), containsInAnyOrder(firstOwnIdentity, secondOwnIdentity, thirdOwnIdentity, fourthOwnIdentity)); + verifyIdentitiesForOwnIdentity(identities, firstOwnIdentity, createTrustedIdentitiesForFirstOwnIdentity()); + verifyIdentitiesForOwnIdentity(identities, secondOwnIdentity, createTrustedIdentitiesForSecondOwnIdentity()); + verifyIdentitiesForOwnIdentity(identities, thirdOwnIdentity, createTrustedIdentitiesForThirdOwnIdentity()); + verifyIdentitiesForOwnIdentity(identities, fourthOwnIdentity, createTrustedIdentitiesForFourthOwnIdentity()); + } + + private void verifyIdentitiesForOwnIdentity(Map> identities, OwnIdentity ownIdentity, Set trustedIdentities) { + assertThat(identities.get(ownIdentity), Matchers.>is(trustedIdentities)); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityManagerTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityManagerTest.java new file mode 100644 index 0000000..b37a983 --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/IdentityManagerTest.java @@ -0,0 +1,48 @@ +package net.pterodactylus.freenet.wot; + +import static com.google.common.base.Optional.of; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import net.pterodactylus.freenet.plugin.PluginException; + +import com.google.common.eventbus.EventBus; +import org.junit.Test; + +/** + * Unit test for {@link IdentityManagerImpl}. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityManagerTest { + + private final EventBus eventBus = mock(EventBus.class); + private final WebOfTrustConnector webOfTrustConnector = mock(WebOfTrustConnector.class); + private final IdentityManager identityManager = new IdentityManagerImpl(eventBus, webOfTrustConnector, new IdentityLoader(webOfTrustConnector, of(new Context("Test")))); + + @Test + public void identityManagerPingsWotConnector() throws PluginException { + assertThat(identityManager.isConnected(), is(true)); + verify(webOfTrustConnector).ping(); + } + + @Test + public void disconnectedWotConnectorIsRecognized() throws PluginException { + doThrow(PluginException.class).when(webOfTrustConnector).ping(); + assertThat(identityManager.isConnected(), is(false)); + verify(webOfTrustConnector).ping(); + } + + @Test + public void serviceNameContainsImportantKeywords() { + assertThat(identityManager.toString(), containsString("Freenet")); + assertThat(identityManager.toString(), containsString("WoT")); + assertThat(identityManager.toString(), containsString("Identity")); + assertThat(identityManager.toString(), containsString("Manager")); + } + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/event/IdentityEventTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/event/IdentityEventTest.java new file mode 100644 index 0000000..e95cc26 --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/event/IdentityEventTest.java @@ -0,0 +1,54 @@ +package net.pterodactylus.freenet.wot.event; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; + +import net.pterodactylus.freenet.wot.Identity; +import net.pterodactylus.freenet.wot.OwnIdentity; + +import org.junit.Test; + +/** + * Unit test for {@link IdentityEvent}. + * + * @author David ‘Bombe’ Roden + */ +public class IdentityEventTest { + + private final OwnIdentity ownIdentity = mock(OwnIdentity.class); + private final Identity identity = mock(Identity.class); + private final IdentityEvent identityEvent = createIdentityEvent(ownIdentity, identity); + + private IdentityEvent createIdentityEvent(final OwnIdentity ownIdentity, final Identity identity) { + return new IdentityEvent(ownIdentity, identity) { + }; + } + + @Test + public void identityEventRetainsIdentities() { + assertThat(identityEvent.getOwnIdentity(), is(ownIdentity)); + assertThat(identityEvent.getIdentity(), is(identity)); + } + + @Test + public void eventsWithTheSameIdentityHaveTheSameHashCode() { + IdentityEvent secondIdentityEvent = createIdentityEvent(ownIdentity, identity); + assertThat(identityEvent.hashCode(), is(secondIdentityEvent.hashCode())); + } + + @Test + public void eventsWithTheSameIdentitiesAreEqual() { + IdentityEvent secondIdentityEvent = createIdentityEvent(ownIdentity, identity); + assertThat(identityEvent, is(secondIdentityEvent)); + assertThat(secondIdentityEvent, is(identityEvent)); + } + + @Test + public void nullDoesNotEqualIdentityEvent() { + assertThat(identityEvent, not(is((Object) null))); + } + + +} diff --git a/wot/src/test/java/net/pterodactylus/freenet/wot/event/OwnIdentityEventTest.java b/wot/src/test/java/net/pterodactylus/freenet/wot/event/OwnIdentityEventTest.java new file mode 100644 index 0000000..5732e5e --- /dev/null +++ b/wot/src/test/java/net/pterodactylus/freenet/wot/event/OwnIdentityEventTest.java @@ -0,0 +1,48 @@ +package net.pterodactylus.freenet.wot.event; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.mockito.Mockito.mock; + +import net.pterodactylus.freenet.wot.OwnIdentity; + +import org.junit.Test; + +/** + * Unit test for {@link OwnIdentityEvent}. + * + * @author David ‘Bombe’ Roden + */ +public class OwnIdentityEventTest { + + private final OwnIdentity ownIdentity = mock(OwnIdentity.class); + private final OwnIdentityEvent ownIdentityEvent = createOwnIdentityEvent(ownIdentity); + + @Test + public void eventRetainsOwnIdentity() { + assertThat(ownIdentityEvent.getOwnIdentity(), is(ownIdentity)); + } + + protected OwnIdentityEvent createOwnIdentityEvent(final OwnIdentity ownIdentity) { + return new OwnIdentityEvent(ownIdentity) { + }; + } + + @Test + public void twoOwnIdentityEventsWithTheSameIdentityHaveTheSameHashCode() { + OwnIdentityEvent secondOwnIdentityEvent = createOwnIdentityEvent(ownIdentity); + assertThat(secondOwnIdentityEvent.hashCode(), is(ownIdentityEvent.hashCode())); + } + + @Test + public void ownIdentityEventDoesNotMatchNull() { + assertThat(ownIdentityEvent, not(is((Object) null))); + } + + @Test + public void ownIdentityEventDoesNotMatchObjectWithADifferentClass() { + assertThat(ownIdentityEvent, not(is(new Object()))); + } + +} -- 2.7.4