/*
* 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.impl.repo;
import static org.ccnx.ccn.profiles.CommandMarker.COMMAND_MARKER_REPO_START_WRITE;
import static org.ccnx.ccn.protocol.Component.NONCE;
import java.io.IOException;
import java.util.HashSet;
import java.util.NoSuchElementException;
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.config.SystemConfiguration;
import org.ccnx.ccn.impl.CCNFlowControl;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.impl.support.ConcurrencyUtils.Waiter;
import org.ccnx.ccn.io.content.ContentDecodingException;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.SignedInfo.ContentType;
/**
* Handle repo specialty start/end protocol
*
* Needs to be able to handle multiple clients. Currently due to limitations in close,
* to do this requires that clients above close their streams in order when multiple
* streams are using the same FC.
*
* @see CCNFlowControl
* @see RepositoryInterestHandler
*/
public class RepositoryFlowControl extends CCNFlowControl implements CCNContentHandler {
// Outstanding interests output by this FlowController. Currently includes only the original
// start write request.
protected HashSet<Interest> _writeInterests = new HashSet<Interest>();
protected boolean localRepo = true;
// Queue of current clients of this RepositoryFlowController
// Implemented as a queue so we can decide which one to close on calls to beforeClose/afterClose
protected Queue<Client> _clients = new ConcurrentLinkedQueue<Client>();
/**
* Handles packets received from the repository after the start write request. It's looking
* for a RepoInfo packet indicating a repository has responded.
*/
public Interest handleContent(ContentObject co,
Interest interest) {
if (Log.isLoggable(Log.FAC_REPO, Level.INFO))
Log.info(Log.FAC_REPO, "handleContent: got potential repo message: {0}", co.name());
if (co.signedInfo().getType() != ContentType.DATA)
return null;
RepositoryInfo repoInfo = new RepositoryInfo();
try {
repoInfo.decode(co.content());
switch (repoInfo.getType()) {
case INFO:
for (Client client : _clients) {
if (client._name.isPrefixOf(co.name())) {
if (Log.isLoggable(Log.FAC_REPO, Level.FINE))
Log.fine(Log.FAC_REPO, "Marked client {0} initialized", client._name);
synchronized (this) {
client._initialized = true;
notifyAll();
}
}
}
break;
default:
break;
}
} catch (ContentDecodingException e) {
Log.info(Log.FAC_REPO, "ContentDecodingException parsing RepositoryInfo: {0} from content object {1}, skipping.", e.getMessage(), co.name());
}
return null;
}
/**
* Preserves information about our clients
*/
protected class Client {
protected ContentName _name;
protected Shape _shape;
protected boolean _initialized = false;
public Client(ContentName name, Shape shape) {
_name = name;
_shape = shape;
}
public ContentName name() { return _name; }
public Shape shape() { return _shape; }
}
/**
* @param handle a CCNHandle - if null one is created
* @throws IOException if library is null and a new CCNHandle can't be created
*/
public RepositoryFlowControl(CCNHandle handle) throws IOException {
super(handle);
}
/**
* constructor to allow the repo flow controller to set the scope for the start write interest
*
* @param handle a CCNHandle - if null, one is created
* @param local boolean to determine if a general start write, or one with the scope set to one.
* A scope set to one will limit the write to a repo on the local device
* @throws IOException
*/
public RepositoryFlowControl(CCNHandle handle, boolean local) throws IOException {
super(handle);
localRepo = local;
}
/**
* @param name an initial namespace for this stream
* @param handle a CCNHandle - if null one is created
* @throws IOException if handle is null and a new CCNHandle can't be created
*/
public RepositoryFlowControl(ContentName name, CCNHandle handle) throws IOException {
super(name, handle);
}
/**
* @param name an initial namespace for this stream
* @param handle a CCNHandle - if null one is created
* @param local boolean to determine if a general start write, or one with the scope set to one.
* A scope set to one will limit the write to a repo on the local device
* @throws IOException if handle is null and a new CCNHandle can't be created
*/
public RepositoryFlowControl(ContentName name, CCNHandle handle, boolean local) throws IOException {
super(name, handle);
localRepo = local;
}
/**
* @param name an initial namespace for this stream
* @param handle a CCNHandle - if null one is created
* @param shape shapes are not currently implemented and may be deprecated. The only currently defined
* shape is "Shape.STREAM"
* @throws IOException if handle is null and a new CCNHandle can't be created
* @see CCNFlowControl
*/
public RepositoryFlowControl(ContentName name, CCNHandle handle, Shape shape) throws IOException {
super(name, handle);
}
/**
* @param name an initial namespace for this stream
* @param handle a CCNHandle - if null one is created
* @param shape shapes are not currently implemented and may be deprecated. The only currently defined
* shape is "Shape.STREAM"
* @param local boolean to determine if a general start write, or one with the scope set to one.
* A scope set to one will limit the write to a repo on the local device
* @throws IOException if handle is null and a new CCNHandle can't be created
* @see CCNFlowControl
*/
public RepositoryFlowControl(ContentName name, CCNHandle handle, Shape shape, boolean local) throws IOException {
super(name, handle);
localRepo = local;
}
@Override
/**
* Send out a start write request to any listening repositories and wait for a response.
*
* @param name the basename of the stream to start
* @param shape currently ignored - can only be Shape.STREAM
* @throws IOException if there is no response from a repository
*/
public void startWrite(ContentName name, Shape shape) throws IOException {
if (Log.isLoggable(Log.FAC_REPO, Level.INFO))
Log.info(Log.FAC_REPO, "RepositoryFlowControl.startWrite called for name {0}, shape {1}", name, shape);
Client client = new Client(name, shape);
_clients.add(client);
// A nonce is used because if we tried to write data with the same name more than once, we could retrieve the
// the previous answer from the cache, and the repo would never be informed of our start write.
ContentName repoWriteName = new ContentName(name, COMMAND_MARKER_REPO_START_WRITE, NONCE);
Interest writeInterest = new Interest(repoWriteName);
if (localRepo || SystemConfiguration.FC_LOCALREPOSITORY) {
//this is meant to be written to a local repository, not any/multiple connected repos
writeInterest.scope(1);
}
_handle.expressInterest(writeInterest, this);
synchronized (this) {
_writeInterests.add(writeInterest);
}
//Wait for information to be returned from a repo
try {
new Waiter(getTimeout()) {
@Override
protected boolean check(Object o, Object check) throws Exception {
return ((Client)check)._initialized;
}
}.wait(this, client);
} catch (Exception e) {
Log.warning(Log.FAC_REPO, e.getClass() + " : " + e.getMessage());
}
synchronized (this) {
if (!client._initialized) {
_clients.remove();
Log.warning(Log.FAC_REPO, "No response from a repository, cannot add name space : " + name);
throw new IOException("No response from a repository for " + name);
}
}
}
/**
* Called after close has completed a flush
*/
@Override
public void afterClose() throws IOException {
try {
_clients.remove();
} catch (NoSuchElementException nse) {}
// super.afterClose() calls waitForPutDrain.
super.afterClose();
// DKS don't actually want to cancel all the interests, only the
// ones relevant to the data we've finished writing.
cancelInterests();
}
/**
* Cancel any outstanding interests on close.
* TODO - since the flow controller may be used by multiple streams we probably want to use Clients to decide
* what interests to cancel.
*/
public void cancelInterests() {
for (Interest writeInterest : _writeInterests){
_handle.cancelInterest(writeInterest, this);
}
}
/**
* Help users determine what type of flow controller this is.
*/
@Override
public SaveType saveType() {
//if the library is overridden with the property or environment variable
//for writing to a local repo, need to return LocalRepo save type
if (SystemConfiguration.FC_LOCALREPOSITORY)
return SaveType.LOCALREPOSITORY;
if (localRepo)
return SaveType.LOCALREPOSITORY;
else
return SaveType.REPOSITORY;
}
}