/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009, 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;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.impl.CCNNetworkManager;
import org.ccnx.ccn.impl.security.keys.BasicKeyManager;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
/**
* The core class encapsulating a Java interface to the CCN network.
* It implements the CCNBase core methods for reading and writing to CCN,
* using an encapsulated CCNNetworkManager to interface with the local
* CCN agent. It encapsulates a KeyManager to interface with the user's
* collection of signing and verification keys. A typical application
* may have one CCNHandle or many; each encapsulates a single connection
* to the local CCN agent.
*
* Once a handle is closed, it cannot be used anymore. It will throw
* an IOException in those cases.
*/
public class CCNHandle implements CCNBase {
protected static CCNHandle _handle = null;
protected final KeyManager _keyManager;
// To give each handle a unique ID so they can be debuged
protected final static AtomicInteger _handleIdCount = new AtomicInteger(0);
protected final int _handleId;
protected final String _handleIdString;
/**
* A CCNNetworkManager embodies a connection to ccnd.
*/
protected final CCNNetworkManager _networkManager;
protected final Object _openLock = new Object();
protected boolean _isOpen = false;
/*
* In order to have a handle automaticaly add a scope to every Interest, we provide a way to set
* a default scope to use, and enable/disable machinery. (The defines for the scope don't belong
* here really, but it seems useful to have them specified.)
*/
protected Integer _scope = disableScope;
public static final Integer disableScope = new Integer(-1);
public static final Integer ccndScope = new Integer(0);
public static final Integer localScope = new Integer(1);
public static final Integer neighborhood = new Integer(2);
/**
* Create a new CCNHandle, opening a new connection to the CCN network
* @return the CCNHandle
* @throws ConfigurationException if there is an issue in the user or system configuration
* which we cannot overcome without outside intervention. See the error message for details.
* @throws IOException if we encounter an error reading system, configuration, or keystore
* data that we expect to be well-formed.
*/
public static CCNHandle open() throws ConfigurationException, IOException {
try {
return new CCNHandle();
} catch (ConfigurationException e) {
Log.severe(Log.FAC_NETMANAGER, "Configuration exception initializing CCN library: " + e.getMessage());
throw e;
} catch (IOException e) {
Log.severe(Log.FAC_NETMANAGER, "IO exception initializing CCN library: " + e.getMessage());
throw e;
}
}
/**
* Create a new CCNHandle, opening a new connection to the CCN network, and
* specifying the KeyManager it should use. Particularly useful in testing, to
* run as if you were a different "user", with a different collection of keys.
* @param keyManager the KeyManager to use
* @return the CCNHandle
* @throws IOException
*/
public static CCNHandle open(KeyManager keyManager) throws IOException {
return new CCNHandle(keyManager);
}
/**
* Returns a static CCNHandle that is made available as a default.
* @return the shared static CCNHandle
*/
public static CCNHandle getHandle() {
if (null != _handle)
return _handle;
try {
return create();
} catch (ConfigurationException e) {
Log.warning(Log.FAC_NETMANAGER, "Configuration exception attempting to create handle: " + e.getMessage());
Log.warningStackTrace(e);
throw new RuntimeException("Error in system configuration. Cannot create handle.",e);
} catch (IOException e) {
Log.warning(Log.FAC_NETMANAGER, "IO exception attempting to create handle: " + e.getMessage());
Log.warningStackTrace(e);
throw new RuntimeException("Error in system IO. Cannot create handle.",e);
}
}
/**
* Internal synchronized creation method
* @return a new CCNHandle
* @throws ConfigurationException if there is an issue in the user or system configuration
* which we cannot overcome without outside intervention. See the error message for details.
* @throws IOException if we encounter an error reading system, configuration, or keystore
* data that we expect to be well-formed.
*/
protected static synchronized CCNHandle create() throws ConfigurationException, IOException {
if (null == _handle) {
KeyManager km = KeyManager.getDefaultKeyManager();
if (km instanceof BasicKeyManager) {
_handle = ((BasicKeyManager) km).handle();
} else {
_handle = new CCNHandle();
}
}
return _handle;
}
/**
* Create a CCNHandle using the specified KeyManager
* @param keyManager the KeyManager to use. cannot be null.
* @throws IOException
*/
protected CCNHandle(KeyManager keyManager) throws IOException {
_handleId = _handleIdCount.incrementAndGet();
_handleIdString = String.format("CCNHandle %d: ", _handleId);
if (null == keyManager) {
throw new IOException(formatMessage("Cannot instantiate handle -- KeyManager is null. Use CCNHandle() constructor to get default KeyManager, or specify one here."));
}
_keyManager = keyManager;
// force initialization of network manager
try {
_networkManager = new CCNNetworkManager(_keyManager);
} catch (IOException ex){
Log.warning(formatMessage("IOException instantiating network manager: " + ex.getMessage()));
Log.warningStackTrace(ex);
throw ex;
}
synchronized(_openLock) {
_isOpen = true;
}
if( Log.isLoggable(Log.FAC_NETMANAGER, Level.INFO) )
Log.info(Log.FAC_NETMANAGER, formatMessage("Handle is now open"));
}
/**
* Create a CCNHandle using the default KeyManager for this user
* @throws ConfigurationException if there is an issue in the user or system configuration
* which we cannot overcome without outside intervention. See the error message for details.
* @throws IOException if we encounter an error reading system, configuration, or keystore
* data that we expect to be well-formed.
*/
protected CCNHandle() throws ConfigurationException, IOException {
this(KeyManager.getDefaultKeyManager());
}
/**
* For testing only
*/
protected CCNHandle(boolean useNetwork) {
_handleId = _handleIdCount.incrementAndGet();
_handleIdString = String.format("CCNHandle %d: ", _handleId);
_networkManager = null;
_keyManager = null;
if( Log.isLoggable(Log.FAC_NETMANAGER, Level.INFO) )
Log.info(Log.FAC_NETMANAGER, formatMessage("Handle is now open (testing only)"));
}
/**
* Retrieve this handle's network manager. Should only be called by low-level
* methods seeking direct access to the network.
*
* If the handle is closed, this will return null.
*
* @return the CCN network manager
*/
public CCNNetworkManager getNetworkManager() {
synchronized(_openLock) {
if( !_isOpen )
return null;
}
return _networkManager;
}
// /**
// * Change the KeyManager this CCNHandle is using.
// * @param keyManager the new KeyManager to use
// */
// public void setKeyManager(KeyManager keyManager) {
// if (null == keyManager) {
// Log.warning(formatMessage("StandardCCNLibrary::setKeyManager: Key manager cannot be null!"));
// throw new IllegalArgumentException(formatMessage("Key manager cannot be null!"));
// }
// _keyManager = keyManager;
// }
/**
* Return the KeyManager we are using.
* @return our current KeyManager
*/
public KeyManager keyManager() { return _keyManager; }
/**
* Get the publisher ID of the default public key we use to sign content
* @return our default publisher ID
*/
public PublisherPublicKeyDigest getDefaultPublisher() {
return keyManager().getDefaultKeyID();
}
/**
* Sets the scope to be set if an Interest doesn't already have one set.
* @param scope Scope -1 disable or if in the range of [0..2]
* @return
*/
public void setScope(Integer scope) throws IOException {
if (this == _handle) {
if (Log.isLoggable(Level.INFO)) Log.info(Log.FAC_NETMANAGER, formatMessage("setScope called on static handle"));
throw new IOException(formatMessage("setScope called on a shared handle."));
}
if (scope == disableScope) {
} else if (scope < 0 || scope > 2) {
if (Log.isLoggable(Level.INFO)) Log.info(Log.FAC_NETMANAGER, formatMessage("setScope scope out of range " + scope));
throw new IOException(formatMessage("setScope called with scope that is out of range [0..2] " + scope));
}
_scope = scope;
if (Log.isLoggable(Level.INFO)) Log.info(Log.FAC_NETMANAGER, formatMessage("setScope set " + scope));
}
/**
* Gets the current value of the default scope.
* @return The current default.
*/
public Integer getScope() throws IOException {
if (this == _handle) {
if (Log.isLoggable(Level.INFO)) Log.info(Log.FAC_NETMANAGER, formatMessage("getScope called on static handle"));
throw new IOException(formatMessage("getScope called on a shared handle."));
}
return _scope;
}
/**
* Helper method wrapped around CCNBase#get(Interest, long)
* @param name name to query for
* @param timeout timeout for get
* @return the object retrieved, or null if timed out
* @throws IOException on error
* @see CCNBase#get(Interest, long)
*/
public ContentObject get(ContentName name, long timeout) throws IOException {
Interest interest = new Interest(name);
return get(interest, timeout);
}
/**
* Helper method wrapped around CCNBase#get(Interest, long)
* @param name name to query for
* @param publisher the desired publisher for the content
* @param timeout timeout for get
* @return the object retrieved, or null if timed out
* @throws IOException on error
* @see CCNBase#get(Interest, long)
*/
public ContentObject get(ContentName name, PublisherPublicKeyDigest publisher, long timeout) throws IOException {
Interest interest = new Interest(name, publisher);
return get(interest, timeout);
}
/**
* Get a single piece of content from CCN. This is a blocking get, it will return
* when matching content is found or it times out, whichever comes first.
* @param interest
* @param timeout
* @return the content object
* @throws IOException
*/
public ContentObject get(Interest interest, long timeout) throws IOException {
while (true) {
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
try {
if (_scope != disableScope) {
if (interest.scope() == null) {
interest.scope(_scope);
}
}
return getNetworkManager().get(interest, timeout);
} catch (InterruptedException e) {}
}
}
/**
* Put a single content object into the network. This is a low-level put,
* and typically should only be called by a flow controller, in response to
* a received Interest. Attempting to write to ccnd without having first
* received a corresponding Interest violates flow balance, and the content
* will be dropped.
* @param co the content object to write. This should be complete and well-formed -- signed and
* so on.
* @return the object that was put if successful, otherwise null.
* @throws IOException
*/
public ContentObject put(ContentObject co) throws IOException {
boolean interrupted = false;
do {
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
try {
if( Log.isLoggable(Level.FINEST) )
Log.finest(Log.FAC_NETMANAGER, formatMessage("Putting content on wire: " + co.name()));
return getNetworkManager().put(co);
} catch (InterruptedException e) {
interrupted = true;
}
} while (interrupted);
return null;
}
/**
* Register a standing interest filter with callback to receive any
* matching interests seen
* @param filter
* @param callbackHandler
*/
public void registerFilter(ContentName filter,
CCNInterestHandler callbackHandler) throws IOException {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("registerFilter " + filter.toString()));
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
getNetworkManager().setInterestFilter(this, filter, callbackHandler);
}
@Deprecated
public void registerFilter(ContentName filter,
CCNFilterListener callbackListener) throws IOException {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("registerFilter " + filter.toString()));
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
getNetworkManager().setInterestFilter(this, filter, callbackListener);
}
/**
* Unregister a standing interest filter
* @param filter
* @param callbackHandler
* @throws IOException if handle is closed
*/
public void unregisterFilter(ContentName filter,
CCNInterestHandler callbackHandler) {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("unregisterFilter " + filter.toString()));
synchronized(_openLock) {
if( !_isOpen ) {
Log.warning(formatMessage("Called unregisterFilter on a closed handle"));
return;
}
}
getNetworkManager().cancelInterestFilter(this, filter, callbackHandler);
}
@Deprecated
public void unregisterFilter(ContentName filter,
CCNFilterListener callbackListener) {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("unregisterFilter " + filter.toString()));
synchronized(_openLock) {
if( !_isOpen ) {
Log.warning(formatMessage("Called unregisterFilter on a closed handle"));
return;
}
}
getNetworkManager().cancelInterestFilter(this, filter, callbackListener);
}
/**
* Query, or express an interest in particular
* content. This request is sent out over the
* CCN to other nodes. On any results, the
* callbackHandler if given, is notified.
* Results may also be cached in a local repository
* for later retrieval by get().
* Get and expressInterest could be implemented
* as a single function that might return some
* content immediately and others by callback;
* we separate the two for now to simplify the
* interface.
*
* Pass it on to the CCNInterestManager to
* forward to the network. Also express it to the
* repositories we manage, particularly the primary.
* Each might generate their own CCNQueryDescriptor,
* so we need to group them together.
* @param interest
* @param handler
*/
public void expressInterest(
Interest interest,
final CCNContentHandler handler) throws IOException {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("expressInterest " + interest.name().toString()));
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
// Will add the interest to the listener.
getNetworkManager().expressInterest(this, interest, handler);
}
public void registerInterest(
Interest interest,
final CCNContentHandler handler) throws IOException {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("expressInterest " + interest.name().toString()));
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
// Will add the interest to the listener.
getNetworkManager().registerInterest(this, interest, handler);
}
@Deprecated
public void expressInterest(
Interest interest,
final CCNInterestListener listener) throws IOException {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("expressInterest " + interest.name().toString()));
synchronized(_openLock) {
if( !_isOpen )
throw new IOException(formatMessage("Handle is closed"));
}
if (_scope != disableScope) {
if (interest.scope() == null) {
interest.scope(_scope);
}
CCNInterestListener myListener = new CCNInterestListener() {
public Interest handleContent(ContentObject data, Interest interest) {
Interest i = listener.handleContent(data, interest);
if (i != null) {
if (_scope != disableScope) {
if (i.scope() == null) {
i.scope(_scope);
}
}
}
return i;
}
};
getNetworkManager().expressInterest(this, interest, myListener);
} else {
// Will add the interest to the listener.
getNetworkManager().expressInterest(this, interest, listener);
}
}
/**
* Cancel this interest.
* @param interest
* @param handler Used to distinguish the same interest
* requested by more than one listener.
*/
public void cancelInterest(Interest interest, CCNContentHandler handler) {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("cancelInterest " + interest.name().toString()));
synchronized(_openLock) {
if( !_isOpen ) {
Log.warning(Log.FAC_NETMANAGER, formatMessage("Called cancelInterest on a closed handle"));
return;
}
}
getNetworkManager().cancelInterest(this, interest, handler);
}
@Deprecated
public void cancelInterest(Interest interest, CCNInterestListener listener) {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("cancelInterest " + interest.name().toString()));
synchronized(_openLock) {
if( !_isOpen ) {
Log.warning(Log.FAC_NETMANAGER, formatMessage("Called cancelInterest on a closed handle"));
return;
}
}
getNetworkManager().cancelInterest(this, interest, listener);
}
/**
* Shutdown the handle and its associated resources
*/
public void close() {
if( Log.isLoggable(Level.FINE) )
Log.fine(Log.FAC_NETMANAGER, formatMessage("Closing handle"));
synchronized(_openLock) {
if( _isOpen ) {
_isOpen = false;
_networkManager.shutdown();
} else {
Log.warning(Log.FAC_NETMANAGER, formatMessage("Handle is already closed. DIAGNOSTIC STACK DUMP."));
Thread.dumpStack();
}
}
synchronized (CCNHandle.class) {
if (_handle == this) {
_handle = null;
}
}
}
/**
* Provide a default verification implementation for users that do not want to
* alter the standard verification process.
* @return a basic ContentVerifier that simply verifies that a piece of content was
* correctly signed by the key it claims to have been published by, implying no
* semantics about the "trustworthiness" of that content or its publisher
*/
public ContentVerifier defaultVerifier() {
return _keyManager.getDefaultVerifier();
}
protected String formatMessage(String message) {
return _handleIdString + message;
}
}