From 9ae2fe052fd8921e9e114f94efb864c90fbf356a Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Mon, 28 Oct 2019 21:19:01 +0100 Subject: [PATCH] =?utf8?q?=E2=99=BB=EF=B8=8F=20Use=20new=20plugin=20connec?= =?utf8?q?tor=20in=20wot=20connector?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../sone/freenet/wot/WebOfTrustConnector.java | 204 ++------------------- .../sone/freenet/plugin/FredPluginConnector.kt | 37 ++++ .../sone/freenet/plugin/PluginConnector.kt | 32 +--- .../sone/freenet/plugin/FredPluginConnectorTest.kt | 133 +++++--------- 4 files changed, 110 insertions(+), 296 deletions(-) create mode 100644 src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt diff --git a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java index a67ba82..a06f826 100644 --- a/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java +++ b/src/main/java/net/pterodactylus/sone/freenet/wot/WebOfTrustConnector.java @@ -17,31 +17,20 @@ package net.pterodactylus.sone.freenet.wot; -import static java.util.logging.Logger.getLogger; -import static net.pterodactylus.sone.utils.NumberParsers.parseInt; - -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.*; import java.util.logging.Logger; - +import java.util.logging.*; import javax.annotation.*; -import net.pterodactylus.sone.freenet.plugin.PluginConnector; -import net.pterodactylus.sone.freenet.plugin.PluginException; -import net.pterodactylus.sone.freenet.plugin.event.ReceivedReplyEvent; +import net.pterodactylus.sone.freenet.plugin.*; -import com.google.common.base.Optional; -import com.google.common.collect.MapMaker; -import com.google.common.eventbus.Subscribe; -import com.google.inject.Inject; -import com.google.inject.Singleton; +import com.google.inject.*; +import freenet.support.*; +import freenet.support.api.*; -import freenet.support.SimpleFieldSet; -import freenet.support.api.Bucket; +import static java.lang.String.*; +import static java.util.logging.Logger.*; +import static net.pterodactylus.sone.utils.NumberParsers.*; /** * Connector for the Web of Trust plugin. @@ -55,15 +44,9 @@ public class WebOfTrustConnector { /** The name of the WoT plugin. */ private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust"; - /** Counter for connection identifiers. */ - private final AtomicLong counter = new AtomicLong(); - /** The plugin connector. */ private final PluginConnector pluginConnector; - /** Map for replies. */ - private final Map replies = new MapMaker().makeMap(); - /** * Creates a new Web of Trust connector that uses the given plugin * connector. @@ -95,7 +78,7 @@ public class WebOfTrustConnector { * if the own identities can not be loaded */ public Set loadAllOwnIdentities() throws WebOfTrustException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get()); + PluginReply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get()); SimpleFieldSet fields = reply.getFields(); int ownIdentityCounter = -1; Set ownIdentities = new HashSet<>(); @@ -142,7 +125,7 @@ public class WebOfTrustConnector { * if an error occured talking to the Web of Trust plugin */ public Set loadTrustedIdentities(OwnIdentity ownIdentity, @Nullable String context) throws PluginException { - Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", (context ==null) ? "" : context).put("WantTrustValues", "true").get()); + PluginReply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("Truster", ownIdentity.getId()).put("Selection", "+").put("Context", (context == null) ? "" : context).put("WantTrustValues", "true").get()); SimpleFieldSet fields = reply.getFields(); Set identities = new HashSet<>(); int identityCounter = -1; @@ -205,7 +188,7 @@ public class WebOfTrustConnector { * 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()); + PluginReply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetProperty").put("Identity", identity.getId()).put("Property", name).get()); return reply.getFields().get("Property"); } @@ -252,7 +235,7 @@ public class WebOfTrustConnector { * 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()); + PluginReply 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"); @@ -376,7 +359,7 @@ public class WebOfTrustConnector { * @throws PluginException * if the request could not be sent */ - private Reply performRequest(SimpleFieldSet fields) throws PluginException { + private PluginReply performRequest(SimpleFieldSet fields) throws PluginException { return performRequest(fields, null); } @@ -392,110 +375,14 @@ public class WebOfTrustConnector { * @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"))) { + private PluginReply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException { + logger.log(Level.FINE, format("Sending FCP Request: %s", fields.get("Message"))); + PluginReply pluginReply = pluginConnector.sendRequest(WOT_PLUGIN_NAME, "", fields, data); + logger.log(Level.FINEST, format("Received FCP Response for %s: %s", fields.get("Message"), pluginReply.getFields().get("Message"))); + if ("Error".equals(pluginReply.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. - */ - 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; - } - + return pluginReply; } /** @@ -571,55 +458,4 @@ public class WebOfTrustConnector { } - /** - * Container for identifying plugins. Plugins are identified by their plugin - * name and their unique identifier. - */ - 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/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt new file mode 100644 index 0000000..4dd8604 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnector.kt @@ -0,0 +1,37 @@ +/* Fred’s plugin stuff is mostly deprecated. ¯\_(ツ)_/¯ */ +@file:Suppress("DEPRECATION") + +package net.pterodactylus.sone.freenet.plugin + +import freenet.pluginmanager.* +import freenet.support.* +import freenet.support.api.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import javax.inject.* + +/** + * [PluginConnector] implementation that uses a [PluginRespiratorFacade] and coroutines to send + * a request to another plugin and receive a reply. + */ +class FredPluginConnector @Inject constructor(private val pluginRespiratorFacade: PluginRespiratorFacade) : PluginConnector { + + override fun sendRequest(pluginName: String, identifier: String, fields: SimpleFieldSet, data: Bucket?): PluginReply { + val receivedReply = Channel() + val responseReceiver = FredPluginTalker { _, _, responseFields, responseData -> + GlobalScope.launch { + receivedReply.send(PluginReply(responseFields, responseData)) + } + } + try { + val pluginTalker = pluginRespiratorFacade.getPluginTalker(responseReceiver, pluginName, "") + pluginTalker.send(fields, data) + return runBlocking { + receivedReply.receive() + } + } catch (e: PluginNotFoundException) { + throw PluginException(e) + } + } + +} diff --git a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt index c55859b..db48dc3 100644 --- a/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt +++ b/src/main/kotlin/net/pterodactylus/sone/freenet/plugin/PluginConnector.kt @@ -17,12 +17,8 @@ package net.pterodactylus.sone.freenet.plugin -import com.google.common.eventbus.* -import com.google.inject.* -import freenet.pluginmanager.* import freenet.support.* import freenet.support.api.* -import net.pterodactylus.sone.freenet.plugin.event.* /** * Interface for talking to other plugins. Other plugins are identified by their @@ -34,34 +30,14 @@ interface PluginConnector { * Sends a message to another plugin running in the same node. * * @param pluginName The fully qualified name of the plugin - * @param identifier The unique identifier of the request * @param fields The message being sent * @param data Optional data + * @return The reply from the plugin + * @throws PluginException if the plugin identified by [pluginName] does not exist */ @Throws(PluginException::class) - fun sendRequest(pluginName: String, identifier: String, fields: SimpleFieldSet, data: Bucket? = null): Unit + fun sendRequest(pluginName: String, identifier: String, fields: SimpleFieldSet, data: Bucket? = null): PluginReply } -/** - * Fred-based [PluginConnector] implementation. - */ -class FredPluginConnector @Inject constructor( - private val eventBus: EventBus, - private val pluginRespiratorFacade: PluginRespiratorFacade -) : PluginConnector, FredPluginTalker { - - override fun sendRequest(pluginName: String, identifier: String, fields: SimpleFieldSet, data: Bucket?) = - getPluginTalker(pluginName, identifier).send(fields, data) - - private fun getPluginTalker(pluginName: String, identifier: String) = - try { - pluginRespiratorFacade.getPluginTalker(this, pluginName, identifier) - } catch (pnfe1: PluginNotFoundException) { - throw PluginException(pnfe1) - } - - override fun onReply(pluginName: String, identifier: String, params: SimpleFieldSet, data: Bucket) = - eventBus.post(ReceivedReplyEvent(this, pluginName, identifier, params, data)) - -} +data class PluginReply(val fields: SimpleFieldSet, val data: Bucket?) diff --git a/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt index 7c1775b..959f680 100644 --- a/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt +++ b/src/test/kotlin/net/pterodactylus/sone/freenet/plugin/FredPluginConnectorTest.kt @@ -1,112 +1,77 @@ -/* EventBus and Subscribe are marked @Beta, ignore that. And Fred stuff is - * often marked as deprecated even though there is no replacement. */ -@file:Suppress("UnstableApiUsage", "DEPRECATION") +/** + * Sone - FredPluginConnectorTest.kt - Copyright © 2019 David ‘Bombe’ 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 . + */ + +/* Fred-based plugin stuff is mostly deprecated. ¯\_(ツ)_/¯ */ +@file:Suppress("DEPRECATION") package net.pterodactylus.sone.freenet.plugin -import com.google.common.eventbus.* import freenet.pluginmanager.* import freenet.support.* import freenet.support.api.* import freenet.support.io.* +import kotlinx.coroutines.* import net.pterodactylus.sone.freenet.* -import net.pterodactylus.sone.freenet.plugin.event.* import org.hamcrest.MatcherAssert.* import org.hamcrest.Matchers.* import org.junit.* import org.junit.rules.* +import kotlin.concurrent.* -/** - * Unit test for [PluginConnector]. - */ -class PluginConnectorTest { +class FredPluginConnectorTest { @Rule @JvmField val expectedException = ExpectedException.none()!! - private val eventBus = EventBus() - private val pluginRespirator = object : PluginRespiratorFacade { - val call1Parameters = mutableListOf() - val call2Parameters = mutableListOf() - override fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String) = - if ("wrong" in pluginName) { - throw PluginNotFoundException() - } else { - object : PluginTalkerFacade { - override fun send(pluginParameters: SimpleFieldSet, data: Bucket?) = Unit - .also { call2Parameters += Call2Parameters(pluginParameters, data) } - }.also { call1Parameters += Call1Parameters(pluginTalker, pluginName, identifier) } - } - } - private val pluginConnector = FredPluginConnector(eventBus, pluginRespirator) - - @Test - fun `sending request calls correct method on plugin respirator`() { - pluginConnector.sendRequest("test.plugin", "test-request-1", fields) - assertThat(pluginRespirator.call1Parameters, hasSize(1)) - assertThat(pluginRespirator.call1Parameters[0].pluginTalker, sameInstance(pluginConnector)) - assertThat(pluginRespirator.call1Parameters[0].pluginName, equalTo("test.plugin")) - assertThat(pluginRespirator.call1Parameters[0].identifier, equalTo("test-request-1")) - } - - @Test - fun `sending request with bucket calls correct method on plugin respirator`() { - pluginConnector.sendRequest("test.plugin", "test-request-1", fields, data) - assertThat(pluginRespirator.call1Parameters, hasSize(1)) - assertThat(pluginRespirator.call1Parameters[0].pluginTalker, sameInstance(pluginConnector)) - assertThat(pluginRespirator.call1Parameters[0].pluginName, equalTo("test.plugin")) - assertThat(pluginRespirator.call1Parameters[0].identifier, equalTo("test-request-1")) - } - @Test - fun `sending request to incorrect plugin translates exception correctly`() { + fun `connector throws exception if plugin can not be found`() = runBlocking { + val pluginConnector = FredPluginConnector(pluginRespiratorFacade) expectedException.expect(PluginException::class.java) - pluginConnector.sendRequest("wrong.plugin", "test-request-1", fields) + pluginConnector.sendRequest("wrong.plugin", "", requestFields, requestData) + Unit } @Test - fun `sending request with bucket to incorrect plugin translates exception correctly`() { - expectedException.expect(PluginException::class.java) - pluginConnector.sendRequest("wrong.plugin", "test-request-1", fields, data) - } - - @Test - fun `sending request calls correct method on plugin talker`() { - pluginConnector.sendRequest("test.plugin", "test-request-1", fields) - assertThat(pluginRespirator.call2Parameters, hasSize(1)) - assertThat(pluginRespirator.call2Parameters[0].pluginParameters, equalTo(fields)) - assertThat(pluginRespirator.call2Parameters[0].data, nullValue()) - } - - @Test - fun `sending request with bucket calls correct method on plugin talker`() { - pluginConnector.sendRequest("test.plugin", "test-request-1", fields, data) - assertThat(pluginRespirator.call2Parameters, hasSize(1)) - assertThat(pluginRespirator.call2Parameters[0].pluginParameters, equalTo(fields)) - assertThat(pluginRespirator.call2Parameters[0].data, equalTo(data)) - } - - @Test - fun `reply is sent to event bus correctly`() { - val listener = object { - val receivedReplyEvents = mutableListOf() - @Subscribe - fun onReply(receivedReplyEvent: ReceivedReplyEvent) = Unit.also { receivedReplyEvents += receivedReplyEvent } - } - eventBus.register(listener) - pluginConnector.onReply("test.plugin", "test-request-1", fields, data) - assertThat(listener.receivedReplyEvents, hasSize(1)) - assertThat(listener.receivedReplyEvents[0].pluginName(), equalTo("test.plugin")) - assertThat(listener.receivedReplyEvents[0].identifier(), equalTo("test-request-1")) - assertThat(listener.receivedReplyEvents[0].fieldSet(), equalTo(fields)) - assertThat(listener.receivedReplyEvents[0].data(), equalTo(data)) + fun `connector returns correct fields and data`() = runBlocking { + val pluginConnector = FredPluginConnector(pluginRespiratorFacade) + val reply = pluginConnector.sendRequest("test.plugin", "", requestFields, requestData) + assertThat(reply.fields, equalTo(responseFields)) + assertThat(reply.data, equalTo(responseData)) } } -private val fields = SimpleFieldSetBuilder().put("foo", "bar").get() -private val data = ArrayBucket(byteArrayOf(1, 2)) +private val requestFields = SimpleFieldSetBuilder().put("foo", "bar").get() +private val requestData: Bucket? = ArrayBucket(byteArrayOf(1, 2)) +private val responseFields = SimpleFieldSetBuilder().put("baz", "quo").get() +private val responseData: Bucket? = ArrayBucket(byteArrayOf(3, 4)) -private data class Call1Parameters(val pluginTalker: FredPluginTalker, val pluginName: String, val identifier: String) -private data class Call2Parameters(val pluginParameters: SimpleFieldSet, val data: Bucket?) +private val pluginRespiratorFacade = object : PluginRespiratorFacade { + override fun getPluginTalker(pluginTalker: FredPluginTalker, pluginName: String, identifier: String) = + if (pluginName == "test.plugin") { + object : PluginTalkerFacade { + override fun send(pluginParameters: SimpleFieldSet, data: Bucket?) { + if ((pluginParameters == requestFields) && (data == requestData)) { + thread { pluginTalker.onReply(pluginName, identifier, responseFields, responseData) } + } + } + } + } else { + throw PluginNotFoundException() + } +} -- 2.7.4