blob: 23e8cf64d39ff20f765aa1c18cdeaebe56c8a01c [file] [log] [blame]
/*
* File name: Subscriber.java
*
* Purpose: Provide the subscriber 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 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.Name.Component;
import net.named_data.jndn.OnInterest;
import net.named_data.jndn.OnRegisterFailed;
import net.named_data.jndn.encoding.EncodingException;
import net.named_data.jndn.transport.Transport;
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 Subscriber implements OnInterest, OnRegisterFailed {
public static final long DEFAULT_TIMEOUT = 2000;
private static final Logger logger = LogManager.getLogger();
protected HashMap<String, Class> types = new HashMap<>();
protected HashMap<Class, OnPublish> handlers = new HashMap<>();
protected Face face;
protected Name channel;
/**
* Initialize a publisher on the given channel; published objects will
* persist as long as this instance lives.
*
* @param face
* @param channel
*/
public Subscriber(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 (net.named_data.jndn.security.SecurityException e) {
logger.error("Failed to configure security correctly for registration: " + this.channel.toUri(), e);
}
}
/**
* Add a type and its alias to this publisher
*
* @param typeAlias
* @param type
*/
public void addType(String typeAlias, Class type) {
types.put(typeAlias, type);
}
/**
* Functional interface defining action to take upon receipt of a published
* object
*
* @param <T>
*/
public interface OnPublish<T>{
public void onPublish(T publishedObject);
}
/**
* Register a handler for receipt of a certain published type
*
* @param type
* @param handler
*/
public void on(Class type, OnPublish handler) {
handlers.put(type, handler);
}
/**
* Register a handler for receipt of a certain published type
*
* @param typeAlias
* @param handler
*/
public void on(String typeAlias, OnPublish handler) {
if (types.containsKey(typeAlias)) {
handlers.put(types.get(typeAlias), handler);
} else {
logger.warn("Unrecognized type (no handler registered): " + typeAlias);
}
}
/**
* Handle alert notifications for published objects; once the published
* object is parsed, this will pass the object to the handle registered
* in on().
*
* @param prefix
* @param interest
* @param transport
* @param registeredPrefixId
*/
@Override
public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
// check format
if (!isAlert(interest)) {
logger.warn("Incoming interest was not in ALERT format: " + interest.getName().toUri());
return;
}
// check signature
// TODO
// retrieve name
Name publishedName = getAlertName(interest);
if (publishedName == null) {
return;
}
// retrieve data
Client client = new Client();
Data data = client.getSync(face, publishedName);
if (data == null) {
logger.warn("Faled to retrieve published object: " + publishedName.toUri());
return;
}
// get handler
String typeAlias = getPublishedType(data);
if (!types.containsKey(typeAlias)) {
logger.warn("Type not found: " + typeAlias);
return;
}
Class type = types.get(typeAlias);
if (!handlers.containsKey(type)) {
logger.warn("No handler found for type: " + typeAlias);
return;
}
// build object
Object publishedObject = null;
// TODO parse object
// call
handlers.get(type).onPublish(publishedObject);
}
/**
* Handle registration failure;
*
* @param prefix
*/
@Override
public void onRegisterFailed(Name prefix) {
logger.error("Failed to register: " + prefix.toUri());
}
/**
* Check if an incoming interest is an alert notification to retrieve data
*
* @param interest
* @return
*/
protected boolean isAlert(Interest interest) {
Component alertComponent = interest.getName().get(-2);
return alertComponent == null || !alertComponent.equals(new Component("ALERT"));
}
/**
* Parse an Interest to retrieve the remote name of the published object to
* then request; the Interest must have the form
* /this/published/channel/ALERT/[encoded name to request]
*
* @param interest
* @return
*/
protected Name getAlertName(Interest interest) {
try {
Name publishedName = new Name();
publishedName.wireDecode(interest.getName().get(-1).getValue());
return publishedName;
} catch (EncodingException e) {
logger.error("Failed to parse remote published name", e);
return null;
}
}
/**
* Parse the published type from a Data packet; must be the second to last
* component of the name
*
* @param data
* @return
*/
protected String getPublishedType(Data data) {
return data.getName().get(-2).toEscapedString();
}
}