/* * Part of the CCNx Java Library. * * Copyright (C) 2008-2010, 2012, 2013 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.profiles.nameenum; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.logging.Level; import org.bouncycastle.util.Arrays; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.config.SystemConfiguration; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.profiles.VersioningProfile; import org.ccnx.ccn.protocol.CCNTime; import org.ccnx.ccn.protocol.Component; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.ContentNameProvider; /** * Blocking and background interface to name enumeration. This allows a caller to specify a prefix * under which to enumerate available children, and name enumeration to proceed in the background * for as long as desired, providing updates whenever new data is published. * Currently implemented as a wrapper around CCNNameEnumerator, will likely directly aggregate * name enumeration responses in the future. * * @see CCNNameEnumerator * @see BasicNameEnumeratorListener */ public class EnumeratedNameList implements BasicNameEnumeratorListener, ContentNameProvider { protected ContentName _namePrefix; protected CCNNameEnumerator _enumerator; protected BasicNameEnumeratorListener callback; // make these contain something other than content names when the enumerator has better data types protected SortedSet<ContentName> _children = new TreeSet<ContentName>(); //protected SortedSet<ContentName> _newChildren = null; protected Map<Long, NewChildrenByThread> _newChildrenByThread = new TreeMap<Long, NewChildrenByThread>(); protected Object _childLock = new Object(); protected CCNTime _lastUpdate = null; protected boolean _enumerating = false; protected boolean _shutdown = false; private class NewChildrenByThread implements Comparable<NewChildrenByThread> { private final Long _id; // If 0 this is in thread pool mode private SortedSet<ContentName> _newChildren = null; private NewChildrenByThread(Long id) { this._id = id; } public int compareTo(NewChildrenByThread o) { return _id.compareTo(o._id); } } /** * Keep track of whether we've ever done enumeration, so we can start it automatically * if someone asks us for something not in our cache. This lets us relax the requirement * that callers pre-enumerate just in case. Can't use hasChildren; there might not * be any children but we might have tried enumeration already. */ protected boolean _hasEnumerated = false; /** * Creates an EnumeratedNameList object * * This constructor creates a new EnumeratedNameList object that will begin enumerating * the children of the specified prefix. The new EnumeratedNameList will use the CCNHandle passed * in to the constructor, or create a new one using CCNHandle#open() if it is null. * * @param namePrefix the ContentName whose children we wish to list. * @param handle the CCNHandle object for sending interests and receiving content object responses. */ public EnumeratedNameList(ContentName namePrefix, CCNHandle handle) throws IOException { this(namePrefix, true, handle); } public EnumeratedNameList(ContentName namePrefix, boolean startEnumerating, CCNHandle handle) throws IOException { if (null == namePrefix) { throw new IllegalArgumentException("namePrefix cannot be null!"); } if (null == handle) { try { handle = CCNHandle.open(); } catch (ConfigurationException e) { throw new IOException("ConfigurationException attempting to open a handle: " + e.getMessage()); } } _namePrefix = namePrefix; if (startEnumerating) { _enumerating = true; _hasEnumerated = true; _enumerator = new CCNNameEnumerator(namePrefix, handle, this); } else { _enumerator = new CCNNameEnumerator(handle, this); } } /** * Method to return the ContentName used for enumeration. * * @return ContentName returns the prefix under which we are enumerating children. */ public ContentName getName() { return _namePrefix; } /** * Cancels ongoing name enumeration. Previously-accumulated information about * children of this name are still stored and available for use. * * @return void * */ public synchronized void stopEnumerating() { if (!_enumerating) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) { Log.info(Log.FAC_SEARCH, "Enumerated name list: Not enumerating, so not canceling prefix."); } return; } _enumerator.cancelPrefix(_namePrefix); _enumerating = false; } /** * Starts enumeration, if we're not enumerating already. * @throws IOException */ public synchronized void startEnumerating() throws IOException { _enumerating = true; _enumerator.registerPrefix(_namePrefix); _hasEnumerated = true; } public boolean isEnumerating() { return _enumerating; } public boolean hasEnumerated() { return _hasEnumerated; } /** * Shutdown anybody waiting for children on this list */ public void shutdown() { _shutdown = true; synchronized (_childLock) { _childLock.notifyAll(); } } /** * Interface to retrieve only new data from enumeration responses as it arrives. * This method blocks and waits for data, but grabs the new data for processing. * In threadPoolContext it will in effect remove the data from every other * listener who is listening in threadPoolContext, in effect handing the new * children to the first consumer to wake up and make the other ones go around again. * There is currently no support for more than one simultaneous thread pool. * * @param threadPoolContext Are we getting data in threadPoolContext? (described above). * @param timeout maximum amount of time to wait, 0 to wait forever. * @return SortedSet<ContentName> Returns the array of single-component * content name children that are new to us, or null if we reached the * timeout before new data arrived */ public SortedSet<ContentName> getNewData(boolean threadPoolContext, long timeout) { SortedSet<ContentName> childArray = null; synchronized(_childLock) { // reentrant Long id = threadPoolContext ? 0 : Thread.currentThread().getId(); NewChildrenByThread ncbt = _newChildrenByThread.get(id); SortedSet<ContentName> newChildren = ncbt == null ? null : ncbt._newChildren; while ((null == newChildren) || newChildren.size() == 0) { waitForNewChildren(threadPoolContext, timeout); ncbt = _newChildrenByThread.get(id); newChildren = ncbt._newChildren; if (timeout != SystemConfiguration.NO_TIMEOUT) break; } if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) { Log.info(Log.FAC_SEARCH, "Waiting for new data on prefix: {0} got {1}.", _namePrefix, ((null == newChildren) ? 0 : newChildren.size())); } if (null != newChildren) { childArray = newChildren; ncbt._newChildren = null; } } return childArray; } /** * Block and wait as long as it takes for new data to appear. See #getNewData(boolean, long). * @return SortedSet<ContentName> Returns the array of single-component * content name children that are new to us. Waits forever if no new data appears */ public SortedSet<ContentName> getNewData() { return getNewData(false, SystemConfiguration.NO_TIMEOUT); } /** * Block and wait for timeout or until new data appears. See #getNewData(boolean, long). * @param timeout in ms * @return SortedSet<ContentName> Returns the array of single-component * content name children that are new to us, or null if we reached the * timeout before new data arrived */ public SortedSet<ContentName> getNewData(long timeout) { return getNewData(false, timeout); } /** * Block and wait for timeout or until new data appears. See #getNewData(boolean, long). * Different from getNewData in that new data is shared among all threads accessing this * instance of EnumeratedNameList. So if another thread gets the data first, we won't get it. * @param timeout in ms * @return SortedSet<ContentName> Returns the array of single-component * content name children that are new to the list instance if we got it first, or null if we * reached the timeout before new data arrived */ public SortedSet<ContentName> getNewDataThreadPool(long timeout) { return getNewData(true, timeout); } /** * Returns single-component ContentName objects containing the name components of the children. * @return SortedSet<ContentName> Returns the array of single-component * content name children that have been retrieved so far, or null if no responses * have yet been received. The latter may indicate either that no children of this prefix * are known to any responders, or that they have not had time to respond. */ public SortedSet<ContentName> getChildren() { if (!hasChildren()) return null; return _children; } /** * Returns true if the prefix has new names that have not been handled by the calling application. * @return true if there are new children available to process */ public boolean hasNewData() { NewChildrenByThread ncbt = getNcbt(); if (null == ncbt) return false; // Never set up return ((null != ncbt._newChildren) && (ncbt._newChildren.size() > 0)); } /** * Returns true if we have received any responses listing available child names. * If no names have yet been received, this may mean either that responses * have not had time to arrive, or there are know children known to available * responders. * * @return true if we have child names received from enumeration responses */ public boolean hasChildren() { return ((null != _children) && (_children.size() > 0)); } /** * Returns the number of children we have, or 0 if we have none. */ public int childCount() { if (null == _children) return 0; return _children.size(); } /** * Returns true if we know the prefix has a child matching the given name component. * * @param childComponent name component to check for in the stored child names. * @return true if that child is in our list of known children */ public boolean hasChild(byte [] childComponent) { for (ContentName child : _children) { if (Arrays.areEqual(childComponent, child.component(0))) { return true; } } return false; } /** * Returns whether a child is present in the list of known children. * <p> * * @param childName String version of a child name to look for * @return boolean Returns true if the name is present in the list of known children. * */ public boolean hasChild(String childName) { return hasChild(Component.parseNative(childName)); } /** * Wait for new children to arrive. * * @param timeout Maximum time to wait for new data. * @param threadPoolContext Are we waiting in threadPoolContext (i.e. other threads can grab children first) * See #getNewData(boolean, long). * @return a boolean value that indicates whether new data was found. */ public boolean waitForNewChildren(boolean threadPoolContext, long timeout) { boolean foundNewData = false; Long id = threadPoolContext ? 0 : Thread.currentThread().getId(); _newChildrenByThread.put(id, new NewChildrenByThread(id)); synchronized(_childLock) { CCNTime lastUpdate = _lastUpdate; long timeRemaining = timeout; long startTime = System.currentTimeMillis(); while (((null == _lastUpdate) || ((null != lastUpdate) && !_lastUpdate.after(lastUpdate))) && ((timeout == SystemConfiguration.NO_TIMEOUT) || (timeRemaining > 0))) { if (_shutdown) break; try { _childLock.wait((timeout != SystemConfiguration.NO_TIMEOUT) ? Math.min(timeRemaining, SystemConfiguration.CHILD_WAIT_INTERVAL) : SystemConfiguration.CHILD_WAIT_INTERVAL); if (timeout != SystemConfiguration.NO_TIMEOUT) { timeRemaining = timeout - (System.currentTimeMillis() - startTime); } } catch (InterruptedException e) { } if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) { SortedSet<ContentName> newChildren = _newChildrenByThread.get(id)._newChildren; Log.info(Log.FAC_SEARCH, "Waiting for new data on prefix: {0}, updated {1}, our update {2}, have {3} children {4} new.", _namePrefix, _lastUpdate, lastUpdate, ((null == _children) ? 0 : _children.size()), ((null == newChildren) ? 0 : newChildren.size())); } } if ((null != _lastUpdate) && ((null == lastUpdate) || (_lastUpdate.after(lastUpdate)))) foundNewData = true; } return foundNewData; } /** * Wait for new children to arrive. * This method does not have a timeout and will wait forever. * * @return void */ public void waitForNewChildren() { waitForNewChildren(false, SystemConfiguration.NO_TIMEOUT); } /** * Wait for new children to arrive. * * @param timeout Maximum amount of time to wait, if 0, waits forever. * @return a boolean value that indicates whether new data was found. */ public boolean waitForNewChildren(long timeout) { return waitForNewChildren(false, timeout); } /** * Wait for new children to arrive in thread pool context. See notes about this above. * @param timeout Maximum amount of time to wait, if 0, waits forever. * @return a boolean value that indicates whether new data was found. */ public boolean waitForNewChildrenThreadPool(long timeout) { return waitForNewChildren(true, timeout); } /** * Waits until there is any data at all. Right now, waits for the first response containing actual * children, not just a name enumeration response. That means it could block * forever if no children exist in a repository or there are not any applications responding to * name enumeration requests. Once we have an initial set of children, this method * returns immediately. * * @param timeout Maximum amount of time to wait, if 0, waits forever. * @return void */ public void waitForChildren(long timeout) { while ((null == _children) || _children.size() == 0) { waitForNewChildren(false, timeout); if (timeout != SystemConfiguration.NO_TIMEOUT) break; } } /** * Wait (block) for initial data to arrive, possibly forever. See #waitForData(long). * * @return void * */ public void waitForChildren() { waitForChildren(SystemConfiguration.NO_TIMEOUT); } /** * Wait for new children to arrive until there is a period of length timeout during which * no new child arrives. * @param timeout The maximum amount of time to wait between consecutive children arrivals. */ public void waitForNoUpdates(long timeout) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Waiting for updates on prefix {0} with max timeout of {1} ms between consecutive children arrivals.", _namePrefix, timeout); long startTime = System.currentTimeMillis(); while (waitForNewChildren(false, timeout)) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Child or children found on prefix {0}", _namePrefix); } if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Quit waiting for updates on prefix {0} after waiting in total {1} ms.", _namePrefix, (System.currentTimeMillis() - startTime)); } /** * Wait for new children to arrive until there is a period of length timeout during which * no new child arrives, or the method hasResult() returns true. The expectation * is that a subclass will monitor incoming updates in its processNewChildren() override * method, and in that method, set some sort of flag that will be tested by hasResult(). * Note that this method does not currently stop enumeration -- enumeration results will * continue to accumulate in the background (and request interests will continue to be sent); * callers must call stopEnumerating() to actually terminate enumeration. */ public void waitForNoUpdatesOrResult(long timeout) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Waiting for updates on prefix {0} with max timeout of {1} ms between consecutive children arrivals.", _namePrefix, timeout); long startTime = System.currentTimeMillis(); if (hasResult()) { return; } while (waitForNewChildren(false, timeout)) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Child or children found on prefix {0}. Have result? {1}", _namePrefix, hasResult()); if (hasResult()) break; } if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Quit waiting for updates on prefix {0} after waiting in total {1} ms. Have desired result? {2}", _namePrefix, (System.currentTimeMillis() - startTime), hasResult()); } /** * Subclasses should override this test to answer true if waiters should break out of a * waitForNoUpdatesOrResult loop. Note that results must be cleared manually using clearResult. * Default behavior always returns false. Subclasses probably want to set a variable in processNewChildren * that will be read here. */ public boolean hasResult() { return false; } /** * Reset whatever state hasResult tests. Overridden by subclasses, default does nothing. */ public void clearResult() { return; } /** * Method to allow subclasses to do post-processing on incoming names * before handing them to customers. * Note that the set handed in here is not the set that will be handed * out; only the name objects are the same. * * @param newChildren SortedSet of children available for processing * * @return void */ protected void processNewChildren(SortedSet<ContentName> newChildren) { // default -- do nothing } /** * If some or all of the children of this name are versions, returns the latest version * among them. * * @return ContentName The latest version component * */ public ContentName getLatestVersionChildName() { // of the available names in _children that are version components, // find the latest one (version-wise) // names are sorted, so the last one that is a version should be the latest version // ListIterator previous doesn't work unless you've somehow gotten it to point at the end... ContentName theName = null; ContentName latestName = null; CCNTime latestTimestamp = null; Iterator<ContentName> it = _children.iterator(); // TODO these are sorted -- we just need to iterate through them in reverse order. Having // trouble finding something like C++'s reverse iterators to do that (linked list iterators // can go backwards -- but you have to run them to the end first). while (it.hasNext()) { theName = it.next(); if (VersioningProfile.isVersionComponent(theName.component(0))) { if (null == latestName) { latestName = theName; latestTimestamp = VersioningProfile.getVersionComponentAsTimestamp(theName.component(0)); } else { CCNTime thisTimestamp = VersioningProfile.getVersionComponentAsTimestamp(theName.component(0)); if (thisTimestamp.after(latestTimestamp)) { latestName = theName; latestTimestamp = thisTimestamp; } } } } return latestName; } /** * Handle responses from CCNNameEnumerator that give us a list of single-component child * names. Filter out the names new to us, add them to our list of known children, postprocess * them with processNewChildren(SortedSet<ContentName>), and signal waiters if we * have new data. * * @param prefix Prefix used for name enumeration. * @param names The list of names returned in this name enumeration response. * * @return int */ @SuppressWarnings("unchecked") public int handleNameEnumerator(ContentName prefix, ArrayList<ContentName> names) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) { if (!_enumerating) { // Right now, just log if we get data out of enumeration, don't drop it on the floor; // don't want to miss results in case we are started again. Log.info(Log.FAC_SEARCH, "ENUMERATION STOPPED: but {0} new name enumeration results: our prefix: {1} returned prefix: {2}", names.size(), _namePrefix, prefix); } else { Log.info(Log.FAC_SEARCH, "{0} new name enumeration results: our prefix: {1} returned prefix: {2}", names.size(), _namePrefix, prefix); } } if (!prefix.equals(_namePrefix)) { Log.warning(Log.FAC_SEARCH, "Returned data doesn't match requested prefix!"); } if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "Handling Name Iteration {0}, size is {1}", prefix, names.size()); // the name enumerator hands off names to us, we own it now // DKS -- want to keep listed as new children we previously had synchronized (_childLock) { TreeSet<ContentName> thisRoundNew = new TreeSet<ContentName>(); thisRoundNew.addAll(names); Iterator<ContentName> it = thisRoundNew.iterator(); while (it.hasNext()) { ContentName name = it.next(); if (_children.contains(name)) { it.remove(); } } if (!thisRoundNew.isEmpty()) { for (NewChildrenByThread ncbt : _newChildrenByThread.values()) { if (null != ncbt._newChildren) { ncbt._newChildren.addAll(thisRoundNew); } else { ncbt._newChildren = (TreeSet<ContentName>)thisRoundNew.clone(); } } _children.addAll(thisRoundNew); _lastUpdate = new CCNTime(); if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) { Log.info(Log.FAC_SEARCH, "New children found: at {0} {1} total children {2}", _lastUpdate, + thisRoundNew.size(), _children.size()); } processNewChildren(thisRoundNew); _childLock.notifyAll(); } } return 0; } /** * Returns the latest version available under this prefix as a byte array. * * @return byte[] Latest child version as byte array */ public byte [] getLatestVersionChildNameComponent() { ContentName latestVersionName = getLatestVersionChildName(); if (null == latestVersionName) return null; return latestVersionName.component(0); } /** * Returns the latest version available under this prefix as a CCNTime object. * * @return CCNTime Latest child version as CCNTime */ public CCNTime getLatestVersionChildTime() { ContentName latestVersion = getLatestVersionChildName(); if (null != latestVersion) { return VersioningProfile.getVersionComponentAsTimestamp(latestVersion.component(0)); } return null; } /** * A static method that performs a one-shot call that returns the complete name of the latest * version of content with the given prefix. An alternative route to finding the name of the * latest version of a piece of content, rather than using methods in the VersioningProfile * to retrieve an arbitrary block of content under that version. Useful when the data under * a version is complex in structure. * * @param name ContentName to find the latest version of * @param handle CCNHandle to use for enumeration * @return ContentName The name supplied to the call with the latest version added. * @throws IOException */ public static ContentName getLatestVersionName(ContentName name, CCNHandle handle) throws IOException { EnumeratedNameList enl = new EnumeratedNameList(name, handle); enl.waitForNoUpdates(SystemConfiguration.MAX_TIMEOUT); ContentName childLatestVersion = enl.getLatestVersionChildName(); enl.stopEnumerating(); if (null != childLatestVersion) { return new ContentName(name, childLatestVersion.component(0)); } return null; } /** * Static method that iterates down the namespace starting with the supplied prefix * as a ContentName (prefixKnownToExist) to a specific child (childName). The method * returns null if the name does not exist in a limited time iteration. If the child * is found, this method returns the EnumeratedNameList object for the parent of the * desired child in the namespace. The current implementation may time out before the * desired name is found. Additionally, the current implementation does not loop on * an enumeration attempt, so a child may be missed if it is not included in the first * enumeration response. * * TODO Add loop to enumerate under a name multiple times to avoid missing a child name * TODO Handle timeouts better to avoid missing children. (Note: We could modify the * name enumeration protocol to return empty responses if we query for an unknown name, * but that adds semantic complications.) * * @param childName ContentName for the child we are looking for under (does not have * to be directly under) a given prefix. * @param prefixKnownToExist ContentName prefix to enumerate to look for a given child. * @param handle CCNHandle for sending and receiving interests and content objects. * * @return EnumeratedNameList Returns the parent EnumeratedNameList for the desired child, * if one is found. Returns null if the child is not found. * * @throws IOException */ public static EnumeratedNameList exists(ContentName childName, ContentName prefixKnownToExist, CCNHandle handle) throws IOException { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: the prefix known to exist is {0} and we are looking for childName {1}", prefixKnownToExist, childName); if ((null == prefixKnownToExist) || (null == childName) || (!prefixKnownToExist.isPrefixOf(childName))) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: Child {0} must be prefixed by name {1}", childName, prefixKnownToExist); throw new IllegalArgumentException("Child " + childName + " must be prefixed by name " + prefixKnownToExist); } if (childName.count() == prefixKnownToExist.count()) { // we're already there if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: we're already there."); return new EnumeratedNameList(childName, handle); } ContentName parentName = prefixKnownToExist; int childIndex = parentName.count(); EnumeratedNameList parentEnumerator = null; while (childIndex < childName.count()) { byte[] childNameComponent = childName.component(childIndex); parentEnumerator = new EnumeratedNameList(parentName, handle); if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: enumerating the parent name {0}", parentName); parentEnumerator.waitForChildren(SystemConfiguration.MAX_TIMEOUT); while (! parentEnumerator.hasChild(childNameComponent)) { if (! parentEnumerator.waitForNewChildren(false, SystemConfiguration.MAX_TIMEOUT)) break; } if (parentEnumerator.hasChild(childNameComponent)) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) { Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: we have a matching child to {0} and the parent enumerator {1} has {2} children.", Component.printURI(childNameComponent), parentName, parentEnumerator.childCount()); } childIndex++; if (childIndex == childName.count()) { if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: we found the childName we were looking for: {0}", childName); return parentEnumerator; } parentEnumerator.stopEnumerating(); parentName = new ContentName(parentName, childNameComponent); continue; } else { if (Log.isLoggable(Level.INFO)) { Log.info("EnumeratedNameList.exists: the parent enumerator {0} has {1} children but none of them are {2}.", parentName, parentEnumerator.childCount(), Component.printURI(childNameComponent)); } break; } } if (Log.isLoggable(Log.FAC_SEARCH, Level.INFO)) Log.info(Log.FAC_SEARCH, "EnumeratedNameList.exists: returning null for search of {0}", childName); return null; } private NewChildrenByThread getNcbt() { Long id = Thread.currentThread().getId(); NewChildrenByThread ncbt = _newChildrenByThread.get(id); if (null == ncbt) ncbt = _newChildrenByThread.get(0L); // Thread pool return ncbt; } /** * Enables an EnumeratedNameList to be used directly in a ContentName builder. * @return Gets the ContentName of the prefix being enumerated * @see ContentNameProvider * @see ContentName#builder(org.ccnx.ccn.protocol.ContentName.StringParser, Object[]) */ public ContentName getContentName() { return _namePrefix; } }