Initial commit
diff --git a/src/main/java/com/intel/jndn/utils/Publisher.java b/src/main/java/com/intel/jndn/utils/Publisher.java
new file mode 100644
index 0000000..862f001
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/Publisher.java
@@ -0,0 +1,215 @@
+/*
+ * File channel: Publisher.java
+ *
+ * Purpose: Provide the publisher side of a pub-sub API.
+ *
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.utils;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnInterest;
+import net.named_data.jndn.OnRegisterFailed;
+import net.named_data.jndn.security.SecurityException;
+import net.named_data.jndn.transport.Transport;
+import net.named_data.jndn.util.Blob;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * This implementation requires both ends (publisher and subscriber) to be
+ * routable to each other. When this class receives data to publish (through
+ * publish()), it sends an alert to all subscribers indicating new data is
+ * available to retrieve; subscribers then retrieve this data using the channel
+ * provided in the alert packet. The flow is as follows: - Publisher listens on
+ * a given channel (e.g. /my/channel) - Subscriber also listens on a given
+ * channel (e.g. /my/channel) - Publisher receives a new Foo to publish -
+ * Publisher notifies subscribers of new data (e.g.
+ * /my/channel/ALERT/{encoded:/my/channel/foo/[timestamp]} - Subscriber
+ * retrieves data (e.g. /my/channel/foo/[timestamp])
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class Publisher implements OnInterest, OnRegisterFailed {
+
+ public static final long DEFAULT_TIMEOUT = 2000;
+ private static final Logger logger = LogManager.getLogger();
+ protected HashMap<Class, String> types = new HashMap<>();
+ protected HashMap<Name, PublishedObject> published = new HashMap<>();
+ protected Face face;
+ protected Name channel;
+ protected boolean registered = true;
+ protected long publishLifetime = -1; // in milliseconds, -1 will persist as long as this instance lives
+
+ /**
+ * Initialize a publisher on the given channel; published objects will
+ * persist as long as this instance lives.
+ *
+ * @param face
+ * @param channel
+ */
+ public Publisher(final Face face, final Name channel) {
+ this.face = face;
+ this.channel = channel;
+ try {
+ this.face.registerPrefix(this.channel, this, this, new ForwardingFlags());
+ } catch (IOException e) {
+ logger.error("Failed to send prefix registration for: " + this.channel.toUri(), e);
+ } catch (SecurityException e) {
+ logger.error("Failed to configure security correctly for registration: " + this.channel.toUri(), e);
+ }
+ }
+
+ /**
+ * Initialize a publisher on the given channel; limit the lifetime of
+ * published objects to save memory.
+ *
+ * @param face
+ * @param channel
+ * @param publishLifetime
+ */
+ public Publisher(final Face face, final Name channel, long publishLifetime) {
+ this(face, channel);
+ this.publishLifetime = publishLifetime;
+ }
+
+ /**
+ * Add a type and its alias to this publisher
+ *
+ * @param typeAlias
+ * @param type
+ */
+ public void addType(String typeAlias, Class type) {
+ types.put(type, typeAlias);
+ }
+
+
+ /**
+ * Publish the given object by sending an alert to all subscribers
+ *
+ * @param publishedObject
+ */
+ public void publish(Object publishedObject) {
+ // check if this publisher is registered on the face
+ if (!registered) {
+ logger.error("Publisher failed to register; cannot publish data to: " + channel.toUri());
+ return;
+ }
+
+ // check known types
+ if (!types.containsKey(publishedObject.getClass())) {
+ logger.error("Attempted (and failed) to publish unknown type: " + publishedObject.getClass().getName());
+ return;
+ }
+
+ // check if old published objects need to be removed
+ clearOldPublished();
+
+ // track published objects
+ Name publishedName = new Name(channel).append(types.get(publishedObject.getClass())).appendTimestamp(System.currentTimeMillis());
+ published.put(publishedName, new PublishedObject(publishedObject));
+
+ // send out alert and don't wait for response
+ Client client = new Client();
+ client.get(face, getAlert(publishedName));
+ logger.debug("Notified channel of new published object: " + publishedName.toUri());
+ }
+
+ /**
+ * Handle incoming requests for published objects; will check the published
+ * list and respond with objects that exactly match the incoming interest.
+ * TODO signing, encryption
+ *
+ * @param prefix
+ * @param interest
+ * @param transport
+ * @param registeredPrefixId
+ */
+ @Override
+ public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+ // check for old published objects
+ clearOldPublished();
+
+ // publish object if available
+ if (published.containsKey(interest.getName())) {
+ try {
+ Data data = new Data(interest.getName());
+ data.setContent(new Blob("TODO parse object"));
+ if (publishLifetime != -1) {
+ data.getMetaInfo().setFreshnessPeriod(publishLifetime);
+ }
+ // data.setSignature(...);
+ transport.send(data.wireEncode().signedBuf());
+ logger.debug("Sent data: " + interest.getName().toUri());
+ } catch (IOException e) {
+ logger.error("Failed to send data: " + interest.getName().toUri());
+ }
+ }
+ }
+
+ /**
+ * Handle registration failure; this will stop the publisher from sending
+ * notifications since the routes will not be setup to respond
+ *
+ * @param prefix
+ */
+ @Override
+ public void onRegisterFailed(Name prefix) {
+ logger.error("Failed to register publisher: " + channel.toUri());
+ registered = false;
+ }
+
+ /**
+ * Remove any published objects that have outlived their lifetime; a
+ * lifetime of -1 persists them forever.
+ */
+ public void clearOldPublished() {
+ if (publishLifetime == -1) {
+ return;
+ }
+ for (Entry<Name, PublishedObject> e : published.entrySet()) {
+ if (System.currentTimeMillis() - e.getValue().publishedOn > publishLifetime) {
+ published.remove(e.getKey());
+ logger.debug("Removing old published object: " + e.getKey().toUri());
+ }
+ }
+ }
+
+ /**
+ * Build an alert Interest to notify subscribers to retrieve data from this
+ * publisher; the Interest will have the form
+ * /this/published/channel/ALERT/[encoded channel to request]
+ *
+ * @param nameToPublish
+ * @return
+ */
+ protected Interest getAlert(Name nameToPublish) {
+ Name alertName = new Name(channel).append("ALERT").append(nameToPublish.wireEncode());
+ Interest alert = new Interest(alertName);
+ alert.setMustBeFresh(true); // interest must reach the application subscribers
+ alert.setInterestLifetimeMilliseconds(DEFAULT_TIMEOUT);
+ return alert;
+ }
+
+ /**
+ * Helper class to track published objects and their timestamps
+ */
+ private class PublishedObject {
+
+ Object publishedObject;
+ long publishedOn;
+
+ public PublishedObject(Object object) {
+ publishedObject = object;
+ }
+ }
+}