/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009, 2010, 2011, 2012 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.impl.repo;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.logging.Level;
import org.ccnx.ccn.CCNContentHandler;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.impl.InterestTable;
import org.ccnx.ccn.impl.InterestTable.Entry;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.profiles.SegmentationProfile;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
/**
* Handles incoming data for the repository. Its jobs are to store data in the repository
* by interfacing with the RepositoryStore and to generate interests for data following the
* received data in an input stream. RepositoryDataListeners are destroyed after the stream
* which triggered their creation has been fully read.
*/
public class RepositoryDataListener implements CCNContentHandler {
private long _timer; // Used to timeout inactive listeners
private final Interest _origInterest; // The interest which originally triggered the creation of
// this listener. Used to filter out duplicate or overlapping
// requests for listeners
private final InterestTable<Object> _interests = new InterestTable<Object>(); // Used to hold outstanding interests
// expressed but not yet satisfied. Also used to decide how many interests
// may be expressed to satisfy the current pipelining window
protected RepositoryServer _server;
private final CCNHandle _handle;
private long _largestSegmentNumberReceived = -1;
private long _finalSegmentNumber = -1; // expected last block of the stream
private final GetLargestSegmentNumberAction _glsna = new GetLargestSegmentNumberAction();
protected boolean _throttled = false;
protected Interest _restartInterest = null;
/**
* @param origInterest interest to be used to identify this listener to filter out subsequent duplicate or overlapping
* requests
* @param interest used only to log the actual interest that created this listener
* @param server associated RepositoryServer
*/
public RepositoryDataListener(Interest origInterest, Interest interest, RepositoryServer server) {
_origInterest = origInterest;
_server = server;
_handle = server.getHandle();
_timer = System.currentTimeMillis();
if (Log.isLoggable(Log.FAC_REPO, Level.INFO)) {
Log.info(Log.FAC_REPO, "Starting up repository listener on original interest: {0} interest {1}", origInterest, interest);
}
}
/**
* The actual incoming data handler. Kicks off a thread to store the data and expresses interest in data following
* the incoming data.
*/
public Interest handleContent(ContentObject co,
Interest interest) {
_server._stats.increment(RepositoryServer.StatsEnum.HandleContent);
_timer = System.currentTimeMillis();
boolean isFinalSegment = false;
if (SegmentationProfile.isSegment(co.name())) {
long thisSegmentNumber = SegmentationProfile.getSegmentNumber(co.name());
if (thisSegmentNumber >= _largestSegmentNumberReceived)
_largestSegmentNumberReceived = thisSegmentNumber;
// For now, only set _finalSegmentNumber when we *know* we have the correct final
// block number -- i.e. we get a block whose segment number matches the encoded
// final block. A pipelining stream may help us by setting the finalBlockID in several
// blocks prior to the last one, to let us know when to slow down -- but it's allowed
// to be wrong, and keep going if it hasn't yet hit a block which is itself marked
// as the last one (whose own segment number matches its own finalBlockID value).
// Taking into account incorrect ramp-down finalBlockIDs, recovering, and knowing we
// have more to get requires a bit more sophisticated tweaking of the pipelining code.
// Basically if we think we know the finalBlockID, we get that block, and it isn't
// marked as the final block, we open the window back up.
if (null != co.signedInfo().getFinalBlockID()) {
// Alright, either we didn't know a final block id before, in which case
// we just believe this one, or we did, in which case this one is later than
// the one we knew, or earlier. If it's later, we just store it and open up
// the window somewhat. If it's earlier, we shorten the window, but don't bother
// canceling already expressed interests for blocks past the window till we finish
// the stream. So just update our notion of finalBlockID.
// So in other words, the only time we use this value to actually cancel outstanding
// interests is when we have hit the end of the stream.
_finalSegmentNumber = SegmentationProfile.getSegmentNumber(co.signedInfo().getFinalBlockID());
if (_finalSegmentNumber == thisSegmentNumber) {
isFinalSegment = true; // we only know for sure what the final block is when this is true
}
if (Log.isLoggable(Log.FAC_REPO, Level.FINEST)) {
Log.finest(Log.FAC_REPO, "Found final segment number: {0}", _finalSegmentNumber);
}
}
}
calculateInterests: synchronized (_interests) {
long largestSegmentNumberRequested = getLargestSegmentNumber();
_interests.remove(interest, null);
// Compute next interests to ask for and ask for them
// Note that this should only ask for 1 interest except for the first time through this code when it
// should ask for "windowSize" interests.
if (Log.isLoggable(Log.FAC_REPO, Level.FINEST)) {
Log.finest(Log.FAC_REPO, "Largest segment number requested is {0}", largestSegmentNumberRequested);
}
int remainingWindow = _server.getWindowSize() - _interests.size();
// Make sure we don't go past prospective last block.
if (_finalSegmentNumber >= 0 && _finalSegmentNumber < (largestSegmentNumberRequested + remainingWindow)) {
// want max to be _finalSegmentNumber or largestSegmentNumberRequested, whichever is larger,
// unless isFinalSegment is true, in which case max is _finalSegmentNumber (i.e. no more interests)
remainingWindow = (int)(_finalSegmentNumber - largestSegmentNumberRequested + 1);
// If we're confident about the final block ID, cancel previous extra interests
if (isFinalSegment) {
cancelHigherInterests(_finalSegmentNumber);
break calculateInterests; // exit the synchronized block and process the data
}
}
if (remainingWindow < 0)
remainingWindow = 0;
if (Log.isLoggable(Log.FAC_REPO, Level.FINEST)) {
Log.finest(Log.FAC_REPO, "REPO: Got block: {0} expressing {1} more interests, largest block {2} final block {3} last block? {4}", co.name(), remainingWindow, _largestSegmentNumberReceived, _finalSegmentNumber, isFinalSegment);
}
if (! _throttled) {
for (int i = 1; i <= remainingWindow; i++) {
ContentName name = SegmentationProfile.segmentName(co.name(), largestSegmentNumberRequested + i);
// DKS - should use better interest generation to only get segments (TBD, in SegmentationProfile)
Interest newInterest = new Interest(name);
if (_server.getThrottle()) {
_throttled = true;
_restartInterest = newInterest;
break;
}
outputInterest(newInterest);
}
}
}
handleData(co);
return null;
}
public void outputInterest(Interest interest) {
try {
_handle.expressInterest(interest, this);
_interests.add(interest, null);
_server._stats.increment(RepositoryServer.StatsEnum.HandleContentExpressInterest);
} catch (IOException e) {
_server._stats.increment(RepositoryServer.StatsEnum.HandleContentExpressInterestErrors);
Log.logStackTrace(Level.WARNING, e);
e.printStackTrace();
}
}
public void restart() {
synchronized (_interests) {
if (_throttled) {
if (null != _restartInterest) {
Log.warning("Restarting - interest is {0}", _restartInterest);
outputInterest(_restartInterest);
_restartInterest = null;
} else
Log.warning("Warning - restart with no interest");
_throttled = false;
}
}
}
/**
* Allow subclasses to override data handling behavior
* @param co
*/
public void handleData(ContentObject co) {
_server._stats.increment(RepositoryServer.StatsEnum.HandleContentHandleData);
_server.getDataHandler().add(co);
}
/**
* Since the interest table doesn't have a defined order for values with the same length we
* must explicitly go through all the values to decide whether we want to take some action
* based on the "value" (i.e. segment #) of some particular interest
*/
/**
* Must match implementation of nextSegmentNumber in input streams, segmenters.
*/
private class GetLargestSegmentNumberAction extends InterestActionClass {
@Override
protected void action(long value, Entry<?> entry, Iterator<Entry<Object>> it) {
if (value >= _value)
_value = value;
}
private long getValue() {
return _value;
}
}
private long getLargestSegmentNumber() {
interestsAction(_glsna);
return _glsna.getValue();
}
/**
* Cancel all interests for segments higher than "value"
* @param value
*/
private class CancelInterestsAction extends InterestActionClass {
CCNContentHandler _handler;
private CancelInterestsAction(long startValue, CCNContentHandler handler) {
_value = startValue;
_handler = handler;
}
@Override
protected void action(long value, Entry<?> entry, Iterator<Entry<Object>> it) {
if (value > _value) {
_server._stats.increment(RepositoryServer.StatsEnum.HandleContentCancelInterest);
_handle.cancelInterest(entry.interest(), _handler);
it.remove();
}
}
}
private void cancelHigherInterests(long value) {
CancelInterestsAction cia = new CancelInterestsAction(value, this);
interestsAction(cia);
}
/**
* Perform the specified action for all values in the interest table
* @param value
* @param action
*/
private abstract class InterestActionClass {
protected long _value = 0;
protected abstract void action(long value, Entry<?> entry, Iterator<Entry<Object>> it);
}
private void interestsAction(InterestActionClass action) {
Collection<Entry<Object>> values = _interests.values();
Iterator<Entry<Object>> it = values.iterator();
while (it.hasNext()) {
Entry<?> entry = it.next();
if (SegmentationProfile.isSegment(entry.interest().name())) {
long value = SegmentationProfile.getSegmentNumber(entry.interest().name());
action.action(value, entry, it);
}
}
}
/**
* Called on listener teardown.
*/
public void cancelInterests() {
for (Entry<Object> entry : _interests.values()) {
_server._stats.increment(RepositoryServer.StatsEnum.HandleContentCancelInterest);
_handle.cancelInterest(entry.interest(), this);
}
}
/**
* Gets the time of the last data received
* @return
*/
public long getTimer() {
return _timer;
}
/**
* Changes the time used to timeout the listener
* @param time
*/
public void setTimer(long time) {
_timer = time;
}
/**
* Gets the namespace served by this listener as an interest
* @return
*/
public Interest getOrigInterest() {
return _origInterest;
}
/**
* Gets the current set of outstanding interests for this listener
* @return
*/
public InterestTable<Object> getInterests() {
return _interests;
}
}