Upgrade API: use base interface, return vanilla Future<Data>, throw exceptions instead of returning null, and improve underlying implementation/tests
diff --git a/src/main/java/com/intel/jndn/utils/Client.java b/src/main/java/com/intel/jndn/utils/Client.java
index cb90f6f..6b09f9f 100644
--- a/src/main/java/com/intel/jndn/utils/Client.java
+++ b/src/main/java/com/intel/jndn/utils/Client.java
@@ -14,86 +14,28 @@
package com.intel.jndn.utils;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Level;
+import java.util.concurrent.Future;
import net.named_data.jndn.Data;
import net.named_data.jndn.Face;
import net.named_data.jndn.Interest;
-import net.named_data.jndn.Name;
-import net.named_data.jndn.OnData;
-import net.named_data.jndn.OnTimeout;
-import java.util.logging.Logger;
/**
- * Provide a client to simplify information retrieval over the NDN network.
+ * Base functionality provided by all NDN clients in this package.
*
* @author Andrew Brown <andrew.brown@intel.com>
*/
-public class Client {
-
- public static final long DEFAULT_SLEEP_TIME = 20;
- public static final long DEFAULT_TIMEOUT = 2000;
- private static final Logger logger = Logger.getLogger(Client.class.getName());
- private static Client defaultInstance;
-
- /**
- * Singleton access for simpler client use
- *
- * @return
- */
- public static Client getDefault() {
- if (defaultInstance == null) {
- defaultInstance = new Client();
- }
- return defaultInstance;
- }
+public interface Client {
/**
* Asynchronously request the Data for an Interest. This will send the
* Interest and return immediately; use futureData.get() to block until the
- * Data returns (see FutureData) or manage the event processing independently.
+ * Data returns (see Future) or manage the event processing independently.
*
* @param face
* @param interest
* @return
*/
- public FutureData getAsync(Face face, Interest interest) {
- final FutureData futureData = new FutureData(face, interest.getName());
-
- // send interest
- try {
- face.expressInterest(interest, new OnData() {
- @Override
- public void onData(Interest interest, Data data) {
- futureData.resolve(data);
- }
- }, new OnTimeout() {
- @Override
- public void onTimeout(Interest interest) {
- futureData.reject(new TimeoutException());
- }
- });
- } catch (IOException e) {
- logger.log(Level.WARNING, "IO failure while sending interest: ", e);
- futureData.reject(e);
- }
-
- return futureData;
- }
-
- /**
- * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
- * second timeout); this will block until complete (i.e. either data is
- * received or the interest times out).
- *
- * @param face
- * @param name
- * @return
- */
- public FutureData getAsync(Face face, Name name) {
- return getAsync(face, getDefaultInterest(name));
- }
+ public Future<Data> getAsync(Face face, Interest interest);
/**
* Synchronously retrieve the Data for an Interest; this will block until
@@ -101,48 +43,8 @@
*
* @param face
* @param interest
- * @return Data packet or null
- */
- public Data getSync(Face face, Interest interest) {
- // setup event
- long startTime = System.currentTimeMillis();
-
- // get future data
- FutureData futureData = getAsync(face, interest);
-
- // process eventCount until a response is received or timeout
- try {
- Data data = futureData.get();
- logger.fine("Request time (ms): " + (System.currentTimeMillis() - startTime));
- return data;
- } catch (ExecutionException | InterruptedException e) {
- logger.log(Level.WARNING, "Failed to retrieve data: ", e);
- return null;
- }
- }
-
- /**
- * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
- * second timeout); this will block until complete (i.e. either data is
- * received or the interest times out).
- *
- * @param face
- * @param name
* @return
+ * @throws java.io.IOException
*/
- public Data getSync(Face face, Name name) {
- return getSync(face, getDefaultInterest(name));
- }
-
- /**
- * Create a default interest for a given Name using some common settings: -
- * lifetime: 2 seconds
- *
- * @param name
- * @return
- */
- public static Interest getDefaultInterest(Name name) {
- Interest interest = new Interest(name, DEFAULT_TIMEOUT);
- return interest;
- }
+ public Data getSync(Face face, Interest interest) throws IOException;
}
diff --git a/src/main/java/com/intel/jndn/utils/SegmentedClient.java b/src/main/java/com/intel/jndn/utils/SegmentedClient.java
index 38524ef..40ef7bb 100644
--- a/src/main/java/com/intel/jndn/utils/SegmentedClient.java
+++ b/src/main/java/com/intel/jndn/utils/SegmentedClient.java
@@ -13,11 +13,13 @@
*/
package com.intel.jndn.utils;
-import java.io.ByteArrayOutputStream;
+import com.intel.jndn.utils.client.SegmentedFutureData;
+import com.intel.jndn.utils.client.FutureData;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.named_data.jndn.Data;
@@ -25,7 +27,6 @@
import net.named_data.jndn.Interest;
import net.named_data.jndn.Name;
import net.named_data.jndn.encoding.EncodingException;
-import net.named_data.jndn.util.Blob;
/**
* Provide a client to simplify retrieving segmented Data packets over the NDN
@@ -38,14 +39,13 @@
*
* @author Andrew Brown <andrew.brown@intel.com>
*/
-public class SegmentedClient {
+public class SegmentedClient implements Client {
private static SegmentedClient defaultInstance;
private static final Logger logger = Logger.getLogger(SegmentedClient.class.getName());
- private static final int SLEEP_TIME_MS = 10;
/**
- * Singleton access for simpler client use
+ * Singleton access for simpler client use.
*
* @return
*/
@@ -67,7 +67,37 @@
* @return a list of FutureData packets; if the first segment fails, the list
* will contain one FutureData with the failure exception
*/
- public List<FutureData> getAsync(Face face, Interest interest) {
+ @Override
+ public Future<Data> getAsync(Face face, Interest interest) {
+ List<Future<Data>> segments = getAsyncList(face, interest);
+ return new SegmentedFutureData(interest.getName().getPrefix(-1), segments);
+ }
+
+ /**
+ * Asynchronously send Interest packets for a segmented result; will block
+ * until the first packet is received and then send remaining interests until
+ * the specified FinalBlockId.
+ *
+ * @param face
+ * @param name
+ * @return an aggregated data packet from all received segments
+ */
+ public Future<Data> getAsync(Face face, Name name) {
+ return getAsync(face, SimpleClient.getDefaultInterest(name));
+ }
+
+ /**
+ * Asynchronously send Interest packets for a segmented result; will block
+ * until the first packet is received and then send remaining interests until
+ * the specified FinalBlockId.
+ *
+ * @param face
+ * @param interest should include either a ChildSelector or an initial segment
+ * number
+ * @return a list of FutureData packets; if the first segment fails, the list
+ * will contain one FutureData with the failure exception
+ */
+ public List<Future<Data>> getAsyncList(Face face, Interest interest) {
// get first segment; default 0 or use a specified start segment
long firstSegment = 0;
boolean specifiedSegment = false;
@@ -78,12 +108,13 @@
// check for interest selector if no initial segment found
if (interest.getChildSelector() == -1) {
logger.log(Level.WARNING, "No child selector set for a segmented Interest; this may result in incorrect retrieval.");
+ // allow this interest to pass without a segment marker since it may still succeed
}
}
// setup segments
- final List<FutureData> segments = new ArrayList<>();
- segments.add(Client.getDefault().getAsync(face, interest));
+ final List<Future<Data>> segments = new ArrayList<>();
+ segments.add(SimpleClient.getDefault().getAsync(face, interest));
// retrieve first packet to find last segment value
long lastSegment;
@@ -103,7 +134,7 @@
for (long i = firstSegment + 1; i <= lastSegment; i++) {
Interest segmentedInterest = new Interest(interest);
segmentedInterest.getName().appendSegment(i);
- FutureData futureData = Client.getDefault().getAsync(face, segmentedInterest);
+ Future<Data> futureData = SimpleClient.getDefault().getAsync(face, segmentedInterest);
segments.add((int) i, futureData);
}
@@ -120,8 +151,8 @@
* @param name
* @return
*/
- public List<FutureData> getAsync(Face face, Name name) {
- return getAsync(face, Client.getDefaultInterest(name));
+ public List<Future<Data>> getAsyncList(Face face, Name name) {
+ return getAsyncList(face, SimpleClient.getDefaultInterest(name));
}
/**
@@ -132,40 +163,18 @@
* @param interest should include either a ChildSelector or an initial segment
* number
* @return a Data packet; the name will inherit from the sent Interest, not
- * the returned packets (TODO or should we parse from returned Data? copy
- * first Data?) and the content will be a concatenation of all of the packet
- * contents.
+ * the returned packets and the content will be a concatenation of all of the
+ * packet contents.
+ * @throws java.io.IOException
*/
- public Data getSync(Face face, Interest interest) {
- List<FutureData> segments = getAsync(face, interest);
-
- // process events until complete
- while (!isSegmentListComplete(segments)) {
- try {
- face.processEvents();
- Thread.sleep(SLEEP_TIME_MS);
- } catch (EncodingException | IOException e) {
- logger.log(Level.WARNING, "Failed to retrieve data: ", e);
- return null;
- } catch (InterruptedException ex) {
- // do nothing
- }
+ @Override
+ public Data getSync(Face face, Interest interest) throws IOException {
+ try {
+ return getAsync(face, interest).get();
+ } catch (ExecutionException | InterruptedException e) {
+ logger.log(Level.WARNING, "Failed to retrieve data.", e);
+ throw new IOException("Failed to retrieve data.", e);
}
-
- // build final blob
- ByteArrayOutputStream content = new ByteArrayOutputStream();
- for (FutureData futureData : segments) {
- try {
- content.write(futureData.get().getContent().getImmutableArray());
- } catch (ExecutionException | IOException | InterruptedException e) {
- logger.log(Level.WARNING, "Failed to parse retrieved data: ", e);
- return null;
- }
- }
-
- Data data = new Data(interest.getName()); // TODO this name may not be correct; may need to contain additional suffixes
- data.setContent(new Blob(content.toByteArray()));
- return data;
}
/**
@@ -177,9 +186,10 @@
* @param face
* @param name
* @return
+ * @throws java.io.IOException
*/
- public Data getSync(Face face, Name name) {
- return getSync(face, Client.getDefaultInterest(name));
+ public Data getSync(Face face, Name name) throws IOException {
+ return getSync(face, SimpleClient.getDefaultInterest(name));
}
/**
@@ -193,19 +203,4 @@
public static boolean hasSegment(Name name) {
return name.get(-1).getValue().buf().get(0) == 0x00;
}
-
- /**
- * Check if a list of segments have returned from the network.
- *
- * @param segments
- * @return
- */
- protected boolean isSegmentListComplete(List<FutureData> segments) {
- for (FutureData futureData : segments) {
- if (!futureData.isDone()) {
- return false;
- }
- }
- return true;
- }
}
diff --git a/src/main/java/com/intel/jndn/utils/SimpleClient.java b/src/main/java/com/intel/jndn/utils/SimpleClient.java
new file mode 100644
index 0000000..a751f8e
--- /dev/null
+++ b/src/main/java/com/intel/jndn/utils/SimpleClient.java
@@ -0,0 +1,145 @@
+/*
+ * jndn-utils
+ * Copyright (c) 2015, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 3, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
+ * more details.
+ */
+package com.intel.jndn.utils;
+
+import com.intel.jndn.utils.client.FutureData;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnTimeout;
+import java.util.logging.Logger;
+
+/**
+ * Provide a client to simplify information retrieval over the NDN network.
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class SimpleClient implements Client {
+
+ public static final long DEFAULT_SLEEP_TIME = 20;
+ public static final long DEFAULT_TIMEOUT = 2000;
+ private static final Logger logger = Logger.getLogger(SimpleClient.class.getName());
+ private static SimpleClient defaultInstance;
+
+ /**
+ * Singleton access for simpler client use
+ *
+ * @return
+ */
+ public static SimpleClient getDefault() {
+ if (defaultInstance == null) {
+ defaultInstance = new SimpleClient();
+ }
+ return defaultInstance;
+ }
+
+ /**
+ * Asynchronously request the Data for an Interest. This will send the
+ * Interest and return immediately; use futureData.get() to block until the
+ * Data returns (see FutureData) or manage the event processing independently.
+ *
+ * @param face
+ * @param interest
+ * @return
+ */
+ @Override
+ public Future<Data> getAsync(Face face, Interest interest) {
+ final FutureData futureData = new FutureData(face, interest.getName());
+
+ // send interest
+ try {
+ face.expressInterest(interest, new OnData() {
+ @Override
+ public void onData(Interest interest, Data data) {
+ futureData.resolve(data);
+ }
+ }, new OnTimeout() {
+ @Override
+ public void onTimeout(Interest interest) {
+ futureData.reject(new TimeoutException());
+ }
+ });
+ } catch (IOException e) {
+ logger.log(Level.WARNING, "IO failure while sending interest: ", e);
+ futureData.reject(e);
+ }
+
+ return futureData;
+ }
+
+ /**
+ * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
+ * second timeout); this will block until complete (i.e. either data is
+ * received or the interest times out).
+ *
+ * @param face
+ * @param name
+ * @return
+ */
+ public Future<Data> getAsync(Face face, Name name) {
+ return getAsync(face, getDefaultInterest(name));
+ }
+
+ /**
+ * Synchronously retrieve the Data for an Interest; this will block until
+ * complete (i.e. either data is received or the interest times out).
+ *
+ * @param face
+ * @param interest
+ * @return Data packet or null
+ * @throws java.io.IOException
+ */
+ @Override
+ public Data getSync(Face face, Interest interest) throws IOException {
+ try {
+ return getAsync(face, interest).get();
+ } catch (ExecutionException | InterruptedException e) {
+ logger.log(Level.WARNING, "Failed to retrieve data.", e);
+ throw new IOException("Failed to retrieve data.", e);
+ }
+ }
+
+ /**
+ * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
+ * second timeout); this will block until complete (i.e. either data is
+ * received or the interest times out).
+ *
+ * @param face
+ * @param name
+ * @return
+ * @throws java.io.IOException
+ */
+ public Data getSync(Face face, Name name) throws IOException {
+ return getSync(face, getDefaultInterest(name));
+ }
+
+ /**
+ * Create a default interest for a given Name using some common settings: -
+ * lifetime: 2 seconds
+ *
+ * @param name
+ * @return
+ */
+ public static Interest getDefaultInterest(Name name) {
+ Interest interest = new Interest(name, DEFAULT_TIMEOUT);
+ return interest;
+ }
+}