/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2011 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library 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
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.test.profiles.versioning;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import org.ccnx.ccn.CCNContentHandler;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.CCNInterestHandler;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.CCNStringObject;
import org.ccnx.ccn.profiles.CommandMarker;
import org.ccnx.ccn.profiles.versioning.InterestData;
import org.ccnx.ccn.profiles.versioning.VersionNumber;
import org.ccnx.ccn.profiles.versioning.VersioningInterestManager;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.junit.Assert;
public class VersioningHelper {
public static void compareReceived(CCNHandle handle, ArrayList<CCNStringObject> sent, TestListener listener) throws Exception {
Assert.assertEquals(sent.size(), listener.received.size());
// make a usable data structure from the ContentObjects
ArrayList<String> recv = new ArrayList<String>();
for( ReceivedData datum : listener.received) {
CCNStringObject so = new CCNStringObject(datum.object, handle);
recv.add(so.string());
}
for( int i = 0; i < sent.size(); i++ ) {
// now make sure what we got is what we expected
String string_sent = sent.get(i).string();
// make sure it exists in received. does not need
// to be in the same order as sent
Assert.assertTrue(recv.contains(string_sent));
}
}
public static ArrayList<CCNStringObject> sendEventStream(CCNHandle handle, ContentName basename, int tosend) throws Exception {
// Send a stream of string objects
ArrayList<CCNStringObject> sent = new ArrayList<CCNStringObject>();
for(int i = 0; i < tosend; i++) {
// Save content
try {
CCNStringObject so = new CCNStringObject(basename,
String.format("string object %d", i),
SaveType.LOCALREPOSITORY, handle);
so.save();
so.close();
sent.add(so);
// System.out.println("Sent version " + VersioningProfile.printAsVersionComponent(so.getVersion()));
// Flow controller just cannot go this fast, so need to slow it down.
Thread.sleep(10);
} catch(Exception e) {
e.printStackTrace();
throw e;
}
// System.out.println(i);
}
return sent;
}
public static class ConditionLong {
protected Lock lock = new ReentrantLock();
protected Condition cond = lock.newCondition();
public long value = 0;
public ConditionLong(long value) {
this.value = value;
}
public long setValue(long value) throws InterruptedException {
lock.lock();
try {
long oldValue = this.value;
this.value = value;
cond.signal();
return oldValue;
} finally {
lock.unlock();
}
}
public long getValue() {
lock.lock();
try {
return value;
} finally {
lock.unlock();
}
}
public long increment() {
lock.lock();
try {
value++;
cond.signal();
return value;
} finally {
lock.unlock();
}
}
public long decrement() {
lock.lock();
try {
value--;
cond.signal();
return value;
} finally {
lock.unlock();
}
}
/**
* @param value = to wait for
* @param timeout = in milliseconds
* @return true if value found, false if timeout
* @throws InterruptedException
*/
public boolean waitForValue(long value, long timeout) throws InterruptedException {
boolean rtn = true;
lock.lock();
try {
while ( rtn && this.value != value ) {
rtn = cond.await(timeout, TimeUnit.MILLISECONDS);
}
} finally {
lock.unlock();
}
return rtn;
}
}
public static class ReceivedData {
public final ContentObject object;
public final Interest interest;
public ReceivedData(ContentObject object, Interest interest) {
this.object = object;
this.interest = interest;
}
}
public static class TestListener implements CCNContentHandler {
public final ConditionLong cl = new ConditionLong(0);
public final ArrayList<ReceivedData> received = new ArrayList<ReceivedData>();
public InterestData id = null;
public int runCount = 0;
public boolean debugOutput = false;
// if true, run() method will send an initial interest, otherwise it will assume there
// is already an interest outstanding.
public boolean sendFirstInterest = true;
public synchronized Interest handleContent(ContentObject data, Interest interest) {
if( debugOutput )
System.out.println("handleContent: " + data.name());
received.add(new ReceivedData(data, interest));
try {
if( null != id ) {
try {
VersionNumber version = new VersionNumber(data.name());
id.addExclude(version);
} catch(Exception e) {
e.printStackTrace();
}
if( cl.getValue() < runCount ) {
Interest newinterest = id.buildInterest();
if( debugOutput )
System.out.println("Return interest: " + newinterest);
return newinterest;
}
}
if( debugOutput )
System.out.println("Return interest: null");
return null;
} finally {
// want to do this at the very end, so anyting waiting on the
// value to be incremented will get it after this procedure is done.
cl.increment();
}
}
public void setInterestData(InterestData id) {
this.id = id;
}
/**
* Sends interests, blocks until done or timeout
* @param handle
* @param count
* @param timeout
* @return true if count received, false otherwise
* @throws IOException
* @throws InterruptedException
*/
public boolean run(CCNHandle handle, int count, long timeout) throws IOException, InterruptedException {
runCount = count;
if( count > 0 && sendFirstInterest ) {
Interest interest = id.buildInterest();
handle.expressInterest(interest, this);
}
// wait for it to be done
boolean rtn = cl.waitForValue(count, timeout);
System.out.println(String.format("run received %d objects", cl.getValue()));
return rtn;
}
}
public static class TestFilterListener implements CCNInterestHandler {
public final ConditionLong cl = new ConditionLong(0);
public final ArrayList<Interest> received = new ArrayList<Interest>();
public int runCount = 0;
public boolean debugOutput = false;
public synchronized boolean handleInterest(Interest interest) {
if( debugOutput )
System.out.println("handleInterest: " + interest.name());
received.add(interest);
cl.increment();
return false;
}
}
/**
* Track interests
*/
public static class SinkHandle extends CCNHandle {
// The set of pending interests
public final ArrayList<Interest> interests = new ArrayList<Interest>();
public final ConditionLong count = new ConditionLong(0);
public final ConditionLong total_count = new ConditionLong(0);
protected SinkHandle() throws ConfigurationException, IOException {
super();
}
protected SinkHandle(KeyManager keyManager) throws IOException {
super(keyManager);
}
public static SinkHandle open(KeyManager keyManager) throws IOException {
synchronized (CCNHandle.class) {
return new SinkHandle(keyManager);
}
}
public static SinkHandle open(CCNHandle handle) throws IOException {
return open(handle.keyManager());
}
@Override
public synchronized void expressInterest(
Interest interest,
CCNContentHandler handler) throws IOException {
// ignore startwrites
if( !interest.name().contains(CommandMarker.COMMAND_MARKER_REPO_START_WRITE.getBytes()) ) {
interests.add(interest);
if( Log.isLoggable(Log.FAC_ENCODING, Level.FINER))
Log.finer(Log.FAC_ENCODING, String.format("expressInterest (%d): %s",
interests.size(), interest.toString()));
count.increment();
total_count.increment();
}
super.expressInterest(interest, handler);
}
@Override
public synchronized void cancelInterest(Interest interest, CCNContentHandler handler) {
if( !interest.name().contains(CommandMarker.COMMAND_MARKER_REPO_START_WRITE.getBytes()) ) {
interests.remove(interest);
// System.out.println(String.format("cancelInterest (%d): %s",
// interests.size(), interest.toString()));
count.decrement();
}
super.cancelInterest(interest, handler);
}
}
public static class TestVIM extends VersioningInterestManager {
protected boolean _sendInterests = false;
public TestVIM(CCNHandle handle, ContentName name,
Set<VersionNumber> exclusions, VersionNumber startingVersion,
CCNContentHandler handler) {
super(handle, name, exclusions, startingVersion, handler);
}
public Interest exposeReceive(ContentObject data, Interest interest) {
return receive(data, interest);
}
public void setSendInterest(boolean enable) {
_sendInterests = enable;
}
public TreeSet<InterestData> getInterestDataTree() {
return _interestData;
}
public TreeSet<VersionNumber> getExclusions() {
return _exclusions;
}
/**
* Don't actually send an interest
*/
@Override
protected void sendInterest(InterestData id) {
Interest old = id.getLastInterest();
Interest interest = id.buildInterest();
synchronized(_interestMap) {
// Remove the old interest so we never match more than one
// thing to an INterestData
if( null != old )
_interestMap.remove(old);
try {
if( _sendInterests )
_handle.expressInterest(interest, this);
_interestMap.put(interest, new InterestMapData(id));
} catch(IOException e) {
e.printStackTrace();
}
}
}
}
}