/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008-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.io.content;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import org.ccnx.ccn.CCNContentHandler;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.ContentVerifier;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.CCNFlowControl;
import org.ccnx.ccn.impl.CCNFlowControl.SaveType;
import org.ccnx.ccn.impl.CCNFlowControl.Shape;
import org.ccnx.ccn.impl.repo.RepositoryFlowControl;
import org.ccnx.ccn.impl.security.crypto.ContentKeys;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.impl.support.Tuple;
import org.ccnx.ccn.io.CCNInputStream;
import org.ccnx.ccn.io.CCNVersionedInputStream;
import org.ccnx.ccn.io.CCNVersionedOutputStream;
import org.ccnx.ccn.io.ErrorStateException;
import org.ccnx.ccn.io.LinkCycleException;
import org.ccnx.ccn.io.NoMatchingContentFoundException;
import org.ccnx.ccn.io.CCNAbstractInputStream.FlagTypes;
import org.ccnx.ccn.io.content.Link.LinkObject;
import org.ccnx.ccn.profiles.SegmentationProfile;
import org.ccnx.ccn.profiles.VersioningProfile;
import org.ccnx.ccn.profiles.versioning.VersionNumber;
import org.ccnx.ccn.protocol.CCNTime;
import org.ccnx.ccn.protocol.Component;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.KeyLocator;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
import org.ccnx.ccn.protocol.SignedInfo.ContentType;
/**
* Extends a NetworkObject to add specifics for using a CCN-based backing store. Each time
* the object is saved creates a new CCN version. Readers can open a specific version or
* not specify a version, in which case the latest available version is read. Defaults
* allow for saving data to a repository or directly to the network.
*
* Need to support four use models:
* dimension 1: synchronous - ask for and block, the latest version or a specific version
* dimension 2: asynchronous - ask for and get in the background, the latest version or a specific
* version
* When possible, keep track of the latest version known so that the latest version queries
* can attempt to do better than that. Start by using only in the background load case, as until
* something comes back we can keep using the old one and the propensity for blocking is high.
*
* Support for subclasses or users specifying different flow controllers with
* different behavior. Build in support for either the simplest standard flow
* controller, or a standard repository-backed flow controller.
*
* These objects attempt to maintain a CCN copy of the current state of their data. In descriptions
* below, an object that is "dirty" is one whose data has been modified locally, but not yet
* saved to the network.
*
* While CCNNetworkObject could be used directly, it almost never is; it is usually
* more effective to define a subclass specialized to save/retrieve a specific object
* type.
*
* Updates, 12/09: Move to creating a flow controller in the write constructor if
* one isn't passed in. Read constructors still lazily create flow controllers on
* first write (tradeoff); preemptive construction (and registering for interests)
* can be achieved by calling the setupSave() method which creates a flow controller
* if one hasn't been created already. Move to a strong default of saving
* to a repository, unless overridden by the subclass itself. Change of repository/raw
* nature can be made with the setRawSave() and setRepositorySave() methods.
*
* TODO: Note that the CCNNetworkObject class hierarchy currently has a plethora of constructors.
* It is also missing some important functionality -- encryption, the ability to specify
* freshness, and so on. Expect new constructors to deal with the latter deficiencies, and
* a cleanup of the constructor architecture overall in the near term.
*/
public abstract class CCNNetworkObject<E> extends NetworkObject<E> implements CCNContentHandler {
protected static final byte [] GONE_OUTPUT = "GONE".getBytes();
/**
* Unversioned "base" name.
*/
protected ContentName _baseName;
/**
* The most recent version we have read/written.
*/
protected byte [] _currentVersionComponent;
/**
* Cached versioned name.
*/
protected ContentName _currentVersionName;
/**
* Flag to indicate whether content has been explicitly marked as GONE
* in the latest version we know about. Use an explicit flag to separate from
* the option for valid null content, or content that has not yet been updated.
*/
protected boolean _isGone = false;
/**
* The first segment for the stored data
*/
protected ContentObject _firstSegment = null;
/**
* If the name we started with was actually a link, detect that, store the link,
* and dereference it to get the content. Call updateLink() to update the link
* itself, and if updated, to update the dereferenced value.
*
* If the initial link is a link, recursion should push that into the link of
* this LinkObject, and read its data. If that is a link, it should push again --
* this should chain through links till we reach an object of the desired type,
* or blow up. (It won't handle encrypted links, though; we may need to distinguish
* between ENCR and ENCRL. Having encrypted links would be handy, to send people
* off in random directions. But it matters a lot to be able to tell if the decryption
* is a LINK or not.)
*
* Writing linked objects is better done by separately writing the object and
* the link, as it gives you more control over what is happening. If you attempt
* to save this object, it may break the link (as the link may link to the particular
* version retrieved). You can use this inner link object to manually update the link
* to the target; but there are no good defaults about how to update the data. So
* you need to specify the new link value yourself. For now we don't prevent users
* from getting their data and their links de-syncrhonized.
*/
protected LinkObject _dereferencedLink;
protected PublisherPublicKeyDigest _currentPublisher;
protected KeyLocator _currentPublisherKeyLocator;
protected CCNHandle _handle;
/**
* We are not allowed to register or deregister prefixes for flow controllers we didn't
* create.
*/
protected CCNFlowControl _flowControl;
protected boolean _FCIsOurs = false;
protected boolean _disableFlowControlRequest = false;
protected PublisherPublicKeyDigest _publisher; // publisher we write under, if null, use handle defaults
protected KeyLocator _keyLocator; // locator to find publisher key
protected SaveType _saveType = null; // what kind of flow controller to make if we don't have one
protected Integer _freshnessSeconds = null; // if we want to set short freshness
protected ContentKeys _keys;
protected ContentVerifier _verifier;
/**
* Controls ongoing update.
*/
Interest _currentInterest = null;
boolean _continuousUpdates = false;
HashSet<UpdateListener> _updateListeners = null;
protected Updater _updater = null;
/**
* Basic write constructor. This will set the object's internal data but it will not save it
* until save() is called. Unless overridden by the subclass, will default to save to
* a repository. Can be changed to save directly to the network using setRawSave().
* If a subclass sets the default behavior to raw saves, this can be overridden on a
* specific instance using setRepositorySave().
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param name Name under which to save object.
* @param data Data to save.
* @param handle CCNHandle to use for network operations. If null, a new one is created using CCNHandle#open().
* @throws IOException If there is an error setting up network backing store.
*/
public CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentName name, E data, SaveType saveType, CCNHandle handle) throws IOException {
this(type, contentIsMutable, name, data, saveType, null, null, handle);
}
/**
* Basic write constructor. This will set the object's internal data but it will not save it
* until save() is called. Unless overridden by the subclass, will default to save to
* a repository. Can be changed to save directly to the network using setRawSave().
* If a subclass sets the default behavior to raw saves, this can be overridden on a
* specific instance using setRepositorySave().
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param name Name under which to save object.
* @param data Data to save.
* @param raw If true, saves to network by default, if false, saves to repository by default.
* @param publisher The key to use to sign this data, or our default if null.
* @param locator The key locator to use to let others know where to get our key.
* @param handle CCNHandle to use for network operations. If null, a new one is created using CCNHandle#open().
* @throws IOException If there is an error setting up network backing store.
*/
public CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentName name, E data, SaveType saveType,
PublisherPublicKeyDigest publisher, KeyLocator locator,
CCNHandle handle) throws IOException {
super(type, contentIsMutable, data);
if (null == handle) {
try {
handle = CCNHandle.open();
} catch (ConfigurationException e) {
throw new IllegalArgumentException("handle null, and cannot create one: " + e.getMessage(), e);
}
}
_updater = new Updater(this);
_handle = handle;
_verifier = handle.defaultVerifier();
_baseName = name;
_publisher = publisher;
_keyLocator = locator;
_saveType = saveType;
// Make our flow controller and register interests for our base name, if we have one.
// Otherwise, create flow controller when we need one.
if (null != name) {
createFlowController();
}
}
/**
* Specialized constructor, allowing subclasses to override default flow controller
* (and hence backing store) behavior.
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param name Name under which to save object.
* @param data Data to save.
* @param publisher The key to use to sign this data, or our default if null.
* @param locator The key locator to use to let others know where to get our key.
* @param flowControl Flow controller to use. A single flow controller object
* is used for all this instance's writes, we use underlying streams to call
* CCNFlowControl#startWrite(ContentName, Shape) on each save. Calls to
* setRawSave() and setRepositorySave() will replace this flow controller
* with a raw or repository flow controller, and should not be used with
* this type of object (which obviously cares about what flow controller to use).
* @throws IOException If there is an error setting up network backing store.
*/
protected CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentName name, E data,
PublisherPublicKeyDigest publisher,
KeyLocator locator,
CCNFlowControl flowControl) throws IOException {
super(type, contentIsMutable, data);
_baseName = name;
_publisher = publisher;
_keyLocator = locator;
if (null == flowControl) {
throw new IOException("FlowControl cannot be null!");
}
_updater = new Updater(this);
_flowControl = flowControl;
_handle = _flowControl.getHandle();
_saveType = _flowControl.saveType();
_verifier = _handle.defaultVerifier();
}
/**
* Read constructor. Will try to pull latest version of this object, or a specific
* named version if specified in the name. If read times out, will leave object in
* its uninitialized state.
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param name Name from which to read the object. If versioned, will read that specific
* version. If unversioned, will attempt to read the latest version available.
* @param handle CCNHandle to use for network operations. If null, a new one is created using CCNHandle#open().
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentName name, CCNHandle handle)
throws ContentDecodingException, IOException {
this(type, contentIsMutable, name,
(PublisherPublicKeyDigest)null, handle);
}
/**
* Read constructor. Will try to pull latest version of this object, or a specific
* named version if specified in the name. If read times out, will leave object in
* its uninitialized state.
*
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param name Name from which to read the object. If versioned, will read that specific
* version. If unversioned, will attempt to read the latest version available.
* @param publisher Particular publisher we require to have signed the content, or null for any publisher.
* @param flowControl Flow controller to use. A single flow controller object
* is used for all this instance's writes, we use underlying streams to call
* CCNFlowControl#startWrite(ContentName, Shape) on each save.
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
protected CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentName name,
PublisherPublicKeyDigest publisher,
CCNFlowControl flowControl)
throws ContentDecodingException, IOException {
super(type, contentIsMutable);
if (null == flowControl) {
throw new IOException("FlowControl cannot be null!");
}
_updater = new Updater(this);
_flowControl = flowControl;
_handle = _flowControl.getHandle();
_saveType = _flowControl.saveType();
_verifier = _handle.defaultVerifier();
update(name, publisher);
}
/**
* Read constructor. Will try to pull latest version of this object, or a specific
* named version if specified in the name. If read times out, will leave object in
* its uninitialized state.
*
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param name Name from which to read the object. If versioned, will read that specific
* version. If unversioned, will attempt to read the latest version available.
* @param publisher Particular publisher we require to have signed the content, or null for any publisher.
* @param handle CCNHandle to use for network operations. If null, a new one is created using CCNHandle#open().
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentName name, PublisherPublicKeyDigest publisher,
CCNHandle handle)
throws ContentDecodingException, IOException {
super(type, contentIsMutable);
if (null == handle) {
try {
handle = CCNHandle.open();
} catch (ConfigurationException e) {
throw new IllegalArgumentException("handle null, and cannot create one: " + e.getMessage(), e);
}
}
_updater = new Updater(this);
_handle = handle;
_verifier = handle.defaultVerifier();
_baseName = name;
update(name, publisher);
}
/**
* Read constructor if you already have a segment of the object. Used by streams.
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param firstSegment First segment of the object, retrieved by other means.
* @param raw If true, defaults to raw network writes, if false, repository writes.
* @param handle CCNHandle to use for network operations. If null, a new one is created using CCNHandle#open().
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentObject firstSegment, CCNHandle handle)
throws ContentDecodingException, IOException {
super(type, contentIsMutable);
if (null == handle) {
try {
handle = CCNHandle.open();
} catch (ConfigurationException e) {
throw new IllegalArgumentException("handle null, and cannot create one: " + e.getMessage(), e);
}
}
_updater = new Updater(this);
_handle = handle;
_verifier = handle.defaultVerifier();
update(firstSegment);
}
/**
* Read constructor if you already have a segment of the object. Used by streams.
* @param type Wrapped class type.
* @param contentIsMutable is the wrapped class type mutable or not
* @param firstSegment First segment of the object, retrieved by other means.
* @param flowControl Flow controller to use. A single flow controller object
* is used for all this instance's writes, we use underlying streams to call
* CCNFlowControl#startWrite(ContentName, Shape) on each save.
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
protected CCNNetworkObject(Class<E> type, boolean contentIsMutable,
ContentObject firstSegment,
CCNFlowControl flowControl)
throws ContentDecodingException, IOException {
super(type, contentIsMutable);
if (null == flowControl)
throw new IllegalArgumentException("flowControl cannot be null!");
_updater = new Updater(this);
_flowControl = flowControl;
_handle = _flowControl.getHandle();
_saveType = _flowControl.saveType();
_verifier = _handle.defaultVerifier();
update(firstSegment);
}
/**
* Copy constructor. Handle it piece by piece, though it means
* updating this whenever the structure changes (rare).
*/
protected CCNNetworkObject(Class<E> type, CCNNetworkObject<? extends E> other) {
super(type, other);
_baseName = other._baseName;
_currentVersionComponent = other._currentVersionComponent;
_currentVersionName = other._currentVersionName;
_isGone = other._isGone;
_currentPublisher = other._currentPublisher;
_currentPublisherKeyLocator = other._currentPublisherKeyLocator;
_handle = other._handle;
_flowControl = other._flowControl;
_disableFlowControlRequest = other._disableFlowControlRequest;
_publisher = other._publisher;
_keyLocator = other._keyLocator;
_saveType = other._saveType;
_keys = (null != other._keys) ? other._keys.clone() : null;
_firstSegment = other._firstSegment;
_verifier = other._verifier;
_updater = new Updater(this);
// Do not copy update behavior. Even if other one is updating, we won't
// pick that up. Have to kick off manually
}
/**
* Maximize laziness of flow controller creation, to make it easiest for client code to
* decide how to store this object.
* When we create the flow controller, we add the base name namespace, so it will respond
* to requests for latest version. Create them immediately in write constructors,
* when we have a strong expectation that we will save data, if we have a namespace
* to start listening on. Otherwise wait till we are going to write.
* @return
* @throws IOException
*/
protected synchronized void createFlowController() throws IOException {
if (null == _flowControl) {
if (null == _saveType) {
if (Log.isLoggable(Log.FAC_IO, Level.FINER))
Log.finer(Log.FAC_IO, "Not creating flow controller yet, no saveType set.");
return;
}
switch (_saveType) {
case RAW:
_flowControl = new CCNFlowControl(_handle);
break;
case REPOSITORY:
_flowControl = new RepositoryFlowControl(_handle, false);
break;
case LOCALREPOSITORY:
_flowControl = new RepositoryFlowControl(_handle, true);
break;
default:
throw new IOException("Unknown save type: " + _saveType);
}
_FCIsOurs = true;
if (_disableFlowControlRequest)
_flowControl.disable();
// Have to register the version root. If we just register this specific version, we won't
// see any shorter interests -- i.e. for get latest version.
//_flowControl.addNameSpace(_baseName);
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "Created " + _saveType + " flow controller, for prefix {0}, save type " + _flowControl.saveType(), _baseName);
}
}
/**
* Get the flow controller associated with this object
* @return the flow controller or null if not assigned
*/
public CCNFlowControl getFlowControl() {
return _flowControl;
}
/**
* Get timeout associated with this object
* @return
*/
public long getTimeout() {
return _flowControl.getTimeout();
}
/**
* Start listening to interests on our base name, if we aren't already.
* @throws IOException
*/
public synchronized void setupSave(SaveType saveType) throws IOException {
setSaveType(saveType);
setupSave();
}
public synchronized void setupSave() throws IOException {
if (null != _flowControl) {
return;
}
createFlowController();
}
/**
* Finalizer. Somewhat dangerous, but currently best way to close
* lingering open registrations. Can't close the handle, till we ref count.
*/
@Override
protected void finalize() throws Throwable {
try {
close(); // close the object, canceling interests and listeners.
} finally {
super.finalize();
}
}
/**
* Close flow controller, remove listeners. Have to call setupSave to save with this object again,
* re-add listeners.
* @return
*/
public synchronized void close() {
cancelInterest();
clearListeners();
if (null != _flowControl) {
_flowControl.close();
}
}
public SaveType saveType() { return _saveType; }
/**
* Used by subclasses to specify a mandatory save type in
* read constructors. Only works on objects whose flow
* controller has not yet been set, to not override
* manually-set FC's.
*/
protected void setSaveType(SaveType saveType) throws IOException {
if (null == _flowControl) {
_saveType = saveType;
} else if (saveType != _saveType){
throw new IOException("Cannot change save type, flow controller already set!");
}
}
/**
* If you want to set the lifetime of objects saved with this instance.
* @param freshnessSeconds If null, will unset any freshness seconds (will
* write objects that stay in cache till forced out); if a value will constrain
* how long objects will stay in cache.
*/
public void setFreshnessSeconds(Integer freshnessSeconds) {
_freshnessSeconds = freshnessSeconds;
}
/**
* Override point where subclasses can modify each input stream before
* it is read. Subclasses should at least set the flags using getInputStreamFlags,
* or call super.setInputStreamProperties.
*/
protected void setInputStreamProperties(CCNInputStream inputStream) {
// default -- just set any flags
inputStream.setFlags(getInputStreamFlags());
}
/**
* Override point where subclasses can specify set of flags on input stream
* at point it is read or where necessary created.
* @return
*/
protected EnumSet<FlagTypes> getInputStreamFlags() {
return null;
}
/**
* Allow verifier to be specified. Could put this in the constructors; though they
* are already complicated enough. If not set, the default verifier for the key manager
* used by the object's handle is used.
* @param verifier the verifier to use. Cannot be null.
*/
public void setVerifier(ContentVerifier verifier) {
if (null != verifier)
_verifier = verifier;
}
/**
* Attempts to find a version after the latest one we have, or times out. If
* it times out, it simply leaves the object unchanged.
* @return returns true if it found an update, false if not
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public boolean update(long timeout) throws ContentDecodingException, IOException {
if (null == _baseName) {
throw new IllegalStateException("Cannot retrieve an object without giving a name!");
}
// Look for first segment of version after ours, or first version if we have none.
ContentObject firstSegment =
VersioningProfile.getFirstBlockOfLatestVersion(getVersionedName(), null, null, timeout,
_handle.defaultVerifier(), _handle);
if (null != firstSegment) {
return update(firstSegment);
}
return false;
}
/**
* The regular update does a call to do multi-hop get latest version -- i.e. it will try
* multiple times to find the latest version of a piece of content, even if interposed caches
* have something older. While that's great when you really need the latest, sometimes you are
* happy with the latest available version available in your local ccnd cache; or you really
* know there is only one version available and you don't want to try multiple times (and incur
* a timeout) in an attempt to get a later version that does not exist. This call, updateAny,
* claims to get "any" version available. In reality, it will do a single-hop latest version;
* i.e. if there are two versions say in your local ccnd cache (or repo with nothing in the ccnd
* cache), it will pull the later one. But
* it won't move beyond those to find a newer version available at a writer, or to find a later
* version in the repo than one in the ccnd cache. Use this if you know there is only one version
* of something, or you want a fast path to the latest version where it really doesn't have to
* be the "absolute" latest.
*
* Like all update methods, it will start from the version you've got -- so it is guaranteed to find
* something after the current version this object knows about (if it has already found something),
* and to time out and return false if there isn't anything later.
*/
public boolean updateAny(long timeout) throws ContentDecodingException, IOException {
if (null == _baseName) {
throw new IllegalStateException("Cannot retrieve an object without giving a name!");
}
// Look for first segment of version after ours, or first version if we have none.
ContentObject firstSegment =
VersioningProfile.getFirstBlockOfAnyLaterVersion(getVersionedName(), null, null, timeout,
_verifier, _handle);
if (null != firstSegment) {
return update(firstSegment);
}
return false;
}
public boolean updateAny() throws ContentDecodingException, IOException {
return updateAny(SystemConfiguration.getDefaultTimeout());
}
/**
* Calls update(long) with the default timeout SystemConfiguration.getDefaultTimeout().
* @return see update(long).
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public boolean update() throws ContentDecodingException, IOException {
return update(SystemConfiguration.getDefaultTimeout());
}
/**
* Load data into object. If name is versioned, load that version. If
* name is not versioned, look for latest version.
* @param name Name of object to read.
* @param publisher Desired publisher, or null for any.
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public boolean update(ContentName name, PublisherPublicKeyDigest publisher) throws ContentDecodingException, IOException {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "Updating object to {0}.", name);
CCNVersionedInputStream is = new CCNVersionedInputStream(name, publisher, _handle);
return update(is);
}
/**
* Load a stream starting with a specific object.
* @param object
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public boolean update(ContentObject object) throws ContentDecodingException, IOException {
CCNInputStream is = new CCNInputStream(object, getInputStreamFlags(), _handle);
setInputStreamProperties(is);
is.seek(0); // in case it wasn't the first segment
return update(is);
}
/**
* Updates the object from a CCNInputStream or one of its subclasses. Used predominantly
* by internal methods, most clients should use update() or update(long). Exposed for
* special-purpose use and experimentation.
* @param inputStream Stream to read object from.
* @return true if an update found, false if not.
* @throws ContentDecodingException if there is a problem decoding the object.
* @throws IOException if there is an error setting up network backing store.
*/
public synchronized boolean update(CCNInputStream inputStream) throws ContentDecodingException, IOException {
// Allow subclasses to modify input stream processing prior to first read.
setInputStreamProperties(inputStream);
Tuple<ContentName, byte []> nameAndVersion = null;
try {
if (inputStream.isGone()) {
if (Log.isLoggable(Log.FAC_IO, Level.FINE))
Log.fine(Log.FAC_IO, "Reading from GONE stream: {0}", inputStream.getBaseName());
_data = null;
// This will have a final version and a segment
nameAndVersion = VersioningProfile.cutTerminalVersion(inputStream.deletionInformation().name());
_currentPublisher = inputStream.deletionInformation().signedInfo().getPublisherKeyID();
_currentPublisherKeyLocator = inputStream.deletionInformation().signedInfo().getKeyLocator();
_available = true;
_isGone = true;
_isDirty = false;
_lastSaved = digestContent();
} else {
super.update(inputStream);
nameAndVersion = VersioningProfile.cutTerminalVersion(inputStream.getBaseName());
_currentPublisher = inputStream.publisher();
_currentPublisherKeyLocator = inputStream.publisherKeyLocator();
_isGone = false;
}
_firstSegment = inputStream.getFirstSegment(); // preserve first segment
} catch (NoMatchingContentFoundException nme) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "NoMatchingContentFoundException in update from input stream {0}, timed out before data was available.", inputStream.getBaseName());
nameAndVersion = VersioningProfile.cutTerminalVersion(inputStream.getBaseName());
_baseName = nameAndVersion.first();
// used to fire off an updateInBackground here, to hopefully get a second
// chance on scooping up the content. But that seemed likely to confuse
// people and leave the object in an undetermined state. So allow caller
// to manage that themselves.
// not an error state, merely a not ready state.
return false;
} catch (LinkCycleException lce) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "Link cycle exception: {0}", lce.getMessage());
setError(lce);
throw lce;
}
_baseName = nameAndVersion.first();
_currentVersionComponent = nameAndVersion.second();
_currentVersionName = null; // cached if used
_dereferencedLink = inputStream.getDereferencedLink(); // gets stack of links used, if any
clearError();
// Signal readers.
newVersionAvailable(false);
return true;
}
/**
* Update this object in the background -- asynchronously. This call updates the
* object a single time, after the first update (the requested version or the
* latest version), the object will not self-update again unless requested.
* To use, create an object using a write constructor, setting the data field
* to null. Then call updateInBackground() to retrieve the object's data asynchronously.
* To wait on data arrival, call either waitForData() or wait() on the object itself.
* @throws IOException
*/
public void updateInBackground() throws IOException {
updateInBackground(false);
}
/**
* Update this object in the background -- asynchronously.
* To use, create an object using a write constructor, setting the data field
* to null. Then call updateInBackground() to retrieve the object's data asynchronously.
* To wait for an update to arrive, call wait() on this object itself.
* @param continuousUpdates If true, updates the
* object continuously to the latest version available, a single time if it is false.
* @throws IOException
*/
public void updateInBackground(boolean continuousUpdates) throws IOException {
if (null == _baseName) {
throw new IllegalStateException("Cannot retrieve an object without giving a name!");
}
// Look for latest version.
updateInBackground(getVersionedName(), continuousUpdates, null);
}
public void updateInBackground(ContentName latestVersionKnown, boolean continuousUpdates) throws IOException {
updateInBackground(latestVersionKnown, continuousUpdates, null);
}
public void updateInBackground(boolean continuousUpdates, UpdateListener listener) throws IOException {
updateInBackground(getVersionedName(), continuousUpdates, listener);
}
/**
* Update this object in the background -- asynchronously.
* To use, create an object using a write constructor, setting the data field
* to null. Then call updateInBackground() to retrieve the object's data asynchronously.
* To wait for an update to arrive, call wait() on this object itself.
* @param latestVersionKnown the name of the latest version we know of, or an unversioned
* name if no version known
* @param continuousUpdates If true, updates the
* object continuously to the latest version available, a single time if it is false.
* @throws IOException
*/
public synchronized void updateInBackground(ContentName latestVersionKnown, boolean continuousUpdates, UpdateListener listener) throws IOException {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: getting latest version after {0} in background.", latestVersionKnown);
cancelInterest();
if (null != listener) {
addListener(listener);
}
synchronized (_updater) {
_continuousUpdates = continuousUpdates;
}
Interest currentInterest = VersioningProfile.firstBlockLatestVersionInterest(latestVersionKnown, null);
synchronized (_updater) {
_currentInterest = currentInterest;
}
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: initial interest: {0}", _currentInterest);
_handle.expressInterest(_currentInterest, this);
}
/**
* Cancel an outstanding updateInBackground().
*/
public synchronized void cancelInterest() {
synchronized (_updater) {
_continuousUpdates = false;
if (null != _currentInterest) {
_handle.cancelInterest(_currentInterest, this);
}
}
}
public synchronized void addListener(UpdateListener listener) {
if (null == _updateListeners) {
_updateListeners = new HashSet<UpdateListener>();
} else if (_updateListeners.contains(listener)) {
return; // don't re-add
}
_updateListeners.add(listener);
}
/**
* Does this object already have this listener. Uses Object.equals
* for comparison; so will only say yes if it has this *exact* listener
* instance already registered.
* @param listener
* @return
*/
public synchronized boolean hasListener(UpdateListener listener) {
if (null == _updateListeners) {
return false;
}
return (_updateListeners.contains(listener));
}
public void removeListener(UpdateListener listener) {
if (null == _updateListeners)
return;
synchronized (this) {
_updateListeners.remove(listener);
}
}
public void clearListeners() {
if (null == _updateListeners)
return;
synchronized(_updateListeners) {
_updateListeners.clear();
}
}
/**
* Save to existing name, if content is dirty. Update version.
* This is the default form of save -- if the object has been told to use
* a repository backing store, by either giving it a repository flow controller,
* calling saveToRepository() on it for its first save, or specifying false
* to a constructor that allows a raw argument, it will save to a repository.
* Otherwise will perform a raw save.
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
public boolean save() throws ContentEncodingException, IOException {
return saveInternal(null, false, null);
}
/**
* Method for CCNFilterListeners to save an object in response to an Interest
* callback. An Interest has already been received, so the object can output
* one ContentObject as soon as one is ready. Ideally this Interest will have
* been received on the CCNHandle the object is using for output. If the object
* is not dirty, it will not be saved, and the Interest will not be consumed.
* If the Interest does not match this object, the Interest will not be consumed;
* it is up to the caller to ensure that the Interest would be matched by writing
* this object. (If the Interest doesn't match, no initial block will be output
* even if the object is saved; the object will wait for matching Interests prior
* to writing its blocks.)
*/
public boolean save(Interest outstandingInterest) throws ContentEncodingException, IOException {
return saveInternal(null, false, outstandingInterest);
}
/**
* Save to existing name, if content is dirty. Saves to specified version.
* This is the default form of save -- if the object has been told to use
* a repository backing store, by either giving it a repository flow controller,
* calling saveToRepository() on it for its first save, or specifying false
* to a constructor that allows a raw argument, it will save to a repository.
* Otherwise will perform a raw save.
* @param version Version to save to.
* @return true if object was saved, false if it was not (if it was not dirty).
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
public boolean save(CCNTime version) throws ContentEncodingException, IOException {
return saveInternal(version, false, null);
}
/**
* Save to existing name, if content is dirty. Saves to specified version.
* Method for CCNFilterListeners to save an object in response to an Interest
* callback. An Interest has already been received, so the object can output
* one ContentObject as soon as one is ready. Ideally this Interest will have
* been received on the CCNHandle the object is using for output. If the object
* is not dirty, it will not be saved, and the Interest will not be consumed.
* If the Interest does not match this object, the Interest will not be consumed;
* it is up to the caller to ensure that the Interest would be matched by writing
* this object. (If the Interest doesn't match, no initial block will be output
* even if the object is saved; the object will wait for matching Interests prior
* to writing its blocks.)
*/
public boolean save(CCNTime version, Interest outstandingInterest)
throws ContentEncodingException, IOException {
return saveInternal(version, false, outstandingInterest);
}
/**
* Save content to specific version. Internal form that performs actual save.
* @param version If version is non-null, assume that is the desired
* version. If not, set version based on current time.
* @param gone Are we saving this content as gone or not.
* @return return Returns true if it saved data, false if it thought data was not dirty and didn't
* save.
* TODO allow freshness specification
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
protected synchronized boolean saveInternal(CCNTime version, boolean gone, Interest outstandingInterest)
throws ContentEncodingException, IOException {
if (null == _baseName) {
throw new IllegalStateException("Cannot save an object without giving it a name!");
}
// move object to this name
// need to make sure we get back the actual name we're using,
// even if output stream does automatic versioning
// probably need to refactor save behavior -- right now, internalWriteObject
// either writes the object or not; we need to only make a new name if we do
// write the object, and figure out if that's happened. Also need to make
// parent behavior just write, put the dirty check higher in the state.
if (!gone && !isDirty()) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "Object not dirty. Not saving.");
return false;
}
if (!gone && (null == _data)) {
// skip some of the prep steps that have side effects rather than getting this exception later from superclass
throw new InvalidObjectException("No data to save!");
}
// Create the flow controller, if we haven't already.
createFlowController();
// This is the point at which we care if we don't have a flow controller
if (null == _flowControl) {
throw new IOException("Cannot create flow controller! Specified save type is " + _saveType + "!");
}
// Handle versioning ourselves to make name handling easier. VOS should respect it.
// We might have been handed a _baseName that was versioned. For most general behavior,
// have to treat it as a normal name and that we are supposed to put our own version
// underneath it. To save as a specific version, need to use save(version).
ContentName name = new ContentName(_baseName, version == null ? CCNTime.now() : version);
// DKS if we add the versioned name, we don't handle get latest version.
// We re-add the baseName here in case an update has changed it.
// TODO -- perhaps disallow updates for unrelated names.
if (_FCIsOurs)
_flowControl.addNameSpace(_baseName);
if (!gone) {
// CCNVersionedOutputStream will version an unversioned name.
// If it gets a versioned name, will respect it.
// This will call startWrite on the flow controller.
//
// Note that we must use the flow controller given to us as opposed to letting
// the OutputStream create its own. This is because there may be dependencies from the
// caller on the specific flow controller - the known case is the flow controller is a
// CCNFlowServer which requires that the flow controller retain its objects after writing
// them. A standard FC would cause the objects to be lost.
CCNVersionedOutputStream cos = new CCNVersionedOutputStream(name, _keyLocator, _publisher, contentType(), _keys, _flowControl);
cos.setFreshnessSeconds(_freshnessSeconds);
if (null != outstandingInterest) {
cos.addOutstandingInterest(outstandingInterest);
}
save(cos); // superclass stream save. calls flush but not close on a wrapping
// digest stream; want to make sure we end up with a single non-MHT signed
// segment and no header on small objects
cos.close();
// Grab digest and segment number after close because for short objects there may not be
// a segment generated until the close
_firstSegment = cos.getFirstSegment();
} else {
// saving object as gone, currently this is always one empty segment so we don't use an OutputStream
ContentName segmentedName = SegmentationProfile.segmentName(name, SegmentationProfile.BASE_SEGMENT );
byte [] empty = new byte[0];
byte [] finalBlockID = SegmentationProfile.getSegmentNumberNameComponent(SegmentationProfile.BASE_SEGMENT);
ContentObject goneObject =
ContentObject.buildContentObject(segmentedName, ContentType.GONE, empty, _publisher, _keyLocator, null, finalBlockID);
// The segmenter in the stream does an addNameSpace of the versioned name. Right now
// this not only adds the prefix (ignored) but triggers the repo start write.
_flowControl.addNameSpace(name);
_flowControl.startWrite(name, Shape.STREAM); // Streams take care of this for the non-gone case.
_flowControl.put(goneObject);
_firstSegment = goneObject;
_flowControl.beforeClose();
_flowControl.afterClose();
_lastSaved = GONE_OUTPUT;
}
_currentPublisher = _firstSegment.signedInfo().getPublisherKeyID();
_currentPublisherKeyLocator = _firstSegment.signedInfo().getKeyLocator();
_currentVersionComponent = name.lastComponent();
_currentVersionName = name;
setDirty(false);
_available = true;
// We have completed our save and don't know when or if another save may occur so don't keep
// ourselves registered with ccnd. That could cause interests to be unnecessarily or incorrectly
// forwarded to us during the dormant period.
if (_FCIsOurs)
_flowControl.removeNameSpace(_baseName);
newVersionAvailable(true);
if (Log.isLoggable(Log.FAC_IO, Level.FINEST))
Log.finest(Log.FAC_IO, "Saved object {0} publisher {1} key locator {2}", name, _currentPublisher, _currentPublisherKeyLocator);
return true;
}
/**
* Convenience method to the data and save it in a single operation.
* @param data new data for object, set with setData
* @return
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
public boolean save(E data) throws ContentEncodingException, IOException {
return save(null, data);
}
/**
* Convenience method to the data and save it as a particular version in a single operation.
* @param version the desired version
* @param data new data for object, set with setData
* @return
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
public synchronized boolean save(CCNTime version, E data) throws ContentEncodingException, IOException {
setData(data);
return save(version);
}
/*
* Methods to save from handlers. We probably don't want to wait in general to do a save from a handler
* but if the save is to a respository (and we really can't tell) we definitely can't do that because we
* have to wait for a response from the repo which only the handler can receive.
*/
/**
* Do a #save from a callback
*/
public void saveLater() {
doSave(null, false, null, false);
}
/**
* Do a #save from a callback
* @param outstandingInterest
*/
public void saveLater(Interest outstandingInterest) {
doSave(null, false, null, false);
}
/**
* Do a #save followed by a close of the network object from a callback
*/
public void saveLaterWithClose() {
doSave(null, false, null, true);
}
/**
* Do a #save followed by a close of the network object from a callback
* @param outstandingInterest
*/
public void saveLaterWithClose(Interest outstandingInterest) {
doSave(null, false, outstandingInterest, true);
}
/**
* Deprecated; use either object defaults or setRepositorySave() to indicate writes
* should go to a repository, then call save() to write.
* If raw=true or DEFAULT_RAW=true specified, this must be the first call to save made
* for this object to force repository storage (overriding default).
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
@Deprecated
public synchronized boolean saveToRepository(CCNTime version) throws ContentEncodingException, IOException {
if (null == _baseName) {
throw new IllegalStateException("Cannot save an object without giving it a name!");
}
setSaveType(SaveType.REPOSITORY);
return save(version);
}
/**
* Deprecated; use either object defaults or setRepositorySave() to indicate writes
* should go to a repository, then call save() to write.
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
@Deprecated
public boolean saveToRepository() throws ContentEncodingException, IOException {
return saveToRepository((CCNTime)null);
}
/**
* Deprecated; use either object defaults or setRepositorySave() to indicate writes
* should go to a repository, then call save() to write.
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
@Deprecated
public boolean saveToRepository(E data) throws ContentEncodingException, IOException {
return saveToRepository(null, data);
}
/**
* Deprecated; use either object defaults or setRepositorySave() to indicate writes
* should go to a repository, then call save() to write.
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
@Deprecated
public synchronized boolean saveToRepository(CCNTime version, E data) throws ContentEncodingException, IOException {
setData(data);
return saveToRepository(version);
}
/**
* Save this object as GONE. Intended to mark the latest version, rather
* than a specific version as GONE. So for now, require that name handed in
* is *not* already versioned; throw an IOException if it is.
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
public synchronized boolean saveAsGone() throws ContentEncodingException, IOException {
return saveAsGone(null);
}
/**
* For use by CCNFilterListeners, saves a GONE object and emits an initial
* block in response to an already-received Interest.
* Save this object as GONE. Intended to mark the latest version, rather
* than a specific version as GONE. So for now, require that name handed in
* is *not* already versioned; throw an IOException if it is.
* @throws IOException
*/
public synchronized boolean saveAsGone(Interest outstandingInterest)
throws ContentEncodingException, IOException {
if (null == _baseName) {
throw new IllegalStateException("Cannot save an object without giving it a name!");
}
_data = null;
_isGone = true;
setDirty(true);
return saveInternal(null, true, outstandingInterest);
}
/**
* Deprecated; use either object defaults or setRepositorySave() to indicate writes
* should go to a repository, then call save() to write.
* If raw=true or DEFAULT_RAW=true specified, this must be the first call to save made
* for this object.
* @throws ContentEncodingException if there is an error encoding the content
* @throws IOException if there is an error reading the content from the network
*/
@Deprecated
public synchronized boolean saveToRepositoryAsGone() throws ContentEncodingException, IOException {
setSaveType(SaveType.REPOSITORY);
return saveAsGone();
}
/**
* Turn off flow control for this object. Warning - calling this risks packet drops. It should only
* be used for tests or other special circumstances in which
* you "know what you are doing".
*/
public synchronized void disableFlowControl() {
if (null != _flowControl)
_flowControl.disable();
_disableFlowControlRequest = true;
}
/**
* Used to signal waiters and listeners that a new version is available.
* @param wasSave is a new version available because we were saved, or because
* we found a new version on the network?
*/
protected void newVersionAvailable(boolean wasSave) {
if (Log.isLoggable(Log.FAC_IO, Level.FINER)) {
Log.finer(Log.FAC_IO, "newVersionAvailable: New version of object available: {0}", getVersionedName());
}
// by default signal all waiters
this.notifyAll();
// and any registered listeners
if (null != _updateListeners) {
for (UpdateListener listener : _updateListeners) {
listener.newVersionAvailable(this, wasSave);
}
}
}
/**
* Will return immediately if this object already has data, otherwise
* will wait indefinitely for the initial data to appear.
*/
public void waitForData() {
if (available())
return;
synchronized (this) {
while (!available()) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
}
/**
* Will wait for data to arrive. Callers should use
* available() to determine whether data has arrived or not.
* If data already available, will return immediately (in other
* words, this is only useful to wait for the first update to
* an object, or to ensure that it has data). To wait for later
* updates, call wait() on the object itself.
* @param timeout In milliseconds. If 0, will wait forever (if data does not arrive).
*/
public void waitForData(long timeout) {
if (available())
return;
synchronized (this) {
long startTime = System.currentTimeMillis();
boolean keepTrying = true;
while (!available() && keepTrying) {
// deal with spontaneous returns from wait()
try {
long waitTime = timeout - (System.currentTimeMillis() - startTime);
if (waitTime > 0)
wait(waitTime);
else
keepTrying = false;
} catch (InterruptedException ie) {}
}
}
}
public boolean isGone() {
return _isGone;
}
@Override
protected byte [] digestContent() throws IOException {
if (isGone()) {
return GONE_OUTPUT;
}
return super.digestContent();
}
@Override
protected synchronized E data() throws ContentNotReadyException, ContentGoneException, ErrorStateException {
if (isGone()) {
throw new ContentGoneException("Content is gone!");
}
return super.data();
}
@Override
public synchronized void setData(E newData) {
_isGone = false; // clear gone, even if we're setting to null; only saveAsGone can set as gone
super.setData(newData);
}
public synchronized CCNTime getVersion() throws IOException {
if (isSaved())
return VersioningProfile.getVersionComponentAsTimestamp(getVersionComponent());
return null;
}
public synchronized VersionNumber getVersionNumber() throws IOException {
if (isSaved())
return new VersionNumber(getVersionComponent());
return null;
}
public synchronized ContentName getBaseName() {
return _baseName;
}
public CCNHandle getHandle() {
return _handle;
}
public synchronized byte [] getVersionComponent() throws IOException {
if (isSaved())
return _currentVersionComponent;
return null;
}
/**
* Returns the first segment number for this object.
* @return The index of the first segment of stream data or null if no segments generated yet.
*/
public Long firstSegmentNumber() {
if (null != _firstSegment) {
return SegmentationProfile.getSegmentNumber(_firstSegment.name());
} else {
return null;
}
}
/**
* Returns the digest of the first segment of this object which may be used
* to help identify object instance unambiguously.
*
* @return The digest of the first segment of this object if available, null otherwise
*/
public byte[] getFirstDigest() {
// Do not attempt to force update here to leave control over whether reading
// or writing with the object creator. The return value may be null if the
// object is not in a state of having a first segment
if (null != _firstSegment) {
return _firstSegment.digest();
} else {
return null;
}
}
/**
* Returns the first segment of this object.
*/
public ContentObject getFirstSegment() {
return _firstSegment;
}
/**
* If we traversed a link to get this object, make it available.
*/
public synchronized LinkObject getDereferencedLink() { return _dereferencedLink; }
/**
* Use only if you know what you are doing.
*/
public synchronized void setDereferencedLink(LinkObject dereferencedLink) { _dereferencedLink = dereferencedLink; }
/**
* Add a LinkObject to the stack we had to dereference to get here.
*/
public synchronized void pushDereferencedLink(LinkObject dereferencedLink) {
if (null == dereferencedLink) {
return;
}
if (null != _dereferencedLink) {
if (null != dereferencedLink.getDereferencedLink()) {
if (Log.isLoggable(Log.FAC_IO, Level.WARNING)) {
Log.warning(Log.FAC_IO, "Merging two link stacks -- {0} already has a dereferenced link from {1}. Behavior unpredictable.",
dereferencedLink.getVersionedName(), dereferencedLink.getDereferencedLink().getVersionedName());
}
}
dereferencedLink.pushDereferencedLink(_dereferencedLink);
}
setDereferencedLink(dereferencedLink);
}
/**
* If the object has been saved or read from the network, returns the (cached) versioned
* name. Otherwise returns the base name.
* @return
*/
public synchronized ContentName getVersionedName() {
try {
if (isSaved()) {
if ((null == _currentVersionName) && (null != _currentVersionComponent)) // cache; only read lock necessary
_currentVersionName = new ContentName(_baseName, _currentVersionComponent);
return _currentVersionName;
}
return getBaseName();
} catch (IOException e) {
if (Log.isLoggable(Log.FAC_IO, Level.WARNING))
Log.warning(Log.FAC_IO, "Invalid state for object {0}, cannot get current version name: {1}", getBaseName(), e);
return getBaseName();
}
}
public synchronized PublisherPublicKeyDigest getContentPublisher() throws IOException {
if (isSaved())
return _currentPublisher;
return null;
}
public synchronized KeyLocator getPublisherKeyLocator() throws IOException {
if (isSaved())
return _currentPublisherKeyLocator;
return null;
}
/**
* Change the publisher information we use when we sign commits to this object.
* Takes effect on the next save(). Useful for objects created with a read constructor,
* but who want to override default publisher information.
* @param signingKey indicates the identity we want to use to sign future writes to this
* object. If null, will default to key manager's (user's) default key.
* @param locator the key locator (key lookup location) information to attach to future
* writes to this object. If null, will be the default value associated with the
* chosen signing key.
*/
public synchronized void setOurPublisherInformation(PublisherPublicKeyDigest publisherIdentity, KeyLocator keyLocator) {
_publisher = publisherIdentity;
_keyLocator = keyLocator;
}
public void setTimeout(int timeout) {
try {
createFlowController();
getFlowControl().setTimeout(timeout);
} catch (IOException e) {}
}
public synchronized Interest handleContent(ContentObject co, Interest interest) {
_updater.add(co);
return null;
}
/**
* Do queued updates in background
*/
protected class Updater {
protected Queue<ContentObject> _queue = new ConcurrentLinkedQueue<ContentObject>();
protected CCNContentHandler _handler = null;
protected boolean _isRunning = false;
protected Updater(CCNContentHandler handler) {
_handler = handler;
}
/**
* Add a content object to the queue for processing. If we aren't running a processing
* thread right now, start one.
*
* @param co
*/
protected void add(ContentObject co) {
_queue.add(co);
if (!_isRunning) {
_isRunning = true;
SystemConfiguration._systemThreadpool.execute(new BackgroundUpdater());
}
}
protected class BackgroundUpdater implements Runnable {
public void run() {
while (true) {
try {
boolean hasNewVersion = false;
byte [][] excludes = null;
ContentObject co = null;
co = _queue.poll();
if (null == co) {
_isRunning = false;
return;
}
try {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: " + _currentInterest + " retrieved " + co.name());
if (VersioningProfile.startsWithLaterVersionOf(co.name(), _currentInterest.name())) {
// OK, we have something that is a later version of our desired object.
// We're not sure it's actually the first content segment.
hasNewVersion = true;
if (VersioningProfile.isVersionedFirstSegment(_currentInterest.name(), co, null)) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: Background updating of {0}, got first segment: {1}", getVersionedName(), co.name());
// Streams assume caller has verified. So we verify here.
// TODO add support for settable verifiers
if (!_verifier.verify(co)) {
if (Log.isLoggable(Log.FAC_SIGNING, Level.WARNING)) {
Log.warning(Log.FAC_SIGNING, "CCNNetworkObject: content object received from background update did not verify! Ignoring object: {0}", co.fullName());
}
hasNewVersion = false;
// TODO -- exclude this one by digest, otherwise we're going
// to get it back! For now, just copy the top-level part of GLV
// behavior and exclude this version component. This isn't the right
// answer, malicious objects can exclude new versions. But it's not clear
// if the right answer is to do full gLV here and let that machinery
// handle things, pulling potentially multiple objects in a callback,
// or we just have to wait for issue #100011, and the ability to selectively
// exclude content digests.
excludes = new byte [][]{co.name().component(_currentInterest.name().count())};
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: got content for {0} that doesn't verify ({1}), excluding bogus version {2} as temporary workaround FIX WHEN POSSIBLE",
_currentInterest.name(), co.fullName(), Component.printURI(excludes[0]));
} else {
update(co);
}
} else {
// Have something that is not the first segment, like a repo write or a later segment. Go back
// for first segment.
ContentName latestVersionName = co.name().cut(_currentInterest.name().count() + 1);
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent (network object): Have version information, now querying first segment of {0}", latestVersionName);
// This should verify the first segment when we get it.
update(latestVersionName, co.signedInfo().getPublisherKeyID());
}
} else {
excludes = new byte [][]{co.name().component(_currentInterest.name().count() - 1)};
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: got content for {0} that doesn't match: {1}", _currentInterest.name(), co.name());
}
} catch (IOException ex) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: Exception {0}: {1} attempting to update based on object : {2}", ex.getClass().getName(), ex.getMessage(), co.name());
if (Log.isLoggable(Log.FAC_IO, Level.FINE)) {
Log.logStackTrace(Log.FAC_IO, Level.FINE, ex);
}
// alright, that one didn't work, try to go on.
}
if (hasNewVersion) {
boolean continuousUpdates = false;
synchronized (_updater) {
continuousUpdates = _continuousUpdates;
}
if (continuousUpdates) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: got a new version, continuous updates, calling updateInBackground recursively then returning null.");
updateInBackground(true);
} else {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: got a new version, not continuous updates, returning null.");
}
// the updates above call newVersionAvailable
} else {
synchronized (_updater) {
if (null != excludes) {
_currentInterest.exclude().add(excludes);
}
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: no new version, returning new interest for expression: {0}", _currentInterest);
_handle.expressInterest (_currentInterest, _handler);
}
}
} catch (IOException ex) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "updateInBackground: handleContent: Exception {0}: {1} attempting to request further updates : {2}", ex.getClass().getName(), ex.getMessage(), _currentInterest);
if (Log.isLoggable(Log.FAC_IO, Level.FINE)) {
Log.logStackTrace(Log.FAC_IO, Level.FINE, ex);
}
}
}
}
}
}
/**
* Do saveInternal in background - used to implement saveLater...
*/
protected void doSave(CCNTime version, boolean gone, Interest outstandingInterest, boolean doClose) {
SystemConfiguration._systemThreadpool.execute(new BackgroundSaver(version, gone, outstandingInterest, doClose));
}
protected class BackgroundSaver implements Runnable {
protected boolean _gone = false;
protected CCNTime _version = null;
protected Interest _outstandingInterest = null;
protected boolean _doClose = false;
public BackgroundSaver(CCNTime version, boolean gone, Interest outstandingInterest, boolean doClose) {
_version = version;
_gone = gone;
_outstandingInterest = outstandingInterest;
_doClose = doClose;
}
public void run() {
try {
saveInternal(_version, _gone, _outstandingInterest);
if (_doClose)
close();
} catch (Exception e) {
Log.logStackTrace(Log.FAC_IO, Level.WARNING, e);
}
}
}
/**
* Subclasses that need to write an object of a particular type can override.
* DKS TODO -- verify type on read, modulo that ENCR overrides everything.
* @return
*/
public ContentType contentType() { return ContentType.DATA; }
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ ((_baseName == null) ? 0 : _baseName.hashCode());
result = prime
* result
+ ((_currentPublisher == null) ? 0 : _currentPublisher
.hashCode());
result = prime * result + Arrays.hashCode(_currentVersionComponent);
return result;
}
@SuppressWarnings("unchecked") // cast to obj<E>
@Override
public boolean equals(Object obj) {
// should hold read lock?
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
CCNNetworkObject<E> other = (CCNNetworkObject<E>) obj;
if (_baseName == null) {
if (other._baseName != null)
return false;
} else if (!_baseName.equals(other._baseName))
return false;
if (_currentPublisher == null) {
if (other._currentPublisher != null)
return false;
} else if (!_currentPublisher.equals(other._currentPublisher))
return false;
if (!Arrays.equals(_currentVersionComponent,
other._currentVersionComponent))
return false;
return true;
}
@Override
public String toString() {
try {
if (isSaved()) {
return getVersionedName() + ": " + (isGone() ? "GONE" : "\nData:" + data()) + "\n Publisher: " +
getContentPublisher() + "\n Publisher KeyLocator: " + getPublisherKeyLocator() + "\n";
} else if (available()) {
return getBaseName() + " (unsaved): " + data();
} else {
return getBaseName() + " (unsaved, no data)";
}
} catch (IOException e) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "Unexpected exception retrieving object information: {0}", e);
return getBaseName() + ": unexpected exception " + e;
}
}
}