All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.util.MemoryContentCache Maven / Gradle / Ivy

/**
 * Copyright (C) 2014-2018 Regents of the University of California.
 * @author: Jeff Thompson 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 program.  If not, see .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.util;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.named_data.jndn.Data;
import net.named_data.jndn.Face;
import net.named_data.jndn.ForwardingFlags;
import net.named_data.jndn.Interest;
import net.named_data.jndn.InterestFilter;
import net.named_data.jndn.Name;
import net.named_data.jndn.OnInterestCallback;
import net.named_data.jndn.OnRegisterFailed;
import net.named_data.jndn.OnRegisterSuccess;
import net.named_data.jndn.encoding.WireFormat;
import net.named_data.jndn.security.SecurityException;

/**
 * A MemoryContentCache holds a set of Data packets and answers an Interest to
 * return the correct Data packet. The cache is periodically cleaned up to
 * remove each stale Data packet based on its FreshnessPeriod (if it has one).
 * @note This class is an experimental feature.  See the API docs for more detail at
 * http://named-data.net/doc/ndn-ccl-api/memory-content-cache.html .
 */
public class MemoryContentCache implements OnInterestCallback {
  /**
   * Create a new MemoryContentCache to use the given Face.
   * @param face The Face to use to call registerPrefix and setInterestFilter,
   * and which will call this object's OnInterest callback.
   * @param cleanupIntervalMilliseconds The interval in milliseconds
   * between each check to clean up stale content in the cache. If this is a
   * large number, then effectively the stale content will not be removed from
   * the cache.
   */
  public MemoryContentCache(Face face, double cleanupIntervalMilliseconds)
  {
    face_ = face;
    cleanupIntervalMilliseconds_ = cleanupIntervalMilliseconds;
    construct();
  }

  /**
   * Create a new MemoryContentCache to use the given Face, with a default
   * cleanupIntervalMilliseconds of 1000.0 milliseconds.
   * @param face The Face to use to call registerPrefix and setInterestFilter,
   * and which will call this object's OnInterest callback.
   */
  public MemoryContentCache(Face face)
  {
    face_ = face;
    cleanupIntervalMilliseconds_ = 1000.0;
    construct();
  }

