First extremely basic, kind-of-working version.
authorDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 15 Sep 2011 13:22:47 +0000 (15:22 +0200)
committerDavid ‘Bombe’ Roden <bombe@pterodactylus.net>
Thu, 15 Sep 2011 13:22:47 +0000 (15:22 +0200)
39 files changed:
pom.xml
src/main/java/net/pterodactylus/wotns/freenet/L10nFilter.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/SimpleFieldSetBuilder.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/fcp/AbstractCommand.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/fcp/Command.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/fcp/FcpException.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/plugin/ConnectorListener.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/plugin/ConnectorListenerManager.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/plugin/PluginConnector.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/plugin/PluginException.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/DefaultIdentity.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/DefaultOwnIdentity.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/Identity.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityListener.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityListenerManager.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityManager.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/OwnIdentity.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/Trust.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/WebOfTrustConnector.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/freenet/wot/WebOfTrustException.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/main/IdentityTargets.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/main/Resolver.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/main/WoTNSPlugin.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/template/IdentityAccessor.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/ui/web/AddTargetPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/ui/web/BasicPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/ui/web/IndexPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/ui/web/ManagePage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/ui/web/ResolverPage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/ui/web/WebInterface.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/web/FreenetRequest.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/web/FreenetTemplatePage.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/web/PageToadlet.java [new file with mode: 0644]
src/main/java/net/pterodactylus/wotns/web/PageToadletFactory.java [new file with mode: 0644]
src/main/resources/i18n/WoTNS.en.properties [new file with mode: 0644]
src/main/resources/templates/addTarget.html [new file with mode: 0644]
src/main/resources/templates/index.html [new file with mode: 0644]
src/main/resources/templates/manage.html [new file with mode: 0644]
src/main/resources/templates/unknown.html [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index b6d1e53..539c5ec 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -5,6 +5,11 @@
        <version>0.1-SNAPSHOT</version>
        <dependencies>
                <dependency>
        <version>0.1-SNAPSHOT</version>
        <dependencies>
                <dependency>
+                       <groupId>net.pterodactylus</groupId>
+                       <artifactId>utils</artifactId>
+                       <version>0.10.1-SNAPSHOT</version>
+               </dependency>
+               <dependency>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
                        <version>3.8.2</version>
                        <groupId>junit</groupId>
                        <artifactId>junit</artifactId>
                        <version>3.8.2</version>
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/L10nFilter.java b/src/main/java/net/pterodactylus/wotns/freenet/L10nFilter.java
new file mode 100644 (file)
index 0000000..692b572
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Sone - L10nFilter.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import net.pterodactylus.util.template.Filter;
+import net.pterodactylus.util.template.TemplateContext;
+import freenet.l10n.BaseL10n;
+
+/**
+ * {@link Filter} implementation replaces {@link String} values with their
+ * translated equivalents.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class L10nFilter implements Filter {
+
+       /** The l10n handler. */
+       private final BaseL10n l10n;
+
+       /**
+        * Creates a new L10n filter.
+        *
+        * @param l10n
+        *            The l10n handler
+        */
+       public L10nFilter(BaseL10n l10n) {
+               this.l10n = l10n;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String format(TemplateContext templateContext, Object data, Map<String, String> parameters) {
+               if (parameters.isEmpty()) {
+                       return l10n.getString(String.valueOf(data));
+               }
+               List<Object> parameterValues = new ArrayList<Object>();
+               int parameterIndex = 0;
+               while (parameters.containsKey(String.valueOf(parameterIndex))) {
+                       Object value = parameters.get(String.valueOf(parameterIndex));
+                       if (((String) value).startsWith("=")) {
+                               value = ((String) value).substring(1);
+                       } else {
+                               value = templateContext.get((String) value);
+                       }
+                       parameterValues.add(value);
+                       ++parameterIndex;
+               }
+               return new MessageFormat(l10n.getString(String.valueOf(data)), new Locale(l10n.getSelectedLanguage().shortCode)).format(parameterValues.toArray());
+       }
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/SimpleFieldSetBuilder.java b/src/main/java/net/pterodactylus/wotns/freenet/SimpleFieldSetBuilder.java
new file mode 100644 (file)
index 0000000..1472a82
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Sone - SimpleFieldSetBuilder.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet;
+
+import net.pterodactylus.util.validation.Validation;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * Helper class to construct {@link SimpleFieldSet} objects in a single call.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class SimpleFieldSetBuilder {
+
+       /** The simple field set that is being constructed. */
+       private final SimpleFieldSet simpleFieldSet;
+
+       /**
+        * Creates a new simple field set builder using a new, empty simple field
+        * set.
+        */
+       public SimpleFieldSetBuilder() {
+               this(new SimpleFieldSet(true));
+       }
+
+       /**
+        * Creates a new simple field set builder that will return the given simple
+        * field set on {@link #get()}.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to build
+        */
+       public SimpleFieldSetBuilder(SimpleFieldSet simpleFieldSet) {
+               Validation.begin().isNotNull("Simple Field Set", simpleFieldSet).check();
+               this.simpleFieldSet = simpleFieldSet;
+       }
+
+       /**
+        * Returns the constructed simple field set.
+        *
+        * @return The construct simple field set
+        */
+       public SimpleFieldSet get() {
+               return simpleFieldSet;
+       }
+
+       /**
+        * Copies the given simple field set into the simple field set being built
+        * in this builder, overwriting all previously existing values.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to copy
+        * @return This simple field set builder
+        */
+       public SimpleFieldSetBuilder put(@SuppressWarnings("hiding") SimpleFieldSet simpleFieldSet) {
+               this.simpleFieldSet.putAllOverwrite(simpleFieldSet);
+               return this;
+       }
+
+       /**
+        * Stores the given value under the given key, overwriting any previous
+        * value.
+        *
+        * @param key
+        *            The key of the value
+        * @param value
+        *            The value to store
+        * @return This simple field set builder
+        */
+       public SimpleFieldSetBuilder put(String key, String value) {
+               simpleFieldSet.putOverwrite(key, value);
+               return this;
+       }
+
+       /**
+        * Stores the given value under the given key, overwriting any previous
+        * value.
+        *
+        * @param key
+        *            The key of the value
+        * @param value
+        *            The value to store
+        * @return This simple field set builder
+        */
+       public SimpleFieldSetBuilder put(String key, int value) {
+               simpleFieldSet.put(key, value);
+               return this;
+       }
+
+       /**
+        * Stores the given value under the given key, overwriting any previous
+        * value.
+        *
+        * @param key
+        *            The key of the value
+        * @param value
+        *            The value to store
+        * @return This simple field set builder
+        */
+       public SimpleFieldSetBuilder put(String key, long value) {
+               simpleFieldSet.put(key, value);
+               return this;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/fcp/AbstractCommand.java b/src/main/java/net/pterodactylus/wotns/freenet/fcp/AbstractCommand.java
new file mode 100644 (file)
index 0000000..01b103c
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * Sone - AbstractCommand.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.fcp;
+
+import freenet.node.FSParseException;
+import freenet.support.SimpleFieldSet;
+
+/**
+ * Basic implementation of a {@link Command} with various helper methods to
+ * simplify processing of input parameters.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public abstract class AbstractCommand implements Command {
+
+       //
+       // PROTECTED METHODS
+       //
+
+       /**
+        * Returns a String value from the given simple field set.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to get the value from
+        * @param key
+        *            The key of the value
+        * @return The String value
+        * @throws FcpException
+        *             if there is no value for the given key in the simple field
+        *             set, or the value can not be converted to a String
+        */
+       protected String getString(SimpleFieldSet simpleFieldSet, String key) throws FcpException {
+               try {
+                       return simpleFieldSet.getString(key);
+               } catch (FSParseException fspe1) {
+                       throw new FcpException("Could not get parameter “" + key + "” as String.", fspe1);
+               }
+       }
+
+       /**
+        * Returns an int value from the given simple field set.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to get the value from
+        * @param key
+        *            The key of the value
+        * @return The int value
+        * @throws FcpException
+        *             if there is no value for the given key in the simple field
+        *             set, or the value can not be converted to an int
+        */
+       protected int getInt(SimpleFieldSet simpleFieldSet, String key) throws FcpException {
+               try {
+                       return simpleFieldSet.getInt(key);
+               } catch (FSParseException fspe1) {
+                       throw new FcpException("Could not get parameter “" + key + "” as int.", fspe1);
+               }
+       }
+
+       /**
+        * Returns an int value from the given simple field set, returning a default
+        * value if the value can not be found or converted.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to get the value from
+        * @param key
+        *            The key of the value
+        * @param defaultValue
+        *            The default value
+        * @return The int value
+        */
+       protected int getInt(SimpleFieldSet simpleFieldSet, String key, int defaultValue) {
+               return simpleFieldSet.getInt(key, defaultValue);
+       }
+
+       /**
+        * Returns a boolean value from the given simple field set.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to get the value from
+        * @param key
+        *            The key of the value
+        * @return The boolean value
+        * @throws FcpException
+        *             if there is no value for the given key in the simple field
+        *             set, or the value can not be converted to a boolean
+        */
+       protected boolean getBoolean(SimpleFieldSet simpleFieldSet, String key) throws FcpException {
+               try {
+                       return simpleFieldSet.getBoolean(key);
+               } catch (FSParseException fspe1) {
+                       throw new FcpException("Could not get parameter “" + key + "” as boolean.", fspe1);
+               }
+       }
+
+       /**
+        * Returns a boolean value from the given simple field set, returning a
+        * default value if the value can not be found or converted.
+        *
+        * @param simpleFieldSet
+        *            The simple field set to get the value from
+        * @param key
+        *            The key of the value
+        * @param defaultValue
+        *            The default value
+        * @return The boolean value
+        */
+       protected boolean getBoolean(SimpleFieldSet simpleFieldSet, String key, boolean defaultValue) {
+               return simpleFieldSet.getBoolean(key, defaultValue);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/fcp/Command.java b/src/main/java/net/pterodactylus/wotns/freenet/fcp/Command.java
new file mode 100644 (file)
index 0000000..4eac38c
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * Sone - Command.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.fcp;
+
+import net.pterodactylus.wotns.freenet.SimpleFieldSetBuilder;
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+/**
+ * Implementation of an FCP interface for other clients or plugins to
+ * communicate with Sone.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Command {
+
+       /**
+        * Executes the command, returning a reply that will be sent back to the
+        * requesting plugin.
+        *
+        * @param parameters
+        *            The parameters of the comand
+        * @param data
+        *            The data of the command (may be {@code null})
+        * @param accessType
+        *            The access type
+        * @return A reply to send back to the plugin
+        * @throws FcpException
+        *             if an error processing the parameters occurs
+        */
+       public Response execute(SimpleFieldSet parameters, Bucket data, AccessType accessType) throws FcpException;
+
+       /**
+        * The access type of the request.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static enum AccessType {
+
+               /** Access from another plugin. */
+               DIRECT,
+
+               /** Access via restricted FCP. */
+               RESTRICTED_FCP,
+
+               /** Access via FCP with full access. */
+               FULL_FCP,
+
+       }
+
+       /**
+        * Interface for command replies.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static class Response {
+
+               /** The message name of the reponse. */
+               private final String messageName;
+
+               /** The reply parameters. */
+               private final SimpleFieldSet replyParameters;
+
+               /** The reply data, may be {@code null}. */
+               private final byte[] data;
+
+               /** The data bucket, may be {@code null}. */
+               private final Bucket bucket;
+
+               /**
+                * Creates a new reply with the given parameters.
+                *
+                * @param messageName
+                *            The message name
+                * @param replyParameters
+                *            The reply parameters
+                */
+               public Response(String messageName, SimpleFieldSet replyParameters) {
+                       this(messageName, replyParameters, null, null);
+               }
+
+               /**
+                * Creates a new reply with the given parameters.
+                *
+                * @param messageName
+                *            The message name
+                * @param replyParameters
+                *            The reply parameters
+                * @param data
+                *            The data of the reply (may be {@code null})
+                */
+               public Response(String messageName, SimpleFieldSet replyParameters, byte[] data) {
+                       this(messageName, replyParameters, data, null);
+               }
+
+               /**
+                * Creates a new reply with the given parameters.
+                *
+                * @param messageName
+                *            The message name
+                * @param replyParameters
+                *            The reply parameters
+                * @param bucket
+                *            The bucket of the reply (may be {@code null})
+                */
+               public Response(String messageName, SimpleFieldSet replyParameters, Bucket bucket) {
+                       this(messageName, replyParameters, null, bucket);
+               }
+
+               /**
+                * Creates a new reply with the given parameters.
+                *
+                * @param messageName
+                *            The message name
+                * @param replyParameters
+                *            The reply parameters
+                * @param data
+                *            The data of the reply (may be {@code null})
+                * @param bucket
+                *            The bucket of the reply (may be {@code null})
+                */
+               private Response(String messageName, SimpleFieldSet replyParameters, byte[] data, Bucket bucket) {
+                       this.messageName = messageName;
+                       this.replyParameters = replyParameters;
+                       this.data = data;
+                       this.bucket = bucket;
+               }
+
+               /**
+                * Returns the reply parameters.
+                *
+                * @return The reply parameters
+                */
+               public SimpleFieldSet getReplyParameters() {
+                       return new SimpleFieldSetBuilder(replyParameters).put("Message", messageName).get();
+               }
+
+               /**
+                * Returns whether the reply has reply data.
+                *
+                * @see #getData()
+                * @return {@code true} if this reply has data, {@code false} otherwise
+                */
+               public boolean hasData() {
+                       return data != null;
+               }
+
+               /**
+                * Returns the data of the reply.
+                *
+                * @return The data of the reply
+                */
+               public byte[] getData() {
+                       return data;
+               }
+
+               /**
+                * Returns whether the reply has a data bucket.
+                *
+                * @see #getBucket()
+                * @return {@code true} if the reply has a data bucket, {@code false}
+                *         otherwise
+                */
+               public boolean hasBucket() {
+                       return bucket != null;
+               }
+
+               /**
+                * Returns the data bucket of the reply.
+                *
+                * @return The data bucket of the reply
+                */
+               public Bucket getBucket() {
+                       return bucket;
+               }
+
+       }
+
+       /**
+        * Response implementation that can return an error message and an optional
+        * error code.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public class ErrorResponse extends Response {
+
+               /**
+                * Creates a new error response with the given message.
+                *
+                * @param message
+                *            The error message
+                */
+               public ErrorResponse(String message) {
+                       super("Error", new SimpleFieldSetBuilder().put("ErrorMessage", message).get());
+               }
+
+               /**
+                * Creates a new error response with the given code and message.
+                *
+                * @param code
+                *            The error code
+                * @param message
+                *            The error message
+                */
+               public ErrorResponse(int code, String message) {
+                       super("Error", new SimpleFieldSetBuilder().put("ErrorMessage", message).put("ErrorCode", code).get());
+               }
+
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/fcp/FcpException.java b/src/main/java/net/pterodactylus/wotns/freenet/fcp/FcpException.java
new file mode 100644 (file)
index 0000000..1544919
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Sone - FcpException.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.fcp;
+
+/**
+ * Base exception for FCP communication.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class FcpException extends Exception {
+
+       /**
+        * Creates a new FCP exception.
+        */
+       public FcpException() {
+               super();
+       }
+
+       /**
+        * Creates a new FCP exception.
+        *
+        * @param message
+        *            The message of the exception
+        */
+       public FcpException(String message) {
+               super(message);
+       }
+
+       /**
+        * Creates a new FCP exception.
+        *
+        * @param cause
+        *            The cause of the exception
+        */
+       public FcpException(Throwable cause) {
+               super(cause);
+       }
+
+       /**
+        * Creates a new FCP exception.
+        *
+        * @param message
+        *            The message of the exception
+        * @param cause
+        *            The cause of the exception
+        */
+       public FcpException(String message, Throwable cause) {
+               super(message, cause);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/plugin/ConnectorListener.java b/src/main/java/net/pterodactylus/wotns/freenet/plugin/ConnectorListener.java
new file mode 100644 (file)
index 0000000..18b121d
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Sone - ConnectorListener.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.plugin;
+
+import java.util.EventListener;
+
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+/**
+ * Interface for objects that want to be notified if a {@link PluginConnector}
+ * receives a reply from a plugin. As a connection listener is always
+ * {@link PluginConnector#addConnectorListener(String, String, ConnectorListener)
+ * added} for a specific plugin, it will always be notified for replies from the
+ * correct plugin (unless you register the same listener for multiple
+ * plugins—which you subsequently should not do).
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface ConnectorListener extends EventListener {
+
+       /**
+        * A reply was received from the plugin this connection listener was added
+        * for.
+        *
+        * @param pluginConnector
+        *            The plugin connector that received the reply
+        * @param fields
+        *            The fields of the reply
+        * @param data
+        *            The data of the reply (may be null)
+        */
+       public void receivedReply(PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data);
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/plugin/ConnectorListenerManager.java b/src/main/java/net/pterodactylus/wotns/freenet/plugin/ConnectorListenerManager.java
new file mode 100644 (file)
index 0000000..0a0c891
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Sone - ConnectorListenerManager.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.plugin;
+
+import net.pterodactylus.util.event.AbstractListenerManager;
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+/**
+ * Manages {@link ConnectorListener}s and fire events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ConnectorListenerManager extends AbstractListenerManager<PluginConnector, ConnectorListener> {
+
+       /**
+        * Creates a new manager for {@link ConnectorListener}s.
+        *
+        * @param pluginConnector
+        *            The plugin connector that is the source for all events
+        */
+       public ConnectorListenerManager(PluginConnector pluginConnector) {
+               super(pluginConnector);
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Notifies all registered listeners that a reply from the plugin was
+        * received.
+        *
+        * @param fields
+        *            The fields of the reply
+        * @param data
+        *            The data of the reply (may be null)
+        */
+       public void fireReceivedReply(SimpleFieldSet fields, Bucket data) {
+               for (ConnectorListener connectorListener : getListeners()) {
+                       connectorListener.receivedReply(getSource(), fields, data);
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/plugin/PluginConnector.java b/src/main/java/net/pterodactylus/wotns/freenet/plugin/PluginConnector.java
new file mode 100644 (file)
index 0000000..bb466f1
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Sone - PluginConnector.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.plugin;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.pterodactylus.util.collection.Pair;
+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;
+
+/**
+ * Interface for talking to other plugins. Other plugins are identified by their
+ * name and a unique connection identifier.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PluginConnector implements FredPluginTalker {
+
+       /** The plugin respirator. */
+       private final PluginRespirator pluginRespirator;
+
+       /** Connector listener managers for all plugin connections. */
+       private final Map<Pair<String, String>, ConnectorListenerManager> connectorListenerManagers = Collections.synchronizedMap(new HashMap<Pair<String, String>, ConnectorListenerManager>());
+
+       /**
+        * Creates a new plugin connector.
+        *
+        * @param pluginRespirator
+        *            The plugin respirator
+        */
+       public PluginConnector(PluginRespirator pluginRespirator) {
+               this.pluginRespirator = pluginRespirator;
+       }
+
+       //
+       // LISTENER MANAGEMENT
+       //
+
+       /**
+        * Adds a connection listener for the given plugin connection.
+        *
+        * @param pluginName
+        *            The name of the plugin
+        * @param identifier
+        *            The identifier of the connection
+        * @param connectorListener
+        *            The listener to add
+        */
+       public void addConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) {
+               getConnectorListenerManager(pluginName, identifier).addListener(connectorListener);
+       }
+
+       /**
+        * Removes a connection listener for the given plugin connection.
+        *
+        * @param pluginName
+        *            The name of the plugin
+        * @param identifier
+        *            The identifier of the connection
+        * @param connectorListener
+        *            The listener to remove
+        */
+       public void removeConnectorListener(String pluginName, String identifier, ConnectorListener connectorListener) {
+               getConnectorListenerManager(pluginName, identifier).removeListener(connectorListener);
+       }
+
+       //
+       // 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 connection listener manager for the given plugin connection,
+        * creating a new one if none does exist yet.
+        *
+        * @param pluginName
+        *            The name of the plugin
+        * @param identifier
+        *            The identifier of the connection
+        * @return The connection listener manager
+        */
+       private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier) {
+               return getConnectorListenerManager(pluginName, identifier, true);
+       }
+
+       /**
+        * Returns the connection listener manager for the given plugin connection,
+        * optionally creating a new one if none does exist yet.
+        *
+        * @param pluginName
+        *            The name of the plugin
+        * @param identifier
+        *            The identifier of the connection
+        * @param create
+        *            {@code true} to create a new manager if there is none,
+        *            {@code false} to return {@code null} in that case
+        * @return The connection listener manager, or {@code null} if none existed
+        *         and {@code create} is {@code false}
+        */
+       private ConnectorListenerManager getConnectorListenerManager(String pluginName, String identifier, boolean create) {
+               ConnectorListenerManager connectorListenerManager = connectorListenerManagers.get(new Pair<String, String>(pluginName, identifier));
+               if (create && (connectorListenerManager == null)) {
+                       connectorListenerManager = new ConnectorListenerManager(this);
+                       connectorListenerManagers.put(new Pair<String, String>(pluginName, identifier), connectorListenerManager);
+               }
+               return connectorListenerManager;
+       }
+
+       /**
+        * 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) {
+               ConnectorListenerManager connectorListenerManager = getConnectorListenerManager(pluginName, identifier, false);
+               if (connectorListenerManager == null) {
+                       /* we don’t care about events for this plugin. */
+                       return;
+               }
+               connectorListenerManager.fireReceivedReply(params, data);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/plugin/PluginException.java b/src/main/java/net/pterodactylus/wotns/freenet/plugin/PluginException.java
new file mode 100644 (file)
index 0000000..39d1d20
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Sone - PluginException.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.plugin;
+
+import net.pterodactylus.wotns.freenet.wot.WebOfTrustException;
+
+/**
+ * Exception that signals an error when communicating with a plugin.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PluginException extends WebOfTrustException {
+
+       /**
+        * 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/src/main/java/net/pterodactylus/wotns/freenet/wot/DefaultIdentity.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/DefaultIdentity.java
new file mode 100644 (file)
index 0000000..e84ba6e
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * Sone - DefaultIdentity.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.cache.CacheException;
+import net.pterodactylus.util.cache.CacheItem;
+import net.pterodactylus.util.cache.DefaultCacheItem;
+import net.pterodactylus.util.cache.MemoryCache;
+import net.pterodactylus.util.cache.ValueRetriever;
+import net.pterodactylus.util.cache.WritableCache;
+import net.pterodactylus.util.collection.TimedMap;
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.wotns.freenet.plugin.PluginException;
+
+/**
+ * A Web of Trust identity.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DefaultIdentity implements Identity {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(DefaultIdentity.class);
+
+       /** The web of trust connector. */
+       private final WebOfTrustConnector webOfTrustConnector;
+
+       /** The ID of the identity. */
+       private final String id;
+
+       /** The nickname of the identity. */
+       private final String nickname;
+
+       /** The request URI of the identity. */
+       private final String requestUri;
+
+       /** The contexts of the identity. */
+       private final Set<String> contexts = Collections.synchronizedSet(new HashSet<String>());
+
+       /** The properties of the identity. */
+       private final Map<String, String> properties = Collections.synchronizedMap(new HashMap<String, String>());
+
+       /** Cached trust. */
+       /* synchronize on itself. */
+       private final WritableCache<OwnIdentity, Trust> trustCache = new MemoryCache<OwnIdentity, Trust>(new ValueRetriever<OwnIdentity, Trust>() {
+
+               @Override
+               @SuppressWarnings("synthetic-access")
+               public CacheItem<Trust> retrieve(OwnIdentity ownIdentity) throws CacheException {
+                       try {
+                               return new DefaultCacheItem<Trust>(webOfTrustConnector.getTrust(ownIdentity, DefaultIdentity.this));
+                       } catch (PluginException pe1) {
+                               throw new CacheException("Could not retrieve trust for OwnIdentity: " + ownIdentity, pe1);
+                       }
+               }
+
+       }, new TimedMap<OwnIdentity, CacheItem<Trust>>(60 * 60 * 1000));
+
+       /**
+        * Creates a new identity.
+        *
+        * @param webOfTrustConnector
+        *            The web of trust connector
+        * @param id
+        *            The ID of the identity
+        * @param nickname
+        *            The nickname of the identity
+        * @param requestUri
+        *            The request URI of the identity
+        */
+       public DefaultIdentity(WebOfTrustConnector webOfTrustConnector, String id, String nickname, String requestUri) {
+               this.webOfTrustConnector = webOfTrustConnector;
+               this.id = id;
+               this.nickname = nickname;
+               this.requestUri = requestUri;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getId() {
+               return id;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getNickname() {
+               return nickname;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getRequestUri() {
+               return requestUri;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Set<String> getContexts() {
+               return Collections.unmodifiableSet(contexts);
+       }
+
+       /**
+        * Sets the contexts of this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param contexts
+        *            The contexts to set
+        */
+       void setContextsPrivate(Set<String> contexts) {
+               this.contexts.clear();
+               this.contexts.addAll(contexts);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean hasContext(String context) {
+               return contexts.contains(context);
+       }
+
+       /**
+        * Adds the given context to this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param context
+        *            The context to add
+        */
+       void addContextPrivate(String context) {
+               contexts.add(context);
+       }
+
+       /**
+        * Removes the given context from this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param context
+        *            The context to remove
+        */
+       public void removeContextPrivate(String context) {
+               contexts.remove(context);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Map<String, String> getProperties() {
+               synchronized (properties) {
+                       return Collections.unmodifiableMap(properties);
+               }
+       }
+
+       /**
+        * Sets all properties of this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param properties
+        *            The new properties of this identity
+        */
+       void setPropertiesPrivate(Map<String, String> properties) {
+               synchronized (this.properties) {
+                       this.properties.clear();
+                       this.properties.putAll(properties);
+               }
+       }
+
+       /**
+        * Sets the property with the given name to the given value.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param name
+        *            The name of the property
+        * @param value
+        *            The value of the property
+        */
+       void setPropertyPrivate(String name, String value) {
+               synchronized (properties) {
+                       properties.put(name, value);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getProperty(String name) {
+               synchronized (properties) {
+                       return properties.get(name);
+               }
+       }
+
+       /**
+        * Removes the property with the given name.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param name
+        *            The name of the property to remove
+        */
+       void removePropertyPrivate(String name) {
+               synchronized (properties) {
+                       properties.remove(name);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Trust getTrust(OwnIdentity ownIdentity) {
+               try {
+                       synchronized (trustCache) {
+                               return trustCache.get(ownIdentity);
+                       }
+               } catch (CacheException ce1) {
+                       logger.log(Level.WARNING, "Could not get trust for OwnIdentity: " + ownIdentity, ce1);
+                       return null;
+               }
+       }
+
+       /**
+        * Sets the trust received for this identity by the given own identity.
+        *
+        * @param ownIdentity
+        *            The own identity that gives the trust
+        * @param trust
+        *            The trust received for this identity
+        */
+       void setTrustPrivate(OwnIdentity ownIdentity, Trust trust) {
+               synchronized (trustCache) {
+                       trustCache.put(ownIdentity, trust);
+               }
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public int hashCode() {
+               return id.hashCode();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean equals(Object object) {
+               if (!(object instanceof DefaultIdentity)) {
+                       return false;
+               }
+               DefaultIdentity identity = (DefaultIdentity) object;
+               return identity.id.equals(id);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return getClass().getSimpleName() + "[id=" + id + ",nickname=" + nickname + ",contexts=" + contexts + ",properties=" + properties + "]";
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/DefaultOwnIdentity.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/DefaultOwnIdentity.java
new file mode 100644 (file)
index 0000000..6f322b3
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * Sone - DefaultOwnIdentity.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import net.pterodactylus.util.validation.Validation;
+
+/**
+ * An own identity is an identity that the owner of the node has full control
+ * over.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class DefaultOwnIdentity extends DefaultIdentity implements OwnIdentity {
+
+       /** The identity manager. */
+       private final WebOfTrustConnector webOfTrustConnector;
+
+       /** The insert URI of the identity. */
+       private final String insertUri;
+
+       /**
+        * Creates a new own identity.
+        *
+        * @param webOfTrustConnector
+        *            The identity manager
+        * @param id
+        *            The ID of the identity
+        * @param nickname
+        *            The nickname of the identity
+        * @param requestUri
+        *            The request URI of the identity
+        * @param insertUri
+        *            The insert URI of the identity
+        */
+       public DefaultOwnIdentity(WebOfTrustConnector webOfTrustConnector, String id, String nickname, String requestUri, String insertUri) {
+               super(webOfTrustConnector, id, nickname, requestUri);
+               this.webOfTrustConnector = webOfTrustConnector;
+               this.insertUri = insertUri;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getInsertUri() {
+               return insertUri;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void addContext(String context) throws WebOfTrustException {
+               webOfTrustConnector.addContext(this, context);
+               addContextPrivate(context);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeContext(String context) throws WebOfTrustException {
+               webOfTrustConnector.removeContext(this, context);
+               removeContextPrivate(context);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setContexts(Set<String> contexts) throws WebOfTrustException {
+               for (String context : getContexts()) {
+                       if (!contexts.contains(context)) {
+                               webOfTrustConnector.removeContext(this, context);
+                       }
+               }
+               for (String context : contexts) {
+                       if (!getContexts().contains(context)) {
+                               webOfTrustConnector.addContext(this, context);
+                       }
+               }
+               setContextsPrivate(contexts);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setProperty(String name, String value) throws WebOfTrustException {
+               webOfTrustConnector.setProperty(this, name, value);
+               setPropertyPrivate(name, value);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeProperty(String name) throws WebOfTrustException {
+               webOfTrustConnector.removeProperty(this, name);
+               removePropertyPrivate(name);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setProperties(Map<String, String> properties) throws WebOfTrustException {
+               for (Entry<String, String> oldProperty : getProperties().entrySet()) {
+                       if (!properties.containsKey(oldProperty.getKey())) {
+                               webOfTrustConnector.removeProperty(this, oldProperty.getKey());
+                       } else {
+                               webOfTrustConnector.setProperty(this, oldProperty.getKey(), properties.get(oldProperty.getKey()));
+                       }
+               }
+               for (Entry<String, String> newProperty : properties.entrySet()) {
+                       if (!getProperties().containsKey(newProperty.getKey())) {
+                               webOfTrustConnector.setProperty(this, newProperty.getKey(), newProperty.getValue());
+                       }
+               }
+               setPropertiesPrivate(properties);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setTrust(Identity target, int trustValue, String comment) throws WebOfTrustException {
+               Validation.begin().isNotNull("Trust Target", target).isNotNull("Trust Comment", comment).isLessOrEqual("Trust Value", trustValue, 100).isGreaterOrEqual("Trust Value", trustValue, -100).check();
+               webOfTrustConnector.setTrust(this, target, trustValue, comment);
+               if (target instanceof DefaultIdentity) {
+                       ((DefaultIdentity) target).setTrustPrivate(this, new Trust(trustValue, trustValue, 0));
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void removeTrust(Identity target) throws WebOfTrustException {
+               Validation.begin().isNotNull("Trust Target", target).check();
+               webOfTrustConnector.removeTrust(this, target);
+               if (target instanceof DefaultIdentity) {
+                       ((DefaultIdentity) target).setTrustPrivate(this, new Trust(null, null, null));
+               }
+       }
+
+       //
+       // OBJECT METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public int hashCode() {
+               /* The hash of DefaultIdentity is fine. */
+               return super.hashCode();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean equals(Object object) {
+               /* The ID of the superclass is still enough. */
+               return super.equals(object);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/Identity.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/Identity.java
new file mode 100644 (file)
index 0000000..41e28ae
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Sone - Identity.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Interface for web of trust identities, defining all functions that can be
+ * performed on an identity. The identity is the main entry point for identity
+ * management.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface Identity {
+
+       /**
+        * Returns the ID of the identity.
+        *
+        * @return The ID of the identity
+        */
+       public String getId();
+
+       /**
+        * Returns the nickname of the identity.
+        *
+        * @return The nickname of the identity
+        */
+       public String getNickname();
+
+       /**
+        * Returns the request URI of the identity.
+        *
+        * @return The request URI of the identity
+        */
+       public String getRequestUri();
+
+       /**
+        * Returns all contexts of this identity.
+        *
+        * @return All contexts of this identity
+        */
+       public Set<String> 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
+        */
+       public boolean hasContext(String context);
+
+       /**
+        * Returns all properties of this identity.
+        *
+        * @return All properties of this identity
+        */
+       public Map<String, String> getProperties();
+
+       /**
+        * Returns the value of the property with the given name.
+        *
+        * @param name
+        *            The name of the property
+        * @return The value of the property
+        */
+       public String getProperty(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
+        */
+       public Trust getTrust(OwnIdentity ownIdentity);
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityListener.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityListener.java
new file mode 100644 (file)
index 0000000..afe788e
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Sone - IdentityListener.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.EventListener;
+
+/**
+ * Listener interface for {@link IdentityManager} events.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface IdentityListener extends EventListener {
+
+       /**
+        * Notifies a listener that an {@link OwnIdentity} that was not known on the
+        * previous check is available.
+        *
+        * @param ownIdentity
+        *            The new own identity
+        */
+       public void ownIdentityAdded(OwnIdentity ownIdentity);
+
+       /**
+        * Notifies a listener that an {@link OwnIdentity} that was available during
+        * the last check has gone away.
+        *
+        * @param ownIdentity
+        *            The disappeared own identity
+        */
+       public void ownIdentityRemoved(OwnIdentity ownIdentity);
+
+       /**
+        * Notifies a listener that a new identity was discovered.
+        *
+        * @param ownIdentity
+        *            The own identity at the root of the trust tree
+        * @param identity
+        *            The new identity
+        */
+       public void identityAdded(OwnIdentity ownIdentity, Identity identity);
+
+       /**
+        * Notifies a listener that some properties of the identity have changed.
+        *
+        * @param ownIdentity
+        *            The own identity at the root of the trust tree
+        * @param identity
+        *            The updated identity
+        */
+       public void identityUpdated(OwnIdentity ownIdentity, Identity identity);
+
+       /**
+        * Notifies a listener that an identity has gone away.
+        *
+        * @param ownIdentity
+        *            The own identity at the root of the trust tree
+        * @param identity
+        *            The disappeared identity
+        */
+       public void identityRemoved(OwnIdentity ownIdentity, Identity identity);
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityListenerManager.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityListenerManager.java
new file mode 100644 (file)
index 0000000..63ce4fa
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Sone - IdentityListenerManager.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import net.pterodactylus.util.event.AbstractListenerManager;
+
+/**
+ * Manager for {@link IdentityListener}s.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityListenerManager extends AbstractListenerManager<IdentityManager, IdentityListener> {
+
+       /**
+        * Creates a new identity listener manager.
+        */
+       public IdentityListenerManager() {
+               super(null);
+       }
+
+       //
+       // ACTIONS
+       //
+
+       /**
+        * Notifies all listeners that an {@link OwnIdentity} that was not known on
+        * the previous check is available.
+        *
+        * @see IdentityListener#ownIdentityAdded(OwnIdentity)
+        * @param ownIdentity
+        *            The new own identity
+        */
+       public void fireOwnIdentityAdded(OwnIdentity ownIdentity) {
+               for (IdentityListener identityListener : getListeners()) {
+                       identityListener.ownIdentityAdded(ownIdentity);
+               }
+       }
+
+       /**
+        * Notifies all listeners that an {@link OwnIdentity} that was available
+        * during the last check has gone away.
+        *
+        * @see IdentityListener#ownIdentityRemoved(OwnIdentity)
+        * @param ownIdentity
+        *            The disappeared own identity
+        */
+       public void fireOwnIdentityRemoved(OwnIdentity ownIdentity) {
+               for (IdentityListener identityListener : getListeners()) {
+                       identityListener.ownIdentityRemoved(ownIdentity);
+               }
+       }
+
+       /**
+        * Notifies all listeners that a new identity was discovered.
+        *
+        * @see IdentityListener#identityAdded(OwnIdentity, Identity)
+        * @param ownIdentity
+        *            The own identity at the root of the trust tree
+        * @param identity
+        *            The new identity
+        */
+       public void fireIdentityAdded(OwnIdentity ownIdentity, Identity identity) {
+               for (IdentityListener identityListener : getListeners()) {
+                       identityListener.identityAdded(ownIdentity, identity);
+               }
+       }
+
+       /**
+        * Notifies all listeners that some properties of the identity have changed.
+        *
+        * @see IdentityListener#identityUpdated(OwnIdentity, Identity)
+        * @param ownIdentity
+        *            The own identity at the root of the trust tree
+        * @param identity
+        *            The updated identity
+        */
+       public void fireIdentityUpdated(OwnIdentity ownIdentity, Identity identity) {
+               for (IdentityListener identityListener : getListeners()) {
+                       identityListener.identityUpdated(ownIdentity, identity);
+               }
+       }
+
+       /**
+        * Notifies all listeners that an identity has gone away.
+        *
+        * @see IdentityListener#identityRemoved(OwnIdentity, Identity)
+        * @param ownIdentity
+        *            The own identity at the root of the trust tree
+        * @param identity
+        *            The disappeared identity
+        */
+       public void fireIdentityRemoved(OwnIdentity ownIdentity, Identity identity) {
+               for (IdentityListener identityListener : getListeners()) {
+                       identityListener.identityRemoved(ownIdentity, identity);
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityManager.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/IdentityManager.java
new file mode 100644 (file)
index 0000000..e9107fe
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * Sone - IdentityManager.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.collection.SetBuilder;
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.service.AbstractService;
+import net.pterodactylus.wotns.freenet.plugin.PluginException;
+
+/**
+ * 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.
+ * <p>
+ * It is also responsible for polling identities from the Web of Trust plugin
+ * and notifying registered {@link IdentityListener}s when {@link Identity}s and
+ * {@link OwnIdentity}s are discovered or disappearing.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityManager extends AbstractService {
+
+       /** Object used for synchronization. */
+       private final Object syncObject = new Object() {
+               /* inner class for better lock names. */
+       };
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(IdentityManager.class);
+
+       /** The event manager. */
+       private final IdentityListenerManager identityListenerManager = new IdentityListenerManager();
+
+       /** The Web of Trust connector. */
+       private final WebOfTrustConnector webOfTrustConnector;
+
+       /** The context to filter for. */
+       private volatile String context;
+
+       /** The currently known own identities. */
+       /* synchronize access on syncObject. */
+       private Map<String, OwnIdentity> currentOwnIdentities = new HashMap<String, OwnIdentity>();
+
+       /** The currently trusted identities. */
+       private Map<OwnIdentity, Set<Identity>> currentTrustedIdentities = new HashMap<OwnIdentity, Set<Identity>>();
+
+       /**
+        * Creates a new identity manager.
+        *
+        * @param webOfTrustConnector
+        *            The Web of Trust connector
+        */
+       public IdentityManager(WebOfTrustConnector webOfTrustConnector) {
+               super("Sone Identity Manager", false);
+               this.webOfTrustConnector = webOfTrustConnector;
+       }
+
+       //
+       // LISTENER MANAGEMENT
+       //
+
+       /**
+        * Adds a listener for identity events.
+        *
+        * @param identityListener
+        *            The listener to add
+        */
+       public void addIdentityListener(IdentityListener identityListener) {
+               identityListenerManager.addListener(identityListener);
+       }
+
+       /**
+        * Removes a listener for identity events.
+        *
+        * @param identityListener
+        *            The listener to remove
+        */
+       public void removeIdentityListener(IdentityListener identityListener) {
+               identityListenerManager.removeListener(identityListener);
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Sets the context to filter own identities and trusted identities for.
+        *
+        * @param context
+        *            The context to filter for, or {@code null} to not filter
+        */
+       public void setContext(String context) {
+               this.context = context;
+       }
+
+       /**
+        * Returns whether the Web of Trust plugin could be reached during the last
+        * try.
+        *
+        * @return {@code true} if the Web of Trust plugin is connected, {@code
+        *         false} otherwise
+        */
+       public boolean isConnected() {
+               try {
+                       webOfTrustConnector.ping();
+                       return true;
+               } catch (PluginException pe1) {
+                       /* not connected, ignore. */
+                       return false;
+               }
+       }
+
+       /**
+        * Returns the own identity with the given ID.
+        *
+        * @param id
+        *            The ID of the own identity
+        * @return The own identity, or {@code null} if there is no such identity
+        */
+       public OwnIdentity getOwnIdentity(String id) {
+               Set<OwnIdentity> allOwnIdentities = getAllOwnIdentities();
+               for (OwnIdentity ownIdentity : allOwnIdentities) {
+                       if (ownIdentity.getId().equals(id)) {
+                               return ownIdentity;
+                       }
+               }
+               return null;
+       }
+
+       /**
+        * Returns all own identities.
+        *
+        * @return All own identities
+        */
+       public Set<OwnIdentity> getAllOwnIdentities() {
+               try {
+                       Set<OwnIdentity> ownIdentities = webOfTrustConnector.loadAllOwnIdentities();
+                       Map<String, OwnIdentity> newOwnIdentities = new HashMap<String, OwnIdentity>();
+                       for (OwnIdentity ownIdentity : ownIdentities) {
+                               newOwnIdentities.put(ownIdentity.getId(), ownIdentity);
+                       }
+                       checkOwnIdentities(newOwnIdentities);
+                       return ownIdentities;
+               } catch (WebOfTrustException wote1) {
+                       logger.log(Level.WARNING, "Could not load all own identities!", wote1);
+                       return Collections.emptySet();
+               }
+       }
+
+       public Set<Identity> getTrustedIdentities(OwnIdentity ownIdentity) {
+               SetBuilder<Identity> identities = new SetBuilder<Identity>();
+               if ((context == null) || ownIdentity.getContexts().contains(context)) {
+                       identities.add(ownIdentity);
+               }
+               synchronized (syncObject) {
+                       identities.addAll(currentTrustedIdentities.get(ownIdentity));
+               }
+               return identities.get();
+       }
+
+       //
+       // SERVICE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void serviceRun() {
+               Map<OwnIdentity, Map<String, Identity>> oldIdentities = Collections.emptyMap();
+               while (!shouldStop()) {
+                       Map<OwnIdentity, Map<String, Identity>> currentIdentities = new HashMap<OwnIdentity, Map<String, Identity>>();
+                       @SuppressWarnings("hiding")
+                       Map<String, OwnIdentity> currentOwnIdentities = new HashMap<String, OwnIdentity>();
+
+                       Set<OwnIdentity> ownIdentities = null;
+                       boolean identitiesLoaded = false;
+                       try {
+                               /* get all identities with the wanted context from WoT. */
+                               ownIdentities = webOfTrustConnector.loadAllOwnIdentities();
+
+                               /* load trusted identities. */
+                               for (OwnIdentity ownIdentity : ownIdentities) {
+                                       if ((context != null) && !ownIdentity.hasContext(context)) {
+                                               continue;
+                                       }
+                                       currentOwnIdentities.put(ownIdentity.getId(), ownIdentity);
+
+                                       Set<Identity> trustedIdentities = webOfTrustConnector.loadTrustedIdentities(ownIdentity, context);
+                                       Map<String, Identity> identities = new HashMap<String, Identity>();
+                                       currentIdentities.put(ownIdentity, identities);
+                                       for (Identity identity : trustedIdentities) {
+                                               identities.put(identity.getId(), identity);
+                                       }
+                               }
+                               identitiesLoaded = true;
+                       } catch (WebOfTrustException wote1) {
+                               logger.log(Level.WARNING, "WoT has disappeared!", wote1);
+                       }
+
+                       if (identitiesLoaded) {
+
+                               /* check for changes. */
+                               checkOwnIdentities(currentOwnIdentities);
+
+                               /* now check for changes in remote identities. */
+                               for (OwnIdentity ownIdentity : currentOwnIdentities.values()) {
+
+                                       /* find new identities. */
+                                       for (Identity currentIdentity : currentIdentities.get(ownIdentity).values()) {
+                                               if (!oldIdentities.containsKey(ownIdentity) || !oldIdentities.get(ownIdentity).containsKey(currentIdentity.getId())) {
+                                                       identityListenerManager.fireIdentityAdded(ownIdentity, currentIdentity);
+                                               }
+                                       }
+
+                                       /* find removed identities. */
+                                       if (oldIdentities.containsKey(ownIdentity)) {
+                                               for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) {
+                                                       if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) {
+                                                               identityListenerManager.fireIdentityRemoved(ownIdentity, oldIdentity);
+                                                       }
+                                               }
+
+                                               /* check for changes in the contexts. */
+                                               for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) {
+                                                       if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) {
+                                                               continue;
+                                                       }
+                                                       Identity newIdentity = currentIdentities.get(ownIdentity).get(oldIdentity.getId());
+                                                       Set<String> oldContexts = oldIdentity.getContexts();
+                                                       Set<String> newContexts = newIdentity.getContexts();
+                                                       if (oldContexts.size() != newContexts.size()) {
+                                                               identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                               continue;
+                                                       }
+                                                       for (String oldContext : oldContexts) {
+                                                               if (!newContexts.contains(oldContext)) {
+                                                                       identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+
+                                               /* check for changes in the properties. */
+                                               for (Identity oldIdentity : oldIdentities.get(ownIdentity).values()) {
+                                                       if (!currentIdentities.get(ownIdentity).containsKey(oldIdentity.getId())) {
+                                                               continue;
+                                                       }
+                                                       Identity newIdentity = currentIdentities.get(ownIdentity).get(oldIdentity.getId());
+                                                       Map<String, String> oldProperties = oldIdentity.getProperties();
+                                                       Map<String, String> newProperties = newIdentity.getProperties();
+                                                       if (oldProperties.size() != newProperties.size()) {
+                                                               identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                               continue;
+                                                       }
+                                                       for (Entry<String, String> oldProperty : oldProperties.entrySet()) {
+                                                               if (!newProperties.containsKey(oldProperty.getKey()) || !newProperties.get(oldProperty.getKey()).equals(oldProperty.getValue())) {
+                                                                       identityListenerManager.fireIdentityUpdated(ownIdentity, newIdentity);
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               /* remember the current set of identities. */
+                               oldIdentities = currentIdentities;
+                               synchronized (syncObject) {
+                                       currentTrustedIdentities.clear();
+                                       for (Entry<OwnIdentity, Map<String, Identity>> entry : currentIdentities.entrySet()) {
+                                               Set<Identity> identities = new HashSet<Identity>();
+                                               currentTrustedIdentities.put(entry.getKey(), identities);
+                                               for (Identity identity : entry.getValue().values()) {
+                                                       identities.add(identity);
+                                               }
+                                       }
+                               }
+                       }
+
+                       /* wait a minute before checking again. */
+                       sleep(60 * 1000);
+               }
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       /**
+        * Checks the given new list of own identities for added or removed own
+        * identities, as compared to {@link #currentOwnIdentities}.
+        *
+        * @param newOwnIdentities
+        *            The new own identities
+        */
+       private void checkOwnIdentities(Map<String, OwnIdentity> newOwnIdentities) {
+               synchronized (syncObject) {
+
+                       /* find removed own identities: */
+                       for (OwnIdentity oldOwnIdentity : currentOwnIdentities.values()) {
+                               if (!newOwnIdentities.containsKey(oldOwnIdentity.getId())) {
+                                       identityListenerManager.fireOwnIdentityRemoved(oldOwnIdentity);
+                               }
+                       }
+
+                       /* find added own identities. */
+                       for (OwnIdentity currentOwnIdentity : newOwnIdentities.values()) {
+                               if (!currentOwnIdentities.containsKey(currentOwnIdentity.getId())) {
+                                       identityListenerManager.fireOwnIdentityAdded(currentOwnIdentity);
+                               }
+                       }
+
+                       currentOwnIdentities.clear();
+                       currentOwnIdentities.putAll(newOwnIdentities);
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/OwnIdentity.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/OwnIdentity.java
new file mode 100644 (file)
index 0000000..b8d4d15
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Sone - OwnIdentity.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Defines a local identity, an own identity.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public interface OwnIdentity extends Identity {
+
+       /**
+        * Returns the insert URI of the identity.
+        *
+        * @return The insert URI of the identity
+        */
+       public String getInsertUri();
+
+       /**
+        * Adds the given context to this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param context
+        *            The context to add
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void addContext(String context) throws WebOfTrustException;
+
+       /**
+        * Sets all contexts of this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param contexts
+        *            All contexts of the identity
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void setContexts(Set<String> contexts) throws WebOfTrustException;
+
+       /**
+        * Removes the given context from this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param context
+        *            The context to remove
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void removeContext(String context) throws WebOfTrustException;
+
+       /**
+        * 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
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void setProperty(String name, String value) throws WebOfTrustException;
+
+       /**
+        * Sets all properties of this identity.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param properties
+        *            The new properties of this identity
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void setProperties(Map<String, String> properties) throws WebOfTrustException;
+
+       /**
+        * Removes the property with the given name.
+        * <p>
+        * This method is only called by the {@link IdentityManager}.
+        *
+        * @param name
+        *            The name of the property to remove
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void removeProperty(String name) throws WebOfTrustException;
+
+       /**
+        * Sets the trust for the given target identity.
+        *
+        * @param target
+        *            The target to set the trust for
+        * @param trustValue
+        *            The new trust value (from {@code -100} or {@code 100})
+        * @param comment
+        *            The comment for the trust assignment
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void setTrust(Identity target, int trustValue, String comment) throws WebOfTrustException;
+
+       /**
+        * Removes any trust assignment for the given target identity.
+        *
+        * @param target
+        *            The targe to remove the trust assignment for
+        * @throws WebOfTrustException
+        *             if an error occurs
+        */
+       public void removeTrust(Identity target) throws WebOfTrustException;
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/Trust.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/Trust.java
new file mode 100644 (file)
index 0000000..a8d7fc6
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Sone - Trust.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+/**
+ * Container class for trust in the web of trust.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class Trust {
+
+       /** Explicitely assigned trust. */
+       private final Integer explicit;
+
+       /** Implicitely calculated trust. */
+       private final Integer implicit;
+
+       /** The distance from the owner of the trust tree. */
+       private final Integer distance;
+
+       /**
+        * Creates a new trust container.
+        *
+        * @param explicit
+        *            The explicit trust
+        * @param implicit
+        *            The implicit trust
+        * @param distance
+        *            The 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;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return getClass().getName() + "[explicit=" + explicit + ",implicit=" + implicit + ",distance=" + distance + "]";
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/WebOfTrustConnector.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/WebOfTrustConnector.java
new file mode 100644 (file)
index 0000000..8a16efc
--- /dev/null
@@ -0,0 +1,553 @@
+/*
+ * Sone - WebOfTrustConnector.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.number.Numbers;
+import net.pterodactylus.wotns.freenet.plugin.ConnectorListener;
+import net.pterodactylus.wotns.freenet.plugin.PluginConnector;
+import net.pterodactylus.wotns.freenet.plugin.PluginException;
+import freenet.support.SimpleFieldSet;
+import freenet.support.api.Bucket;
+
+/**
+ * Connector for the Web of Trust plugin.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class WebOfTrustConnector implements ConnectorListener {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(WebOfTrustConnector.class);
+
+       /** The name of the WoT plugin. */
+       private static final String WOT_PLUGIN_NAME = "plugins.WebOfTrust.WebOfTrust";
+
+       /** A random connection identifier. */
+       private static final String PLUGIN_CONNECTION_IDENTIFIER = "WoTNS-WoT-Connector-" + Math.abs(Math.random());
+
+       /** The current reply. */
+       private Reply reply;
+
+       /** The plugin connector. */
+       private final PluginConnector pluginConnector;
+
+       /**
+        * Creates a new Web of Trust connector that uses the given plugin
+        * connector.
+        *
+        * @param pluginConnector
+        *            The plugin connector
+        */
+       public WebOfTrustConnector(PluginConnector pluginConnector) {
+               this.pluginConnector = pluginConnector;
+               pluginConnector.addConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
+       }
+
+       //
+       // ACTIONS
+       //
+
+       public void stop() {
+               pluginConnector.removeConnectorListener(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, this);
+       }
+
+       /**
+        * Loads all own identities from the Web of Trust plugin.
+        *
+        * @return All own identity
+        * @throws WebOfTrustException
+        *             if the own identities can not be loaded
+        */
+       public Set<OwnIdentity> loadAllOwnIdentities() throws WebOfTrustException {
+               @SuppressWarnings("hiding")
+               Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetOwnIdentities").get());
+               SimpleFieldSet fields = reply.getFields();
+               int ownIdentityCounter = -1;
+               Set<OwnIdentity> ownIdentities = new HashSet<OwnIdentity>();
+               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(this, id, nickname, requestUri, insertUri);
+                       ownIdentity.setContextsPrivate(parseContexts("Contexts" + ownIdentityCounter + ".", fields));
+                       ownIdentity.setPropertiesPrivate(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<Identity> 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<Identity> loadTrustedIdentities(OwnIdentity ownIdentity, String context) throws PluginException {
+               @SuppressWarnings("hiding")
+               Reply reply = performRequest(SimpleFieldSetConstructor.create().put("Message", "GetIdentitiesByScore").put("TreeOwner", ownIdentity.getId()).put("Selection", "+").put("Context", (context == null) ? "" : context).get());
+               SimpleFieldSet fields = reply.getFields();
+               Set<Identity> identities = new HashSet<Identity>();
+               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(this, id, nickname, requestUri);
+                       identity.setContextsPrivate(parseContexts("Contexts" + identityCounter + ".", fields));
+                       identity.setPropertiesPrivate(parseProperties("Properties" + identityCounter + ".", fields));
+                       identity.setTrustPrivate(ownIdentity, new Trust(Numbers.safeParseInteger(fields.get("Trust" + identityCounter)), Numbers.safeParseInteger(fields.get("Score" + identityCounter)), Numbers.safeParseInteger(fields.get("Rank" + identityCounter))));
+                       identities.add(identity);
+               }
+               return identities;
+       }
+
+       /**
+        * 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 {
+               @SuppressWarnings("hiding")
+               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 WebOfTrustException {
+               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 Set<String> parseContexts(String prefix, SimpleFieldSet fields) {
+               Set<String> contexts = new HashSet<String>();
+               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 Map<String, String> parseProperties(String prefix, SimpleFieldSet fields) {
+               Map<String, String> properties = new HashMap<String, String>();
+               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 synchronized Reply performRequest(SimpleFieldSet fields, Bucket data) throws PluginException {
+               reply = new Reply();
+               logger.log(Level.FINE, "Sending FCP Request: " + fields.get("Message"));
+               synchronized (reply) {
+                       pluginConnector.sendRequest(WOT_PLUGIN_NAME, PLUGIN_CONNECTION_IDENTIFIER, fields, data);
+                       try {
+                               reply.wait();
+                       } catch (InterruptedException ie1) {
+                               logger.log(Level.WARNING, "Got interrupted while waiting for reply on " + fields.get("Message") + ".", ie1);
+                       }
+               }
+               logger.log(Level.FINEST, "Received FCP Response for %s: %s", new Object[] { 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;
+       }
+
+       //
+       // INTERFACE ConnectorListener
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void receivedReply(@SuppressWarnings("hiding") PluginConnector pluginConnector, SimpleFieldSet fields, Bucket data) {
+               String messageName = fields.get("Message");
+               logger.log(Level.FINEST, "Received Reply from Plugin: " + messageName);
+               synchronized (reply) {
+                       reply.setFields(fields);
+                       reply.setData(data);
+                       reply.notify();
+               }
+       }
+
+       /**
+        * Container for the data of the reply from a plugin.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       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 <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       private static class SimpleFieldSetConstructor {
+
+               /** The field set being created. */
+               private 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) {
+                       SimpleFieldSetConstructor simpleFieldSetConstructor = new SimpleFieldSetConstructor(shortLived);
+                       return simpleFieldSetConstructor;
+               }
+
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/freenet/wot/WebOfTrustException.java b/src/main/java/net/pterodactylus/wotns/freenet/wot/WebOfTrustException.java
new file mode 100644 (file)
index 0000000..345ea6a
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Sone - WebOfTrustException.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.freenet.wot;
+
+/**
+ * Exception that signals an error processing web of trust identities, mostly
+ * when communicating with the web of trust plugin.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+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/src/main/java/net/pterodactylus/wotns/main/IdentityTargets.java b/src/main/java/net/pterodactylus/wotns/main/IdentityTargets.java
new file mode 100644 (file)
index 0000000..2eacd08
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * WoTNS - IdentityTargets.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.main;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import net.pterodactylus.wotns.freenet.wot.Identity;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityTargets implements Iterable<Entry<String, String>> {
+
+       private final Identity identity;
+
+       private final Map<String, String> targets = new HashMap<String, String>();
+
+       public IdentityTargets(Identity identity) {
+               this.identity = identity;
+       }
+
+       public Map<String, String> getTargets() {
+               scanForTargets();
+               return Collections.unmodifiableMap(targets);
+       }
+
+       public String getTarget(String name) {
+               scanForTargets();
+               return targets.get(name);
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       private void scanForTargets() {
+               synchronized (targets) {
+                       for (Entry<String, String> property : identity.getProperties().entrySet()) {
+                               if (property.getKey().startsWith("tns.")) {
+                                       targets.put(property.getKey().substring(4), property.getValue());
+                               }
+                       }
+               }
+       }
+
+       //
+       // ITERABLE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Iterator<Entry<String, String>> iterator() {
+               return targets.entrySet().iterator();
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/main/Resolver.java b/src/main/java/net/pterodactylus/wotns/main/Resolver.java
new file mode 100644 (file)
index 0000000..4dacad9
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * WoTNS - Resolver.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.main;
+
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+
+import net.pterodactylus.util.collection.Default;
+import net.pterodactylus.wotns.freenet.wot.Identity;
+import net.pterodactylus.wotns.freenet.wot.IdentityManager;
+import net.pterodactylus.wotns.freenet.wot.OwnIdentity;
+import net.pterodactylus.wotns.freenet.wot.Trust;
+import freenet.keys.FreenetURI;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class Resolver {
+
+       private final IdentityManager identityManager;
+
+       private OwnIdentity ownIdentity;
+
+       public Resolver(IdentityManager identityManager) {
+               this.identityManager = identityManager;
+       }
+
+       public void setOwnIdentity(OwnIdentity ownIdentity) {
+               this.ownIdentity = ownIdentity;
+       }
+
+       //
+       // ACTIONS
+       //
+
+       public FreenetURI resolveURI(String shortUri) throws MalformedURLException {
+               int firstSlash = shortUri.indexOf('/');
+               if (firstSlash == -1) {
+                       throw new MalformedURLException("At least one slash is required.");
+               }
+               String shortName = shortUri.substring(0, firstSlash);
+               String target = shortUri.substring(firstSlash + 1);
+               Identity identity = locateIdentity(shortName);
+               System.out.println("located identity: " + identity);
+               if (identity == null) {
+                       return null;
+               }
+               return new FreenetURI(identity.getProperty("tns." + target));
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       private Identity locateIdentity(String shortName) {
+               int atSign = shortName.indexOf('@');
+               String identityName = shortName;
+               String keyStart = "";
+               if (atSign > -1) {
+                       identityName = shortName.substring(0, atSign);
+                       keyStart = shortName.substring(atSign + 1);
+               }
+               @SuppressWarnings("hiding")
+               final OwnIdentity ownIdentity;
+               if (this.ownIdentity == null) {
+                       Set<OwnIdentity> ownIdentities = identityManager.getAllOwnIdentities();
+                       if (!ownIdentities.isEmpty()) {
+                               ownIdentity = ownIdentities.iterator().next();
+                       } else {
+                               ownIdentity = null;
+                       }
+               } else {
+                       ownIdentity = this.ownIdentity;
+               }
+               if (ownIdentity == null) {
+                       return null;
+               }
+               System.out.println("using own identity " + ownIdentity + " to resolve " + shortName);
+               Set<Identity> trustedIdentities = Default.forNull(identityManager.getTrustedIdentities(ownIdentity), Collections.<Identity>emptySet());
+               List<Identity> matchingIdentities = new ArrayList<Identity>();
+               System.out.println("checking " + trustedIdentities);
+               for (Identity identity : trustedIdentities) {
+                       if (identity.getNickname().equals(identityName) && identity.getId().startsWith(keyStart)) {
+                               matchingIdentities.add(identity);
+                       }
+               }
+               if (matchingIdentities.isEmpty()) {
+                       return null;
+               }
+               Collections.sort(matchingIdentities, new Comparator<Identity>() {
+
+                       @Override
+                       public int compare(Identity leftIdentity, Identity rightIdentity) {
+                               Trust leftTrust = leftIdentity.getTrust(ownIdentity);
+                               Trust rightTrust = rightIdentity.getTrust(ownIdentity);
+                               int leftTrustCombined = ((leftTrust.getExplicit() != null) ? leftTrust.getExplicit() : 0) + ((leftTrust.getImplicit() != null) ? leftTrust.getImplicit() : 0);
+                               int rightTrustCombined = ((rightTrust.getExplicit() != null) ? rightTrust.getExplicit() : 0) + ((rightTrust.getImplicit() != null) ? rightTrust.getImplicit() : 0);
+                               return leftTrustCombined - rightTrustCombined;
+                       }
+               });
+               return matchingIdentities.get(0);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/main/WoTNSPlugin.java b/src/main/java/net/pterodactylus/wotns/main/WoTNSPlugin.java
new file mode 100644 (file)
index 0000000..f3ec6d4
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * WoTNS - WoTNSPlugin.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.main;
+
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.version.Version;
+import net.pterodactylus.wotns.freenet.plugin.PluginConnector;
+import net.pterodactylus.wotns.freenet.wot.IdentityManager;
+import net.pterodactylus.wotns.freenet.wot.OwnIdentity;
+import net.pterodactylus.wotns.freenet.wot.WebOfTrustConnector;
+import net.pterodactylus.wotns.ui.web.WebInterface;
+import freenet.client.HighLevelSimpleClient;
+import freenet.clients.http.ToadletContainer;
+import freenet.l10n.PluginL10n;
+import freenet.l10n.BaseL10n.LANGUAGE;
+import freenet.pluginmanager.FredPlugin;
+import freenet.pluginmanager.FredPluginBaseL10n;
+import freenet.pluginmanager.FredPluginL10n;
+import freenet.pluginmanager.FredPluginThreadless;
+import freenet.pluginmanager.FredPluginVersioned;
+import freenet.pluginmanager.PluginRespirator;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class WoTNSPlugin implements FredPlugin, FredPluginL10n, FredPluginBaseL10n, FredPluginVersioned, FredPluginThreadless {
+
+       static {
+               Logging.setup("WoTNS");
+               Logging.setupConsoleLogging();
+       }
+
+       private static final Version VERSION = new Version(0, 1);
+
+       private PluginRespirator pluginRespirator;
+
+       private PluginL10n l10n;
+
+       private WebInterface webInterface;
+
+       private Resolver resolver;
+
+       private WebOfTrustConnector webOfTrustConnector;
+
+       private IdentityManager identityManager;
+
+       //
+       // ACCESSORS
+       //
+
+       public HighLevelSimpleClient getHighLevelSimpleClient() {
+               return pluginRespirator.getHLSimpleClient();
+       }
+
+       public ToadletContainer getToadletContainer() {
+               return pluginRespirator.getToadletContainer();
+       }
+
+       public IdentityManager getIdentityManager() {
+               return identityManager;
+       }
+
+       public Resolver getResolver() {
+               return resolver;
+       }
+
+       //
+       // FREDPLUGIN METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void runPlugin(PluginRespirator pluginRespirator) {
+               this.pluginRespirator = pluginRespirator;
+
+               PluginConnector pluginConnector = new PluginConnector(pluginRespirator);
+               webOfTrustConnector = new WebOfTrustConnector(pluginConnector);
+               identityManager = new IdentityManager(webOfTrustConnector);
+//             identityManager.setContext("WoTNS");
+               identityManager.start();
+
+               resolver = new Resolver(identityManager);
+               OwnIdentity bombeIdentity = identityManager.getOwnIdentity("e3myoFyp5avg6WYN16ImHri6J7Nj8980Fm~aQe4EX1U");
+               resolver.setOwnIdentity(bombeIdentity);
+
+               webInterface = new WebInterface(this);
+
+               webInterface.start();
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void terminate() {
+               identityManager.stop();
+               webOfTrustConnector.stop();
+               webInterface.stop();
+               Logging.shutdown();
+       }
+
+       //
+       // FREDPLUGINL10N METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getString(String key) {
+               return l10n.getBase().getString(key);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public void setLanguage(LANGUAGE newLanguage) {
+               l10n = new PluginL10n(this, newLanguage);
+       }
+
+       //
+       // FREDPLUGINBASEL10N METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getL10nFilesBasePath() {
+               return "i18n";
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getL10nFilesMask() {
+               return "WoTNS.${lang}.properties";
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getL10nOverrideFilesMask() {
+               return "WoTNS.${lang}.override.properties";
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public ClassLoader getPluginClassLoader() {
+               return WoTNSPlugin.class.getClassLoader();
+       }
+
+       //
+       // FREDPLUGINVERSIONED METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getVersion() {
+               return VERSION.toString();
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/template/IdentityAccessor.java b/src/main/java/net/pterodactylus/wotns/template/IdentityAccessor.java
new file mode 100644 (file)
index 0000000..7039ef7
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * WoTNS - IdentityAccessor.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.template;
+
+import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.wotns.freenet.wot.Identity;
+import net.pterodactylus.wotns.main.IdentityTargets;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IdentityAccessor extends ReflectionAccessor {
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Object get(TemplateContext templateContext, Object object, String member) {
+               Identity identity = (Identity) object;
+               if ("targets".equals(member)) {
+                       return new IdentityTargets(identity).getTargets();
+               }
+               return super.get(templateContext, object, member);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/ui/web/AddTargetPage.java b/src/main/java/net/pterodactylus/wotns/ui/web/AddTargetPage.java
new file mode 100644 (file)
index 0000000..fb44cb5
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * WoTNS - AddTargetPage.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.ui.web;
+
+import java.net.MalformedURLException;
+
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.web.Method;
+import net.pterodactylus.wotns.freenet.wot.OwnIdentity;
+import net.pterodactylus.wotns.freenet.wot.WebOfTrustException;
+import net.pterodactylus.wotns.web.FreenetRequest;
+import freenet.keys.FreenetURI;
+
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class AddTargetPage extends BasicPage {
+
+       /**
+        * @param webInterface
+        * @param path
+        * @param template
+        */
+       public AddTargetPage(Template template, WebInterface webInterface) {
+               super(webInterface, "addTarget.html", template);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+               if (request.getMethod() == Method.POST) {
+                       String ownIdentityId = request.getHttpRequest().getPartAsStringFailsafe("ownIdentity", 43);
+                       OwnIdentity ownIdentity = identityManager.getOwnIdentity(ownIdentityId);
+                       if (ownIdentity == null) {
+                               /* TODO - show error. */
+                               return;
+                       }
+                       String name = request.getHttpRequest().getPartAsStringFailsafe("name", 64).trim();
+                       if (name.length() == 0) {
+                               /* TODO - show error. */
+                               return;
+                       }
+                       String target = request.getHttpRequest().getPartAsStringFailsafe("target", 256).trim();
+                       if (name.length() == 0) {
+                               /* TODO - show error. */
+                               return;
+                       }
+                       try {
+                               new FreenetURI(target);
+                               ownIdentity.setProperty("tns." + name, target);
+                               throw new RedirectException("manage.html?ownIdentity=" + ownIdentityId);
+                       } catch (MalformedURLException mue1) {
+                               /* TODO - show error. */
+                       } catch (WebOfTrustException wote1) {
+                               /* TODO - show error. */
+                       }
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/ui/web/BasicPage.java b/src/main/java/net/pterodactylus/wotns/ui/web/BasicPage.java
new file mode 100644 (file)
index 0000000..ff5ffd2
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * WoTNS - BasicPage.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.ui.web;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.wotns.freenet.wot.IdentityManager;
+import net.pterodactylus.wotns.freenet.wot.OwnIdentity;
+import net.pterodactylus.wotns.web.FreenetRequest;
+import net.pterodactylus.wotns.web.FreenetTemplatePage;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class BasicPage extends FreenetTemplatePage {
+
+       protected final WebInterface webInterface;
+       protected final IdentityManager identityManager;
+
+       public BasicPage(WebInterface webInterface, String path, Template template) {
+               super(path, webInterface.getTemplateContextFactory(), template, "noPermission.html");
+               this.webInterface = webInterface;
+               this.identityManager = webInterface.getWoTNSPlugin().getIdentityManager();
+       }
+
+       //
+       // PROTECTED METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+
+               List<OwnIdentity> ownIdentities = new ArrayList<OwnIdentity>(webInterface.getWoTNSPlugin().getIdentityManager().getAllOwnIdentities());
+               Collections.sort(ownIdentities, new Comparator<OwnIdentity>() {
+
+                       @Override
+                       public int compare(OwnIdentity leftOwnIdentity, OwnIdentity rightOwnIdentity) {
+                               return leftOwnIdentity.getNickname().compareTo(rightOwnIdentity.getNickname());
+                       }
+               });
+
+               templateContext.set("ownIdentities", ownIdentities);
+               templateContext.set("formPassword", webInterface.getWoTNSPlugin().getToadletContainer().getFormPassword());
+       }
+}
diff --git a/src/main/java/net/pterodactylus/wotns/ui/web/IndexPage.java b/src/main/java/net/pterodactylus/wotns/ui/web/IndexPage.java
new file mode 100644 (file)
index 0000000..933f9a6
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * WoTNS - IndexPage.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.ui.web;
+
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.wotns.web.FreenetRequest;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class IndexPage extends BasicPage {
+
+       /**
+        * @param path
+        * @param contentType
+        * @param templateContextFactory
+        * @param template
+        */
+       public IndexPage(Template template, WebInterface webInterface) {
+               super(webInterface, "index.html", template);
+       }
+
+       //
+       // FREENETTEMPLATEPAGE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/ui/web/ManagePage.java b/src/main/java/net/pterodactylus/wotns/ui/web/ManagePage.java
new file mode 100644 (file)
index 0000000..1681fbd
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * WoTNS - ManagePage.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.ui.web;
+
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.web.Method;
+import net.pterodactylus.wotns.freenet.wot.OwnIdentity;
+import net.pterodactylus.wotns.web.FreenetRequest;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ManagePage extends BasicPage {
+
+       /**
+        * @param webInterface
+        * @param path
+        * @param template
+        */
+       public ManagePage(Template template, WebInterface webInterface) {
+               super(webInterface, "manage.html", template);
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
+               super.processTemplate(request, templateContext);
+               if (request.getMethod() == Method.POST) {
+               }
+               String ownIdentityId = request.getHttpRequest().getParam("ownIdentity");
+               OwnIdentity ownIdentity = webInterface.getWoTNSPlugin().getIdentityManager().getOwnIdentity(ownIdentityId);
+               templateContext.set("ownIdentity", ownIdentity);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/ui/web/ResolverPage.java b/src/main/java/net/pterodactylus/wotns/ui/web/ResolverPage.java
new file mode 100644 (file)
index 0000000..1a4f687
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * WoTNS - ResolverPage.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.ui.web;
+
+import java.net.MalformedURLException;
+
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.wotns.main.Resolver;
+import net.pterodactylus.wotns.web.FreenetRequest;
+import freenet.keys.FreenetURI;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ResolverPage extends BasicPage {
+
+       private final Resolver resolver;
+
+       public ResolverPage(Template unknownNameTemplate, WebInterface webInterface, Resolver resolver) {
+               super(webInterface, "", unknownNameTemplate);
+               this.resolver = resolver;
+       }
+
+       //
+       // PAGE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isPrefixPage() {
+               return true;
+       }
+
+       //
+       // FREENETTEMPLATEPAGE METHODS
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
+               String uri = request.getUri().getPath();
+               String path = uri.substring(uri.indexOf('/', 1) + 1);
+               FreenetURI targetUri;
+               try {
+                       targetUri = resolver.resolveURI(path);
+                       if (targetUri != null) {
+                               throw new RedirectException("/" + targetUri.toString());
+                       }
+               } catch (MalformedURLException mue1) {
+                       /* TODO - do something. */
+               }
+               templateContext.set("shortName", path);
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/ui/web/WebInterface.java b/src/main/java/net/pterodactylus/wotns/ui/web/WebInterface.java
new file mode 100644 (file)
index 0000000..4151531
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * WoTNS - WebInterface.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.ui.web;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import net.pterodactylus.util.template.HtmlFilter;
+import net.pterodactylus.util.template.ReflectionAccessor;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContextFactory;
+import net.pterodactylus.util.template.TemplateParser;
+import net.pterodactylus.wotns.freenet.wot.Identity;
+import net.pterodactylus.wotns.main.WoTNSPlugin;
+import net.pterodactylus.wotns.template.IdentityAccessor;
+import net.pterodactylus.wotns.web.PageToadlet;
+import net.pterodactylus.wotns.web.PageToadletFactory;
+import freenet.clients.http.ToadletContainer;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class WebInterface {
+
+       private final WoTNSPlugin wotNSPlugin;
+
+       private final TemplateContextFactory templateContextFactory = new TemplateContextFactory();
+
+       /** The registered toadlets. */
+       private final List<PageToadlet> pageToadlets = new ArrayList<PageToadlet>();
+
+       public WebInterface(WoTNSPlugin woTNSPlugin) {
+               this.wotNSPlugin = woTNSPlugin;
+
+               templateContextFactory.addAccessor(Object.class, new ReflectionAccessor());
+               templateContextFactory.addAccessor(Identity.class, new IdentityAccessor());
+               templateContextFactory.addFilter("html", new HtmlFilter());
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       public WoTNSPlugin getWoTNSPlugin() {
+               return wotNSPlugin;
+       }
+
+       public TemplateContextFactory getTemplateContextFactory() {
+               return templateContextFactory;
+       }
+
+       //
+       // ACTIONS
+       //
+
+       public void start() {
+               registerToadlets();
+       }
+
+       public void stop() {
+               unregisterToadlets();
+       }
+
+       //
+       // PRIVATE METHODS
+       //
+
+       private void registerToadlets() {
+               Template indexTemplate = TemplateParser.parse(createReader("/templates/index.html"));
+               Template unknownTemplate = TemplateParser.parse(createReader("/templates/unknown.html"));
+               Template manageTemplate = TemplateParser.parse(createReader("/templates/manage.html"));
+               Template addTargetTemplate = TemplateParser.parse(createReader("/templates/addTarget.html"));
+
+               PageToadletFactory pageToadletFactory = new PageToadletFactory(wotNSPlugin.getHighLevelSimpleClient(), "/tns/");
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new ResolverPage(unknownTemplate, this, wotNSPlugin.getResolver())));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new IndexPage(indexTemplate, this), "Index"));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new ManagePage(manageTemplate, this)));
+               pageToadlets.add(pageToadletFactory.createPageToadlet(new AddTargetPage(addTargetTemplate, this)));
+
+               ToadletContainer toadletContainer = wotNSPlugin.getToadletContainer();
+               toadletContainer.getPageMaker().addNavigationCategory("/tns/index.html", "Navigation.Menu.Name", "Navigation.Menu.Tooltip", wotNSPlugin);
+               for (PageToadlet toadlet : pageToadlets) {
+                       String menuName = toadlet.getMenuName();
+                       if (menuName != null) {
+                               toadletContainer.register(toadlet, "Navigation.Menu.Name", toadlet.path(), true, "Navigation.Menu.Item." + menuName + ".Name", "Navigation.Menu.Item." + menuName + ".Tooltip", false, toadlet);
+                       } else {
+                               toadletContainer.register(toadlet, null, toadlet.path(), true, false);
+                       }
+               }
+       }
+
+       /**
+        * Unregisters all toadlets.
+        */
+       private void unregisterToadlets() {
+               ToadletContainer toadletContainer = wotNSPlugin.getToadletContainer();
+               for (PageToadlet pageToadlet : pageToadlets) {
+                       toadletContainer.unregister(pageToadlet);
+               }
+               toadletContainer.getPageMaker().removeNavigationCategory("Navigation.Menu.Name");
+       }
+
+       /**
+        * Creates a {@link Reader} from the {@link InputStream} for the resource
+        * with the given name.
+        *
+        * @param resourceName
+        *            The name of the resource
+        * @return A {@link Reader} for the resource
+        */
+       private Reader createReader(String resourceName) {
+               try {
+                       return new InputStreamReader(getClass().getResourceAsStream(resourceName), "UTF-8");
+               } catch (UnsupportedEncodingException uee1) {
+                       return null;
+               }
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/web/FreenetRequest.java b/src/main/java/net/pterodactylus/wotns/web/FreenetRequest.java
new file mode 100644 (file)
index 0000000..bf15f5b
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Sone - FreenetRequest.java - Copyright © 2011 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.web;
+
+import java.net.URI;
+
+import net.pterodactylus.util.web.Method;
+import net.pterodactylus.util.web.Request;
+import freenet.clients.http.ToadletContext;
+import freenet.support.api.HTTPRequest;
+
+/**
+ * Encapsulates all Freenet-specific properties of a request.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class FreenetRequest extends Request {
+
+       /** The underlying HTTP request from Freenet. */
+       private final HTTPRequest httpRequest;
+
+       /** The toadlet context. */
+       private final ToadletContext toadletContext;
+
+       /**
+        * Creates a new freenet request.
+        *
+        * @param uri
+        *            The URI that is being accessed
+        * @param method
+        *            The method used to access this page
+        * @param httpRequest
+        *            The underlying HTTP request from Freenet
+        * @param toadletContext
+        *            The toadlet context
+        */
+       public FreenetRequest(URI uri, Method method, HTTPRequest httpRequest, ToadletContext toadletContext) {
+               super(uri, method);
+               this.httpRequest = httpRequest;
+               this.toadletContext = toadletContext;
+       }
+
+       //
+       // ACCESSORS
+       //
+
+       /**
+        * Returns the underlying HTTP request from Freenet.
+        *
+        * @return The underlying HTTP request from Freenet
+        */
+       public HTTPRequest getHttpRequest() {
+               return httpRequest;
+       }
+
+       /**
+        * Returns the toadlet context.
+        *
+        * @return The toadlet context
+        */
+       public ToadletContext getToadletContext() {
+               return toadletContext;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/web/FreenetTemplatePage.java b/src/main/java/net/pterodactylus/wotns/web/FreenetTemplatePage.java
new file mode 100644 (file)
index 0000000..4cc63bb
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * Sone - FreenetTemplatePage.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.web;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.pterodactylus.util.logging.Logging;
+import net.pterodactylus.util.template.Template;
+import net.pterodactylus.util.template.TemplateContext;
+import net.pterodactylus.util.template.TemplateContextFactory;
+import net.pterodactylus.util.web.Method;
+import net.pterodactylus.util.web.Page;
+import net.pterodactylus.util.web.RedirectResponse;
+import net.pterodactylus.util.web.Response;
+import freenet.clients.http.LinkEnabledCallback;
+import freenet.clients.http.PageMaker;
+import freenet.clients.http.PageNode;
+import freenet.clients.http.ToadletContext;
+import freenet.support.HTMLNode;
+
+/**
+ * Base class for all {@link Page}s that are rendered with {@link Template}s and
+ * fit into Freenet’s web interface.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class FreenetTemplatePage implements Page<FreenetRequest>, LinkEnabledCallback {
+
+       /** The logger. */
+       private static final Logger logger = Logging.getLogger(FreenetTemplatePage.class);
+
+       /** The path of the page. */
+       private final String path;
+
+       /** The template context factory. */
+       private final TemplateContextFactory templateContextFactory;
+
+       /** The template to render. */
+       private final Template template;
+
+       /** Where to redirect for invalid form passwords. */
+       private final String invalidFormPasswordRedirectTarget;
+
+       /**
+        * Creates a new template page.
+        *
+        * @param path
+        *            The path of the page
+        * @param templateContextFactory
+        *            The template context factory
+        * @param template
+        *            The template to render
+        * @param invalidFormPasswordRedirectTarget
+        *            The target to redirect to if a POST request does not contain
+        *            the correct form password
+        */
+       public FreenetTemplatePage(String path, TemplateContextFactory templateContextFactory, Template template, String invalidFormPasswordRedirectTarget) {
+               this.path = path;
+               this.templateContextFactory = templateContextFactory;
+               this.template = template;
+               this.invalidFormPasswordRedirectTarget = invalidFormPasswordRedirectTarget;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String getPath() {
+               return path;
+       }
+
+       /**
+        * Returns the title of the page.
+        *
+        * @param request
+        *            The request to serve
+        * @return The title of the page
+        */
+       protected String getPageTitle(FreenetRequest request) {
+               return null;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isPrefixPage() {
+               return false;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public Response handleRequest(FreenetRequest request, Response response) throws IOException {
+               String redirectTarget = getRedirectTarget(request);
+               if (redirectTarget != null) {
+                       return new RedirectResponse(redirectTarget);
+               }
+
+               if (isFullAccessOnly() && !request.getToadletContext().isAllowedFullAccess()) {
+                       return response.setStatusCode(401).setStatusText("Not authorized").setContentType("text/html");
+               }
+               ToadletContext toadletContext = request.getToadletContext();
+               if (request.getMethod() == Method.POST) {
+                       /* require form password. */
+                       String formPassword = request.getHttpRequest().getPartAsStringFailsafe("formPassword", 32);
+                       if (!formPassword.equals(toadletContext.getContainer().getFormPassword())) {
+                               return new RedirectResponse(invalidFormPasswordRedirectTarget);
+                       }
+               }
+               PageMaker pageMaker = toadletContext.getPageMaker();
+               PageNode pageNode = pageMaker.getPageNode(getPageTitle(request), toadletContext);
+               for (String styleSheet : getStyleSheets()) {
+                       pageNode.addCustomStyleSheet(styleSheet);
+               }
+               for (Map<String, String> linkNodeParameters : getAdditionalLinkNodes(request)) {
+                       HTMLNode linkNode = pageNode.headNode.addChild("link");
+                       for (Entry<String, String> parameter : linkNodeParameters.entrySet()) {
+                               linkNode.addAttribute(parameter.getKey(), parameter.getValue());
+                       }
+               }
+               String shortcutIcon = getShortcutIcon();
+               if (shortcutIcon != null) {
+                       pageNode.addForwardLink("icon", shortcutIcon);
+               }
+
+               TemplateContext templateContext = templateContextFactory.createTemplateContext();
+               templateContext.mergeContext(template.getInitialContext());
+               try {
+                       long start = System.nanoTime();
+                       processTemplate(request, templateContext);
+                       long finish = System.nanoTime();
+                       logger.log(Level.FINEST, "Template was rendered in " + ((finish - start) / 1000) / 1000.0 + "ms.");
+               } catch (RedirectException re1) {
+                       return new RedirectResponse(re1.getTarget());
+               }
+
+               StringWriter stringWriter = new StringWriter();
+               template.render(templateContext, stringWriter);
+               pageNode.content.addChild("%", stringWriter.toString());
+
+               postProcess(request, templateContext);
+
+               return response.setStatusCode(200).setStatusText("OK").setContentType("text/html").write(pageNode.outer.generate());
+       }
+
+       /**
+        * Can be overridden to return a custom set of style sheets that are to be
+        * included in the page’s header.
+        *
+        * @return Additional style sheets to load
+        */
+       protected Collection<String> getStyleSheets() {
+               return Collections.emptySet();
+       }
+
+       /**
+        * Returns the name of the shortcut icon to include in the page’s header.
+        *
+        * @return The URL of the shortcut icon, or {@code null} for no icon
+        */
+       protected String getShortcutIcon() {
+               return null;
+       }
+
+       /**
+        * Can be overridden when extending classes need to set variables in the
+        * template before it is rendered.
+        *
+        * @param request
+        *            The request that is rendered
+        * @param templateContext
+        *            The template context to set variables in
+        * @throws RedirectException
+        *             if the processing page wants to redirect after processing
+        */
+       protected void processTemplate(FreenetRequest request, TemplateContext templateContext) throws RedirectException {
+               /* do nothing. */
+       }
+
+       /**
+        * This method will be called after
+        * {@link #processTemplate(FreenetRequest, TemplateContext)} has processed
+        * the template and the template was rendered. This method will not be
+        * called if {@link #processTemplate(FreenetRequest, TemplateContext)}
+        * throws a {@link RedirectException}!
+        *
+        * @param request
+        *            The request being processed
+        * @param templateContext
+        *            The template context that supplied the rendered data
+        */
+       protected void postProcess(FreenetRequest request, TemplateContext templateContext) {
+               /* do nothing. */
+       }
+
+       /**
+        * Can be overridden to redirect the user to a different page, in case a log
+        * in is required, or something else is wrong.
+        *
+        * @param request
+        *            The request that is processed
+        * @return The URL to redirect to, or {@code null} to not redirect
+        */
+       protected String getRedirectTarget(FreenetRequest request) {
+               return null;
+       }
+
+       /**
+        * Returns additional &lt;link&gt; nodes for the HTML’s &lt;head&gt; node.
+        *
+        * @param request
+        *            The request for which to return the link nodes
+        * @return All link nodes that should be added to the HTML head
+        */
+       protected List<Map<String, String>> getAdditionalLinkNodes(FreenetRequest request) {
+               return Collections.emptyList();
+       }
+
+       /**
+        * Returns whether this page should only be allowed for requests from hosts
+        * with full access.
+        *
+        * @return {@code true} if this page should only be allowed for hosts with
+        *         full access, {@code false} to allow this page for any host
+        */
+       protected boolean isFullAccessOnly() {
+               return false;
+       }
+
+       //
+       // INTERFACE LinkEnabledCallback
+       //
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isEnabled(ToadletContext toadletContext) {
+               return !isFullAccessOnly();
+       }
+
+       /**
+        * Exception that can be thrown to signal that a subclassed {@link Page}
+        * wants to redirect the user during the
+        * {@link FreenetTemplatePage#processTemplate(FreenetRequest, TemplateContext)}
+        * method call.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static class RedirectException extends Exception {
+
+               /** The target to redirect to. */
+               private final String target;
+
+               /**
+                * Creates a new redirect exception.
+                *
+                * @param target
+                *            The target of the redirect
+                */
+               public RedirectException(String target) {
+                       this.target = target;
+               }
+
+               /**
+                * Returns the target to redirect to.
+                *
+                * @return The target to redirect to
+                */
+               public String getTarget() {
+                       return target;
+               }
+
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/web/PageToadlet.java b/src/main/java/net/pterodactylus/wotns/web/PageToadlet.java
new file mode 100644 (file)
index 0000000..818a27c
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * Sone - PageToadlet.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.web;
+
+import java.io.IOException;
+import java.net.URI;
+
+import net.pterodactylus.util.web.Header;
+import net.pterodactylus.util.web.Method;
+import net.pterodactylus.util.web.Page;
+import net.pterodactylus.util.web.Response;
+import freenet.client.HighLevelSimpleClient;
+import freenet.clients.http.LinkEnabledCallback;
+import freenet.clients.http.Toadlet;
+import freenet.clients.http.ToadletContext;
+import freenet.clients.http.ToadletContextClosedException;
+import freenet.support.MultiValueTable;
+import freenet.support.api.Bucket;
+import freenet.support.api.HTTPRequest;
+import freenet.support.io.Closer;
+
+/**
+ * {@link Toadlet} implementation that is wrapped around a {@link Page}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PageToadlet extends Toadlet implements LinkEnabledCallback {
+
+       /** The name of the menu item. */
+       private final String menuName;
+
+       /** The page that handles processing. */
+       private final Page<FreenetRequest> page;
+
+       /** The path prefix for the page. */
+       private final String pathPrefix;
+
+       /**
+        * Creates a new toadlet that hands off processing to a {@link Page}.
+        *
+        * @param highLevelSimpleClient
+        *            The high-level simple client
+        * @param menuName
+        *            The name of the menu item
+        * @param page
+        *            The page to handle processing
+        * @param pathPrefix
+        *            Prefix that is prepended to all {@link Page#getPath()} return
+        *            values
+        */
+       protected PageToadlet(HighLevelSimpleClient highLevelSimpleClient, String menuName, Page<FreenetRequest> page, String pathPrefix) {
+               super(highLevelSimpleClient);
+               this.menuName = menuName;
+               this.page = page;
+               this.pathPrefix = pathPrefix;
+       }
+
+       /**
+        * Returns the name to display in the menu.
+        *
+        * @return The name in the menu
+        */
+       public String getMenuName() {
+               return menuName;
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String path() {
+               return pathPrefix + page.getPath();
+       }
+
+       /**
+        * Handles a HTTP GET request.
+        *
+        * @param uri
+        *            The URI of the request
+        * @param httpRequest
+        *            The HTTP request
+        * @param toadletContext
+        *            The toadlet context
+        * @throws IOException
+        *             if an I/O error occurs
+        * @throws ToadletContextClosedException
+        *             if the toadlet context is closed
+        */
+       public void handleMethodGET(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
+               handleRequest(new FreenetRequest(uri, Method.GET, httpRequest, toadletContext));
+       }
+
+       /**
+        * Handles a HTTP POST request.
+        *
+        * @param uri
+        *            The URI of the request
+        * @param httpRequest
+        *            The HTTP request
+        * @param toadletContext
+        *            The toadlet context
+        * @throws IOException
+        *             if an I/O error occurs
+        * @throws ToadletContextClosedException
+        *             if the toadlet context is closed
+        */
+       public void handleMethodPOST(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException {
+               handleRequest(new FreenetRequest(uri, Method.POST, httpRequest, toadletContext));
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public String toString() {
+               return getClass().getName() + "[path=" + path() + ",page=" + page + "]";
+       }
+
+       /**
+        * Handles a HTTP request.
+        *
+        * @param pageRequest
+        *            The request to handle
+        * @throws IOException
+        *             if an I/O error occurs
+        * @throws ToadletContextClosedException
+        *             if the toadlet context is closed
+        */
+       private void handleRequest(FreenetRequest pageRequest) throws IOException, ToadletContextClosedException {
+               Bucket pageBucket = null;
+               try {
+                       pageBucket = pageRequest.getToadletContext().getBucketFactory().makeBucket(-1);
+                       Response pageResponse = new Response(pageBucket.getOutputStream());
+                       pageResponse = page.handleRequest(pageRequest, pageResponse);
+                       MultiValueTable<String, String> headers = new MultiValueTable<String, String>();
+                       if (pageResponse.getHeaders() != null) {
+                               for (Header header : pageResponse.getHeaders()) {
+                                       for (String value : header) {
+                                               headers.put(header.getName(), value);
+                                       }
+                               }
+                       }
+                       writeReply(pageRequest.getToadletContext(), pageResponse.getStatusCode(), pageResponse.getContentType(), pageResponse.getStatusText(), headers, pageBucket);
+               } catch (Throwable t1) {
+                       writeInternalError(t1, pageRequest.getToadletContext());
+               } finally {
+                       Closer.close(pageBucket);
+               }
+       }
+
+       /**
+        * {@inheritDoc}
+        */
+       @Override
+       public boolean isEnabled(ToadletContext toadletContext) {
+               if (page instanceof LinkEnabledCallback) {
+                       return ((LinkEnabledCallback) page).isEnabled(toadletContext);
+               }
+               return true;
+       }
+
+}
diff --git a/src/main/java/net/pterodactylus/wotns/web/PageToadletFactory.java b/src/main/java/net/pterodactylus/wotns/web/PageToadletFactory.java
new file mode 100644 (file)
index 0000000..64fdcc0
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Sone - PageToadletFactory.java - Copyright © 2010 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 <http://www.gnu.org/licenses/>.
+ */
+
+package net.pterodactylus.wotns.web;
+
+import net.pterodactylus.util.web.Page;
+import freenet.client.HighLevelSimpleClient;
+
+/**
+ * Factory that creates {@link PageToadlet}s using a given
+ * {@link HighLevelSimpleClient}.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class PageToadletFactory {
+
+       /** The client to use when creating the toadlets. */
+       private final HighLevelSimpleClient highLevelSimpleClient;
+
+       /** The prefix for all pages’ paths. */
+       private final String pathPrefix;
+
+       /**
+        * Creates a new {@link PageToadlet} factory.
+        *
+        * @param highLevelSimpleClient
+        *            The client to use when creating the toadlets
+        * @param pathPrefix
+        *            The path that is prepended to all pages’ paths
+        */
+       public PageToadletFactory(HighLevelSimpleClient highLevelSimpleClient, String pathPrefix) {
+               this.highLevelSimpleClient = highLevelSimpleClient;
+               this.pathPrefix = pathPrefix;
+       }
+
+       /**
+        * Creates a {@link PageToadlet} that wraps the given page and does not
+        * appear in the node’s menu.
+        *
+        * @param page
+        *            The page to wrap
+        * @return The toadlet wrapped around the page
+        */
+       public PageToadlet createPageToadlet(Page<FreenetRequest> page) {
+               return createPageToadlet(page, null);
+       }
+
+       /**
+        * Creates a {@link PageToadlet} that wraps the given page and appears in
+        * the node’s menu under the given name.
+        *
+        * @param page
+        *            The page to wrap
+        * @param menuName
+        *            The name of the menu item
+        * @return The toadlet wrapped around the page
+        */
+       public PageToadlet createPageToadlet(Page<FreenetRequest> page, String menuName) {
+               return new PageToadlet(highLevelSimpleClient, menuName, page, pathPrefix);
+       }
+
+}
diff --git a/src/main/resources/i18n/WoTNS.en.properties b/src/main/resources/i18n/WoTNS.en.properties
new file mode 100644 (file)
index 0000000..b034e45
--- /dev/null
@@ -0,0 +1,4 @@
+Navigation.Menu.Name=WoTNS
+Navigation.Menu.Tooltip=Configure the Web of Trust Name Service
+Navigation.Menu.Item.Index.Name=Configure
+Navigation.Menu.Item.Index.Tooltip=Configure the Web of Trust Name Service
diff --git a/src/main/resources/templates/addTarget.html b/src/main/resources/templates/addTarget.html
new file mode 100644 (file)
index 0000000..9bae233
--- /dev/null
@@ -0,0 +1,2 @@
+<h1>Add target.</h1>
+<p>An error occured.</p>
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
new file mode 100644 (file)
index 0000000..a0c8bee
--- /dev/null
@@ -0,0 +1,12 @@
+<h1>WoTNS</h1>
+<p>Yay, Index!</p>
+<h1>Manage Names</h1>
+<form method="get" action="manage.html">
+       <select name="ownIdentity">
+               <option disabled="true">Choose identity…</option>
+               <%foreach ownIdentities ownIdentity>
+                       <option value="<%ownIdentity.id|html>"><%ownIdentity.nickname|html></option>
+               <%/foreach>
+       </select>
+       <button type="submit" name="manage">Manage</button>
+</form>
diff --git a/src/main/resources/templates/manage.html b/src/main/resources/templates/manage.html
new file mode 100644 (file)
index 0000000..b0b2be6
--- /dev/null
@@ -0,0 +1,22 @@
+<h1>Manage “<%ownIdentity.nickname|html>”</h1>
+<%foreach ownIdentity.targets target>
+       <%first>
+               <h2>Existing Targets</h2>
+       <%/first>
+       <form action="editTarget.html" method="post">
+               <input type="hidden" name="formPassword" value="<%formPassword|html>"/>
+               <input type="hidden" name="name" value="<%target.key|html>"/>
+               <%target.key|html>:
+               <input type="text" name="target" value="<%target.value|html>"/>
+               <button type="submit" name="edit">Save</button>
+               <button type="submit" name="delete">Delete</button>
+       </form>
+<%/foreach>
+<h2>Add Target</h2>
+<form action="addTarget.html" method="post">
+       <input type="hidden" name="formPassword" value="<%formPassword|html>"/>
+       <input type="hidden" name="ownIdentity" value="<%ownIdentity.id|html>"/>
+       <input type="text" name="name" value=""/>
+       <input type="text" name="target" value=""/>
+       <button type="submit" name="add">Add</button>
+</form>
diff --git a/src/main/resources/templates/unknown.html b/src/main/resources/templates/unknown.html
new file mode 100644 (file)
index 0000000..6fab8b9
--- /dev/null
@@ -0,0 +1,2 @@
+<h1>Unknown name</h1>
+<p>The name “<%shortName|html>” is unknown.</p>
\ No newline at end of file