/* * A CCNx library test. * * Copyright (C) 2008-2012 Palo Alto Research Center, Inc. * * This work is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * This work 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 General Public License * for more details. You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ package org.ccnx.ccn.test; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.ccnx.ccn.CCNContentHandler; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.profiles.SegmentationProfile; import org.ccnx.ccn.protocol.Component; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.ContentObject; import org.ccnx.ccn.protocol.Exclude; import org.ccnx.ccn.protocol.ExcludeComponent; import org.ccnx.ccn.protocol.Interest; import org.ccnx.ccn.protocol.MalformedContentNameStringException; /** * A class to help write tests without a repository or setting up a separate * thread to read data. A Flosser tries to pull all content written under * a set of specified namespaces; essentially loading that content into ccnd. * Based on ccnslurp, uses excludes to not get the same data back * * By default does not floss namespaces below segments. This is because most * commonly we will receive a file with a segmented namespace and no hierarchy below * that. Trying to floss below this will lead to large numbers of unsatisfied interests * being expressed, adversely affecting performance. * * Call stopMonitoringNamespace as soon as you are done with a namespace to improve * performance. * * See CCNVersionedInputStream for related stream-based flossing code (basically * a precursor to the full Flosser). * * The "floss" term refers to "mental floss" -- think a picture of * someone running dental floss in and out through their ears; here we are * running content from an app, in through ccnd, to the flosser, so other * parts of the (test) app can pull it back from ccnd later. */ public class Flosser implements CCNContentHandler { CCNHandle _handle; Map<ContentName, Interest> _interests = new HashMap<ContentName, Interest>(); Map<ContentName, Set<ContentName>> _subInterests = new HashMap<ContentName, Set<ContentName>>(); HashSet<ContentObject> _processedObjects = new HashSet<ContentObject>(); boolean _flossSubNamespaces = false; boolean _shutdown = false; /** * Constructors that called handleNamespace() now throwing NullPointerException as this doesn't exist yet. * @throws ConfigurationException * @throws IOException */ public Flosser() throws ConfigurationException, IOException { _handle = CCNHandle.open(); } public Flosser(ContentName namespace) throws ConfigurationException, IOException { this(); handleNamespace(namespace); } public void stopMonitoringNamespace(String namespace) throws MalformedContentNameStringException { stopMonitoringNamespace(ContentName.fromNative(namespace)); } public void stopMonitoringNamespace(ContentName namespace) { synchronized(_interests) { if (!_interests.containsKey(namespace)) { return; } Set<ContentName> subInterests = _subInterests.get(namespace); if (null != subInterests) { Iterator<ContentName> it = subInterests.iterator(); while (it.hasNext()) { ContentName subNamespace = it.next(); removeInterest(subNamespace); it.remove(); } } // Remove the top-level interest. removeInterest(namespace); _subInterests.remove(namespace); Log.info(Log.FAC_TEST, "FLOSSER: no longer monitoring namespace: {0}", namespace); } } public void stopMonitoringNamespaces() { synchronized(_interests) { Set<ContentName> namespaceSet = getNamespaces(); // Go through a few hoops to avoid a ConcurrentModificationException, as code // other than us is going to remove the things we're iterating over from the Set. ContentName [] namespaces = new ContentName[namespaceSet.size()]; namespaces = namespaceSet.toArray(namespaces); for (int i=0; i < namespaces.length; ++i) { stopMonitoringNamespace(namespaces[i]); } } } public void setFlossSubNamespaces(boolean flag) { _flossSubNamespaces = flag; } protected void removeInterest(ContentName namespace) { synchronized(_interests) { if (!_interests.containsKey(namespace)) { return; } Interest interest = _interests.get(namespace); _handle.cancelInterest(interest, this); _interests.remove(namespace); Log.fine(Log.FAC_TEST, "Cancelled interest in {0}", namespace); } } public void handleNamespace(String namespace) throws MalformedContentNameStringException, IOException { handleNamespace(ContentName.fromNative(namespace)); } /** * Handle a top-level namespace. * @param namespace * @throws IOException */ public void handleNamespace(ContentName namespace) throws IOException { synchronized(_interests) { if (_shutdown) { Log.info(Log.FAC_TEST, "FLOSSER: in the process of shutting down. Not handling new namespace {0}.", namespace); return; } if (_interests.containsKey(namespace)) { Log.fine(Log.FAC_TEST, "FLOSSER: Already handling namespace: {0}", namespace); return; } Log.info(Log.FAC_TEST, "FLOSSER: handling namespace: {0}", namespace); Interest namespaceInterest = new Interest(namespace); _interests.put(namespace, namespaceInterest); _handle.expressInterest(namespaceInterest, this); Set<ContentName> subNamespaces = _subInterests.get(namespace); if (null == subNamespaces) { subNamespaces = new HashSet<ContentName>(); _subInterests.put(namespace, subNamespaces); Log.info(Log.FAC_TEST, "FLOSSER: setup parent namespace: {0}", namespace); } } } public void handleNamespace(ContentName namespace, ContentName parent) throws IOException { synchronized(_interests) { if (_shutdown) { Log.info(Log.FAC_TEST, "FLOSSER: in the process of shutting down. Not handling new subnamespace {0} under parent {1}.", namespace, parent); return; } if (_interests.containsKey(namespace)) { Log.fine(Log.FAC_TEST, "Already handling child namespace: {0}", namespace); return; } Log.info(Log.FAC_TEST, "FLOSSER: handling child namespace: {0} expected parent: {1}", namespace, parent); Interest namespaceInterest = new Interest(namespace); namespaceInterest.minSuffixComponents(2); // Don't reget the parent _interests.put(namespace, namespaceInterest); _handle.expressInterest(namespaceInterest, this); // Now we need to find a parent in the subInterest map, and reflect this namespace underneath it. ContentName parentNamespace = parent; Set<ContentName> subNamespace = _subInterests.get(parentNamespace); while ((subNamespace == null) && (!parentNamespace.equals(ContentName.ROOT))) { parentNamespace = parentNamespace.parent(); subNamespace = _subInterests.get(parentNamespace); Log.info(Log.FAC_TEST, "FLOSSER: initial parent not found in map, looked up {0} found in map? {1}", parentNamespace, ((null == subNamespace) ? "no" : "yes")); } if (null != subNamespace) { Log.info(Log.FAC_TEST, "FLOSSER: Adding subnamespace: {0} to ancestor {1}", namespace, parentNamespace); subNamespace.add(namespace); } else { Log.info(Log.FAC_TEST, "FLOSSER: Cannot find ancestor namespace for {0}", namespace); for (ContentName n : _subInterests.keySet()) { Log.info(Log.FAC_TEST, "FLOSSER: available ancestor: {0}", n); } } } } public Interest handleContent(ContentObject result, Interest interest) { Log.finest(Log.FAC_TEST, "Interests registered: " + _interests.size() + " content object returned"); // Parameterized behavior that subclasses can override. ContentName interestName = null; if (_processedObjects.contains(result)) { Log.fine(Log.FAC_TEST, "FLOSSER: Got repeated content for interest: {0} content: {1}", interest, result.name()); } else { Log.finest(Log.FAC_TEST, "FLOSSER: Got new content for interest {0} content name: {1}", interest, result.name()); processContent(result); // update the interest. follow process used by ccnslurp. // exclude the next component of this object, and set up a // separate interest to explore its children. // first, remove the interest from our list as we aren't going to // reexpress it in exactly the same way synchronized(_interests) { for (Entry<ContentName, Interest> entry : _interests.entrySet()) { if (entry.getValue().equals(interest)) { interestName = entry.getKey(); _interests.remove(interestName); break; } } } int prefixCount = interest.name().count(); // DKS TODO should the count above be count()-1 and this just prefixCount? if (prefixCount == result.name().count()) { if (null == interest.exclude()) { ArrayList<Exclude.Element> excludes = new ArrayList<Exclude.Element>(); excludes.add(new ExcludeComponent(result.digest())); interest.exclude(new Exclude(excludes)); Log.finest(Log.FAC_TEST, "Creating new exclude filter for interest {0}", interest.name()); } else { if (interest.exclude().match(result.digest())) { Log.fine(Log.FAC_TEST, "We should have already excluded content digest: " + DataUtils.printBytes(result.digest())); } else { // Has to be in order... Log.finest(Log.FAC_TEST, "Adding child component to exclude."); interest.exclude().add(new byte [][] { result.digest() }); } } Log.finer(Log.FAC_TEST, "Excluding content digest: " + DataUtils.printBytes(result.digest()) + " onto interest {0} total excluded: " + interest.exclude().size(), interest.name()); } else { // Add an exclude for the content we just got // DKS TODO might need to split to matchedComponents like ccnslurp if (null == interest.exclude()) { ArrayList<Exclude.Element> excludes = new ArrayList<Exclude.Element>(); excludes.add(new ExcludeComponent(result.name().component(prefixCount))); interest.exclude(new Exclude(excludes)); Log.finest(Log.FAC_TEST, "Creating new exclude filter for interest {0}", interest.name()); } else { if (interest.exclude().match(result.name().component(prefixCount))) { Log.fine(Log.FAC_TEST, "We should have already excluded child component: {0}", Component.printURI(result.name().component(prefixCount))); } else { // Has to be in order... Log.finest(Log.FAC_TEST, "Adding child component to exclude."); interest.exclude().add( new byte [][] { result.name().component(prefixCount) }); } } Log.finer(Log.FAC_TEST, "Excluding child " + Component.printURI(result.name().component(prefixCount)) + " total excluded: " + interest.exclude().size()); if (_flossSubNamespaces || SegmentationProfile.isNotSegmentMarker(result.name().component(prefixCount))) { ContentName newNamespace = null; try { if (interest.name().count() == result.name().count()) { newNamespace = new ContentName(interest.name(), result.digest()); Log.info(Log.FAC_TEST, "Not adding content exclusion namespace: {0}", newNamespace); } else { newNamespace = new ContentName(interest.name(), result.name().component(interest.name().count())); Log.info(Log.FAC_TEST, "Adding new namespace: {0}", newNamespace); handleNamespace(newNamespace, interest.name()); } } catch (IOException ioex) { Log.warning("IOException picking up namespace: {0}", newNamespace); } } } } if (null != interest) synchronized(_interests) { _interests.put(interest.name(), interest); } return interest; } public void stop() { Log.info(Log.FAC_TEST, "Stop flossing."); synchronized (_interests) { _shutdown = true; stopMonitoringNamespaces(); Log.info(Log.FAC_TEST, "Stopped flossing: remaining namespaces {0} (should be 0), subnamespaces {1} (should be 0).", _interests.size(), _subInterests.size()); } _handle.close(); } public void logNamespaces() { ContentName [] namespaces = getNamespaces().toArray(new ContentName[getNamespaces().size()]); for (ContentName name : namespaces) { Log.info("Flosser: monitoring namespace: " + name); } } public Set<ContentName> getNamespaces() { synchronized (_interests) { return _interests.keySet(); } } /** * Override in subclasses that want to do something more interesting than log. * @param result */ protected void processContent(ContentObject result) { Log.info(Log.FAC_TEST, "Flosser got: " + result.fullName()); } }