  private void
  construct()
  {
    nextCleanupTime_ = Common.getNowMilliseconds() + cleanupIntervalMilliseconds_;

    storePendingInterestCallback_ = new OnInterestCallback() {
      public void onInterest
        (Name localPrefix, Interest localInterest, Face localFace,
         long localInterestFilterId, InterestFilter localFilter)
      {
        storePendingInterest(localInterest, localFace);
      }
    };
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onRegisterSuccess This calls
   * onRegisterSuccess.onRegisterSuccess(prefix, registeredPrefixId) when this
   * receives a success message from the forwarder. If onRegisterSuccess is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param flags See Face.registerPrefix.
   * @param wireFormat See Face.registerPrefix.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix
    (Name prefix, OnRegisterFailed onRegisterFailed,
     OnRegisterSuccess onRegisterSuccess, OnInterestCallback onDataNotFound,
     ForwardingFlags flags, WireFormat wireFormat)
     throws IOException, SecurityException
  {
    if (onDataNotFound != null)
      onDataNotFoundForPrefix_.put(prefix.toUri(), onDataNotFound);
    long registeredPrefixId = face_.registerPrefix
      (prefix, this, onRegisterFailed, onRegisterSuccess, flags, wireFormat);
    registeredPrefixIdList_.add(registeredPrefixId);
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * This uses the default WireFormat.getDefaultWireFormat().
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onRegisterSuccess This calls
   * onRegisterSuccess.onRegisterSuccess(prefix, registeredPrefixId) when this
   * receives a success message from the forwarder. If onRegisterSuccess is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onInterest.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * @param flags See Face.registerPrefix.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix
    (Name prefix, OnRegisterFailed onRegisterFailed,
     OnRegisterSuccess onRegisterSuccess, OnInterestCallback onDataNotFound,
     ForwardingFlags flags) throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, onRegisterSuccess, onDataNotFound, flags,
       WireFormat.getDefaultWireFormat());
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * This uses the default WireFormat.getDefaultWireFormat().
   * Use default ForwardingFlags.
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onRegisterSuccess This calls
   * onRegisterSuccess.onRegisterSuccess(prefix, registeredPrefixId) when this
   * receives a success message from the forwarder. If onRegisterSuccess is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix
    (Name prefix, OnRegisterFailed onRegisterFailed,
     OnRegisterSuccess onRegisterSuccess, OnInterestCallback onDataNotFound)
    throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, onRegisterSuccess, onDataNotFound,
       new ForwardingFlags(), WireFormat.getDefaultWireFormat());
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * Do not call a callback if a data packet is not found in the cache.
   * This uses the default WireFormat.getDefaultWireFormat().
   * Use default ForwardingFlags.
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onRegisterSuccess This calls
   * onRegisterSuccess.onRegisterSuccess(prefix, registeredPrefixId) when this
   * receives a success message from the forwarder. If onRegisterSuccess is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix(Name prefix, OnRegisterFailed onRegisterFailed,
     OnRegisterSuccess onRegisterSuccess)
    throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, onRegisterSuccess, null, new ForwardingFlags(),
       WireFormat.getDefaultWireFormat());
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param flags See Face.registerPrefix.
   * @param wireFormat See Face.registerPrefix.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix
    (Name prefix, OnRegisterFailed onRegisterFailed, OnInterestCallback onDataNotFound,
     ForwardingFlags flags, WireFormat wireFormat) throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, null, onDataNotFound, flags, wireFormat);
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * This uses the default WireFormat.getDefaultWireFormat().
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * @param flags See Face.registerPrefix.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix
    (Name prefix, OnRegisterFailed onRegisterFailed, OnInterestCallback onDataNotFound,
     ForwardingFlags flags) throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, onDataNotFound, flags,
       WireFormat.getDefaultWireFormat());
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * This uses the default WireFormat.getDefaultWireFormat().
   * Use default ForwardingFlags.
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix
    (Name prefix, OnRegisterFailed onRegisterFailed, OnInterestCallback onDataNotFound)
    throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, onDataNotFound, new ForwardingFlags(),
       WireFormat.getDefaultWireFormat());
  }

  /**
   * Call registerPrefix on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name matches the filter.
   * Alternatively, if the Face's registerPrefix has already been called, then
   * you can call this object's setInterestFilter.
   * Do not call a callback if a data packet is not found in the cache.
   * This uses the default WireFormat.getDefaultWireFormat().
   * Use default ForwardingFlags.
   * @param prefix The Name for the prefix to register. This copies the Name.
   * @param onRegisterFailed If register prefix fails for any reason, this
   * calls onRegisterFailed.onRegisterFailed(prefix).
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   * @throws IOException For I/O error in sending the registration request.
   * @throws SecurityException If signing a command interest for NFD and cannot
   * find the private key for the certificateName.
   */
  public final void
  registerPrefix(Name prefix, OnRegisterFailed onRegisterFailed)
    throws IOException, SecurityException
  {
    registerPrefix
      (prefix, onRegisterFailed, null, new ForwardingFlags(),
       WireFormat.getDefaultWireFormat());
  }

  /**
   * Call setInterestFilter on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name matches the filter.
   * @param filter The InterestFilter with a prefix and optional regex filter
   * used to match the name of an incoming Interest. This makes a copy of filter.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  Note: If you call setInterestFilter multiple times where
   * filter.getPrefix() is the same, it is undetermined which onDataNotFound
   * will be called. If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   */
  public final void
  setInterestFilter(InterestFilter filter, OnInterestCallback onDataNotFound)
  {
    if (onDataNotFound != null)
      onDataNotFoundForPrefix_.put(filter.getPrefix().toUri(), onDataNotFound);
    long interestFilterId = face_.setInterestFilter(filter, this);
    interestFilterIdList_.add(interestFilterId);
  }

  /**
   * Call setInterestFilter on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Do not call a callback if a data packet is not found in the cache.
   * @param filter The InterestFilter with a prefix and optional regex filter
   * used to match the name of an incoming Interest. This makes a copy of filter.
   */
  public final void
  setInterestFilter(InterestFilter filter)
  {
    setInterestFilter(filter, null);
  }

  /**
   * Call setInterestFilter on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * @param prefix The Name prefix used to match the name of an incoming
   * Interest. This copies the Name.
   * @param onDataNotFound If a data packet for an interest is not found in the
   * cache, this forwards the interest by calling
   * onDataNotFound.onInterest(prefix, interest, face, interestFilterId, filter).
   * Your callback can find the Data packet for the interest and call
   * face.putData(data).  Note: If you call setInterestFilter multiple times where
   * filter.getPrefix() is the same, it is undetermined which onDataNotFound
   * will be called. If your callback cannot find the Data packet, it can
   * optionally call storePendingInterest(interest, face) to store the pending
   * interest in this object to be satisfied by a later call to add(data). If
   * you want to automatically store all pending interests, you can simply use
   * getStorePendingInterest() for onDataNotFound. If onDataNotFound is null,
   * this does not use it.
   * NOTE: The library will log any exceptions thrown by this callback, but for
   * better error handling the callback should catch and properly handle any
   * exceptions.
   */
  public final void
  setInterestFilter(Name prefix, OnInterestCallback onDataNotFound)
  {
    if (onDataNotFound != null)
      onDataNotFoundForPrefix_.put(prefix.toUri(), onDataNotFound);
    long interestFilterId = face_.setInterestFilter(prefix, this);
    interestFilterIdList_.add(interestFilterId);
  }

  /**
   * Call setInterestFilter on the Face given to the constructor so that this
   * MemoryContentCache will answer interests whose name has the prefix.
   * Do not call a callback if a data packet is not found in the cache.
   * @param prefix The Name prefix used to match the name of an incoming
   * Interest. This copies the Name.
   */
  public final void
  setInterestFilter(Name prefix)
  {
    setInterestFilter(prefix, null);
  }

  /**
   * Call Face.unsetInterestFilter and Face.removeRegisteredPrefix for all the
   * prefixes given to the setInterestFilter and registerPrefix method on this
   * MemoryContentCache object so that it will not receive interests any more.
   * You can call this if you want to "shut down" this MemoryContentCache while
   * your application is still running.
   */
  public final void
  unregisterAll()
  {
    for (int i = 0; i < interestFilterIdList_.size(); ++i)
      face_.unsetInterestFilter((long)interestFilterIdList_.get(i));
    interestFilterIdList_.clear();

    for (int i = 0; i < registeredPrefixIdList_.size(); ++i)
      face_.removeRegisteredPrefix((long)registeredPrefixIdList_.get(i));
    registeredPrefixIdList_.clear();

    // Also clear each onDataNotFoundForPrefix given to registerPrefix.
    onDataNotFoundForPrefix_.clear();
  }

  /**
   * Add the Data packet to the cache so that it is available to use to
   * answer interests. If data.getMetaInfo().getFreshnessPeriod() is not
   * negative, set the staleness time to now plus the maximum of
   * data.getMetaInfo().getFreshnessPeriod() and minimumCacheLifetime, which is
   * checked during cleanup to remove stale content.
   * This also checks if cleanupIntervalMilliseconds
   * milliseconds have passed and removes stale content from the cache. After
   * removing stale content, remove timed-out pending interests from
   * storePendingInterest(), then if the added Data packet satisfies any
   * interest, send it through the face and remove the interest from the pending
   * interest table.
   * @param data The Data packet object to put in the cache. This copies the
   * fields from the object.
   */
  public final void
  add(Data data)
  {
    double nowMilliseconds = Common.getNowMilliseconds();
    doCleanup(nowMilliseconds);

    if (data.getMetaInfo().getFreshnessPeriod() >= 0.0) {
      // The content will go stale, so use staleTimeCache_.
      StaleTimeContent content = new StaleTimeContent
        (data, nowMilliseconds, minimumCacheLifetime_);
      // Insert into staleTimeCache, sorted on content.cacheRemovalTimeMilliseconds_.
      // Search from the back since we expect it to go there.
      int i = staleTimeCache_.size() - 1;
      while (i >= 0) {
        if (staleTimeCache_.get(i).getCacheRemovalTimeMilliseconds() <=
            content.getCacheRemovalTimeMilliseconds())
          break;
        --i;
      }
      // Element i is the greatest less than or equal to
      // content.cacheRemovalTimeMilliseconds_, so insert after it.
      staleTimeCache_.add(i + 1, content);
    }
    else
      // The data does not go stale, so use noStaleTimeCache_.
      noStaleTimeCache_.add(new Content(data));

    // Remove timed-out interests and check if the data packet matches any
    // pending interest.
    // Go backwards through the list so we can erase entries.
    for (int i = pendingInterestTable_.size() - 1; i >= 0; --i) {
      PendingInterest pendingInterest = pendingInterestTable_.get(i);
      if (pendingInterest.isTimedOut(nowMilliseconds)) {
        pendingInterestTable_.remove(i);
        continue;
      }

      if (pendingInterest.getInterest().matchesName(data.getName())) {
        try {
          // Send to the same face from the original call to onInterest.
          // wireEncode returns the cached encoding if available.
          pendingInterest.getFace().send(data.wireEncode());
        } catch (IOException ex) {
          Logger.getLogger(MemoryContentCache.class.getName()).log(Level.SEVERE,
            ex.getMessage());
          return;
        }

        // The pending interest is satisfied, so remove it.
        pendingInterestTable_.remove(i);
      }
    }
  }

  /**
   * Store an interest from an OnInterest callback in the internal pending
   * interest table (normally because there is no Data packet available yet to
   * satisfy the interest). add(data) will check if the added Data packet
   * satisfies any pending interest and send it through the face.
   * @param interest The Interest for which we don't have a Data packet yet. You
   * should not modify the interest after calling this.
   * @param face The Face with the connection which received the interest. This
   * comes from the OnInterest callback.
   */
  public final void
  storePendingInterest(Interest interest, Face face)
  {
    pendingInterestTable_.add(new PendingInterest(interest, face));
  }

  /**
   * Return a callback to use for onDataNotFound in registerPrefix which simply
   * calls storePendingInterest() to store the interest that doesn't match a
   * Data packet. add(data) will check if the added Data packet satisfies any
   * pending interest and send it.
   * @return A callback to use for onDataNotFound in registerPrefix().
   */
  public final OnInterestCallback
  getStorePendingInterest()
  {
    return storePendingInterestCallback_;
  }

  /**
   * Get the minimum lifetime before removing stale content from the cache.
   * @return The minimum cache lifetime in milliseconds.
   */
  public final double
  getMinimumCacheLifetime() { return minimumCacheLifetime_; }

  /**
   * Set the minimum lifetime before removing stale content from the cache which
   * can keep content in the cache longer than the lifetime defined in the meta
   * info. This can be useful for matching interests where MustBeFresh is false.
   * The default minimum cache lifetime is zero, meaning that content is removed
   * when its lifetime expires.
   * @param minimumCacheLifetime The minimum cache lifetime in milliseconds.
   */
  public final void
  setMinimumCacheLifetime(double minimumCacheLifetime)
  {
    minimumCacheLifetime_ = minimumCacheLifetime;
  }

  public final void
  onInterest
    (Name prefix, Interest interest, Face face, long interestFilterId,
     InterestFilter filter)
  {
    double nowMilliseconds = Common.getNowMilliseconds();
    doCleanup(nowMilliseconds);

    Name.Component selectedComponent = null;
    Blob selectedEncoding = null;
    // We need to iterate over both arrays.
    int totalSize = staleTimeCache_.size() + noStaleTimeCache_.size();
    for (int i = 0; i < totalSize; ++i) {
      Content content;
      boolean isFresh = true;
      if (i < staleTimeCache_.size()) {
        StaleTimeContent staleTimeContent = staleTimeCache_.get(i);
        content = staleTimeContent;
        isFresh = staleTimeContent.isFresh(nowMilliseconds);
      }
      else
        // We have iterated over the first array. Get from the second.
        content = noStaleTimeCache_.get(i - staleTimeCache_.size());

      if (interest.matchesName(content.getName()) &&
          !(interest.getMustBeFresh() && !isFresh)) {
        if (interest.getChildSelector() < 0) {
          // No child selector, so send the first match that we have found.
          try {
            face.send(content.getDataEncoding());
          } catch (IOException ex) {
            Logger.getLogger(MemoryContentCache.class.getName()).log(Level.SEVERE, null, ex);
          }
          return;
        }
        else {
          // Update selectedEncoding based on the child selector.
          Name.Component component;
          if (content.getName().size() > interest.getName().size())
            component = content.getName().get(interest.getName().size());
          else
            component = emptyComponent_;

          boolean gotBetterMatch = false;
          if (selectedEncoding == null)
            // Save the first match.
            gotBetterMatch = true;
          else {
            if (interest.getChildSelector() == 0) {
              // Leftmost child.
              if (component.compare(selectedComponent) < 0)
                gotBetterMatch = true;
            }
            else {
              // Rightmost child.
              if (component.compare(selectedComponent) > 0)
                gotBetterMatch = true;
            }
          }

          if (gotBetterMatch) {
            selectedComponent = component;
            selectedEncoding = content.getDataEncoding();
          }
        }
      }
    }

    if (selectedEncoding != null) {
      // We found the leftmost or rightmost child.
      try {
        face.send(selectedEncoding);
      } catch (IOException ex) {
        Logger.getLogger(MemoryContentCache.class.getName()).log(Level.SEVERE, null, ex);
      }
    }
    else {
      // Call the onDataNotFound callback (if defined).
      Object onDataNotFound = onDataNotFoundForPrefix_.get(prefix.toUri());
      if (onDataNotFound != null) {
        try {
          ((OnInterestCallback)onDataNotFound).onInterest
            (prefix, interest, face, interestFilterId, filter);
        } catch (Throwable ex) {
          logger_.log(Level.SEVERE, "Error in onDataNotFound", ex);
        }
      }
    }
  }

  /**
   * Content is a private class to hold the name and encoding for each entry
   * in the cache. This base class is for a Data packet without a
   * FreshnessPeriod.
   */
  private class Content {
    /**
     * Create a new Content entry to hold data's name and wire encoding.
     * @param data The Data packet whose name and wire encoding are copied.
     */
    public Content(Data data)
    {
      // wireEncode returns the cached encoding if available.
      name_ = data.getName();
      dataEncoding_ = data.wireEncode();
    }

    public final Name
    getName() { return name_; }

    public final Blob
    getDataEncoding() { return dataEncoding_; }

    private final Name name_;
    private final Blob dataEncoding_;
  }

  /**
   * StaleTimeContent extends Content to include the cacheRemovalTimeMilliseconds_
   * for when this entry should be cleaned up from the cache.
   */
  private class StaleTimeContent extends Content {
    /**
     * Create a new StaleTimeContent to hold data's name and wire encoding
     * as well as the cacheRemovalTimeMilliseconds_ which is now plus the maximum of
     * data.getMetaInfo().getFreshnessPeriod() and the minimumCacheLifetime.
     * @param data The Data packet whose name and wire encoding are copied.
     * @param nowMilliseconds The current time in milliseconds from
     * Common.getNowMilliseconds().
     * @param minimumCacheLifetime The minimum cache lifetime in milliseconds.
     */
    public StaleTimeContent
      (Data data, double nowMilliseconds, double minimumCacheLifetime)
    {
      // wireEncode returns the cached encoding if available.
      super(data);

      cacheRemovalTimeMilliseconds_ = nowMilliseconds +
        Math.max(data.getMetaInfo().getFreshnessPeriod(), minimumCacheLifetime);
      freshnessExpiryTimeMilliseconds_ = nowMilliseconds +
        data.getMetaInfo().getFreshnessPeriod();
    }

    /**
     * Check if this content is stale and should be removed from the cache,
     * according to the content freshness period and the minimumCacheLifetime.
     * @param nowMilliseconds The current time in milliseconds from
     * Common.getNowMilliseconds().
     * @return True if this content should be removed, otherwise false.
     */
    public final boolean
    isPastRemovalTime(double nowMilliseconds)
    {
      return cacheRemovalTimeMilliseconds_ <= nowMilliseconds;
    }

    /**
     * Check if the content is still fresh according to its freshness period
     * (independent of when to remove from the cache).
     * @param nowMilliseconds The current time in milliseconds from
     * Common.getNowMilliseconds().
     * @return True if the content is still fresh, otherwise false.
     */
    public final boolean
    isFresh(double nowMilliseconds)
    {
      return freshnessExpiryTimeMilliseconds_ > nowMilliseconds;
    }

    public final double
    getCacheRemovalTimeMilliseconds() { return cacheRemovalTimeMilliseconds_; }

    private final double cacheRemovalTimeMilliseconds_; /**< The time when the content
      becomes stale and should be removed from the cache in milliseconds
      according to Common.getNowMilliseconds(). */
    private final double freshnessExpiryTimeMilliseconds_; /**< The time when
      the freshness period of the content expires (independent of when to
      remove from the cache) in milliseconds according to Common.getNowMilliseconds(). */
  }

  /**
   * A PendingInterest holds an interest which onInterest received but could
   * not satisfy. When we add a new data packet to the cache, we will also check
   * if it satisfies a pending interest.
   */
  private static class PendingInterest {
    /**
     * Create a new PendingInterest and set the timeoutTime_ based on the current
     * time and the interest lifetime.
     * @param interest The interest.
     * @param face The face from the onInterest callback. If the
     * interest is satisfied later by a new data packet, we will send the data
     * packet to the face.
     */
    public PendingInterest(Interest interest, Face face)
    {
      interest_ = interest;
      face_ = face;

      // Set up timeoutTimeMilliseconds_.
      double interestLifetime = interest_.getInterestLifetimeMilliseconds();
      if (interestLifetime < 0.0)
        // The InterestLifetime is omitted, so use a default.
        interestLifetime = 4000.0;

      timeoutTimeMilliseconds_ = Common.getNowMilliseconds() + interestLifetime;
    }

    /**
     * Return the interest given to the constructor.
     */
    public final Interest
    getInterest() { return interest_; }

    /**
     * Return the face given to the constructor.
     */
    public final Face
    getFace() { return face_; }

    /**
     * Check if this interest is timed out.
     * @param nowMilliseconds The current time in milliseconds from
     *   Common.getNowMilliseconds.
     * @return True if this interest timed out, otherwise false.
     */
    public final boolean
    isTimedOut(double nowMilliseconds)
    {
      return nowMilliseconds >= timeoutTimeMilliseconds_;
    }

    private final Interest interest_;
    private final Face face_;
    private final double timeoutTimeMilliseconds_; /**< The time when the
      * interest times out in milliseconds according to ndn_getNowMilliseconds,
      * or -1 for no timeout. */
  }

  /**
   * Check if now is greater than nextCleanupTime_ and, if so, remove stale
   * content from staleTimeCache_ and reset nextCleanupTime_ based on
   * cleanupIntervalMilliseconds_. Since add(Data) does a sorted insert into
   * staleTimeCache_, the check for stale data is quick and does not require
   * searching the entire staleTimeCache_.
   * @param nowMilliseconds The current time in milliseconds from
   * Common.getNowMilliseconds().
   */
  private void
  doCleanup(double nowMilliseconds)
  {
    if (nowMilliseconds >= nextCleanupTime_) {
      // staleTimeCache_ is sorted on cacheRemovalTimeMilliseconds_, so we only need to
      // erase the stale entries at the front, then quit.
      while (staleTimeCache_.size() > 0 &&
             staleTimeCache_.get(0).isPastRemovalTime(nowMilliseconds))
        staleTimeCache_.remove(0);

      nextCleanupTime_ = nowMilliseconds + cleanupIntervalMilliseconds_;
    }
  }

  private final Face face_;
  private final double cleanupIntervalMilliseconds_;
  private double nextCleanupTime_;
  // Use HashMap without generics so it works with older Java compilers.
  private final HashMap onDataNotFoundForPrefix_ =
    new HashMap(); /**< The map key is the prefix.toUri().
                    * The value is the OnInterest callback. */
  // Use ArrayList without generics so it works with older Java compilers.
  private final ArrayList interestFilterIdList_ = new ArrayList();
  private final ArrayList registeredPrefixIdList_ = new ArrayList();
  private final ArrayList noStaleTimeCache_ = new ArrayList();
  private final ArrayList staleTimeCache_ =
    new ArrayList();
  private final Name.Component emptyComponent_ = new Name.Component();
  private final ArrayList pendingInterestTable_ =
    new ArrayList();
  private OnInterestCallback storePendingInterestCallback_;
  private double minimumCacheLifetime_ = 0;
  private static final Logger logger_ = Logger.getLogger(MemoryContentCache.class.getName());
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy