TransformHistory.java

  1. package emissary.core;

  2. import emissary.directory.KeyManipulator;
  3. import emissary.place.IServiceProviderPlace;

  4. import org.apache.commons.collections4.CollectionUtils;
  5. import org.apache.commons.lang3.StringUtils;

  6. import java.io.Serializable;
  7. import java.util.ArrayDeque;
  8. import java.util.ArrayList;
  9. import java.util.Collections;
  10. import java.util.Deque;
  11. import java.util.List;
  12. import java.util.stream.Collectors;
  13. import javax.annotation.Nullable;

  14. import static emissary.place.IServiceProviderPlace.SPROUT_KEY;

  15. public class TransformHistory implements Serializable {

  16.     private static final long serialVersionUID = -7252497842562281631L;

  17.     private final List<History> history;

  18.     public TransformHistory() {
  19.         history = new ArrayList<>();
  20.     }

  21.     public TransformHistory(TransformHistory history) {
  22.         this.history = new ArrayList<>(history.history);
  23.     }

  24.     /**
  25.      * Replace history with the new history
  26.      *
  27.      * @param keys of new history strings to use
  28.      */
  29.     public void set(List<String> keys) {
  30.         clear();
  31.         addAll(keys.stream().map(History::new).collect(Collectors.toList()));
  32.     }

  33.     /**
  34.      * Replace history with the new history
  35.      *
  36.      * @param history of new history strings to use
  37.      */
  38.     public void set(TransformHistory history) {
  39.         clear();
  40.         addAll(history.history);
  41.     }

  42.     private void addAll(List<History> history) {
  43.         this.history.addAll(history);
  44.     }

  45.     /**
  46.      * Appends the new key to the transform history. This is called by MobileAgent before moving to the new place. It
  47.      * usually adds the four-tuple of a place's key
  48.      *
  49.      * @see emissary.core.MobileAgent#agentControl
  50.      * @param key the new value to append
  51.      */
  52.     public void append(final String key) {
  53.         append(key, false);
  54.     }

  55.     /**
  56.      * Appends the new key to the transform history. This is called by MobileAgent before moving to the new place. It
  57.      * usually adds the four-tuple of a place's key. Coordinated history keys are meant for informational purposes and have
  58.      * no bearing on the routing algorithm. It is important to list the places visited in coordination, but should not
  59.      * report as the last place visited.
  60.      *
  61.      * @see emissary.core.MobileAgent#agentControl
  62.      * @param key the new value to append
  63.      * @param coordinated true if history entry is for informational purposes only
  64.      */
  65.     public void append(String key, boolean coordinated) {
  66.         if (coordinated) {
  67.             History last = lastVisit();
  68.             if (last != null) {
  69.                 last.addCoordinated(key);
  70.             }
  71.         } else {
  72.             history.add(new History(key));
  73.         }

  74.     }

  75.     /**
  76.      * Clear the transformation history
  77.      */
  78.     public void clear() {
  79.         history.clear();
  80.     }

  81.     /**
  82.      * For backwards compatibility, only return the history that does not contain the coordinated places
  83.      *
  84.      * @return List of places visited sans coordination places
  85.      */
  86.     public List<String> get() {
  87.         return get(false);
  88.     }

  89.     /**
  90.      * Return the transform history, optionally including coordinated places
  91.      *
  92.      * @return List of places visited
  93.      */
  94.     public List<String> get(boolean includeCoordinated) {
  95.         if (includeCoordinated) {
  96.             List<String> keys = new ArrayList<>();
  97.             history.forEach(k -> {
  98.                 keys.add(k.getKey());
  99.                 keys.addAll(k.getCoordinated());
  100.             });
  101.             return keys;
  102.         } else {
  103.             return history.stream()
  104.                     .map(History::getKey)
  105.                     .collect(Collectors.toList());
  106.         }
  107.     }

  108.     /**
  109.      * Return the full history object
  110.      *
  111.      * @return history object
  112.      */
  113.     public List<History> getHistory() {
  114.         return Collections.unmodifiableList(history);
  115.     }

  116.     /**
  117.      * Get the last place visited (does not include places visited during coordination)
  118.      *
  119.      * @return last place visited
  120.      */
  121.     @Nullable
  122.     public History lastVisit() {
  123.         if (CollectionUtils.isEmpty(history)) {
  124.             return null;
  125.         }
  126.         return history.get(history.size() - 1);
  127.     }

  128.     /**
  129.      * Get the second-to-last place visited (does not include places visited during coordination)
  130.      *
  131.      * @return second-to-last place visited
  132.      */
  133.     @Nullable
  134.     public History penultimateVisit() {
  135.         if (CollectionUtils.isEmpty(history) || history.size() < 2) {
  136.             return null;
  137.         }
  138.         return history.get(history.size() - 2);
  139.     }

  140.     /**
  141.      * Test to see if a place has been visited (does not include places visited during coordination)
  142.      *
  143.      * @return true is place has been visited
  144.      */
  145.     public boolean hasVisited(final String pattern) {
  146.         for (final History hist : history) {
  147.             if (KeyManipulator.gmatch(hist.getKey(), pattern)) {
  148.                 return true;
  149.             }
  150.         }
  151.         return false;
  152.     }

  153.     /**
  154.      * True if this payload hasn't had any processing yet. Does not count parent processing as being for this payload.
  155.      *
  156.      * @return true if not yet started
  157.      */
  158.     public boolean beforeStart() {
  159.         if (history.isEmpty()) {
  160.             return true;
  161.         }
  162.         final String s = history.get(history.size() - 1).getKey();
  163.         return s.contains(IServiceProviderPlace.SPROUT_KEY);
  164.     }

  165.     public int size() {
  166.         return size(false);
  167.     }

  168.     public int size(boolean includeCoordinated) {
  169.         int size = history.size();
  170.         if (includeCoordinated) {
  171.             size += (int) history.stream().mapToLong(h -> h.coordinated.size()).sum();
  172.         }
  173.         return size;
  174.     }

  175.     public Deque<String> format() {
  176.         Deque<String> formattedHistory = new ArrayDeque<>();

  177.         String prevDataAndServiceType = "";
  178.         for (final History h : this.history) {
  179.             String key = h.getKey();
  180.             String currentDataAndServiceType = "";
  181.             StringBuilder displayStrings = new StringBuilder();

  182.             if (key.contains(SPROUT_KEY)) {
  183.                 displayStrings.append(StringUtils.substringBefore(formattedHistory.removeLast(), ".")).append(".")
  184.                         .append(KeyManipulator.getServiceType(key)).append(": ");
  185.                 while (!formattedHistory.isEmpty()) {
  186.                     String last = formattedHistory.removeLast();
  187.                     if (last.contains(SPROUT_KEY)) {
  188.                         formattedHistory.add(last);
  189.                         break;
  190.                     }
  191.                 }
  192.             } else {
  193.                 currentDataAndServiceType = KeyManipulator.getDataType(key) + "." + KeyManipulator.getServiceType(key);
  194.                 if (currentDataAndServiceType.equals(prevDataAndServiceType)) {
  195.                     displayStrings.append(formattedHistory.removeLast()).append(", ");
  196.                 } else {
  197.                     displayStrings.append(currentDataAndServiceType).append(": ");
  198.                 }
  199.             }

  200.             displayStrings.append(KeyManipulator.getServiceClassname(key));
  201.             if (CollectionUtils.isNotEmpty(h.getCoordinated())) {
  202.                 displayStrings
  203.                         .append(h.getCoordinated().stream().map(KeyManipulator::getServiceClassname).collect(Collectors.joining(", ", "(", ")")));
  204.             }

  205.             formattedHistory.add(displayStrings.toString());
  206.             prevDataAndServiceType = currentDataAndServiceType;
  207.         }
  208.         return formattedHistory;
  209.     }

  210.     @Override
  211.     public String toString() {
  212.         final StringBuilder myOutput = new StringBuilder();
  213.         final String ls = System.lineSeparator();
  214.         myOutput.append("transform history (").append(size(true)).append(") :").append(ls);
  215.         history.forEach(x -> myOutput.append(x.toString()).append(ls));
  216.         return myOutput.toString();
  217.     }

  218.     public static class History {
  219.         String key;
  220.         List<String> coordinated = new ArrayList<>();

  221.         /**
  222.          * Needed to support Kryo deserialization
  223.          */
  224.         private History() {}

  225.         public History(String key) {
  226.             this.key = key;
  227.         }

  228.         public String getKey() {
  229.             return getKey(false);
  230.         }

  231.         public String getKey(boolean stripUrl) {
  232.             return stripUrl ? stripUrl(key) : key;
  233.         }

  234.         public List<String> getCoordinated() {
  235.             return getCoordinated(false);
  236.         }

  237.         public List<String> getCoordinated(boolean stripUrl) {
  238.             return stripUrl ? coordinated.stream().map(History::stripUrl).collect(Collectors.toUnmodifiableList())
  239.                     : Collections.unmodifiableList(coordinated);
  240.         }

  241.         public void addCoordinated(String key) {
  242.             coordinated.add(key);
  243.         }

  244.         protected static String stripUrl(String key) {
  245.             return StringUtils.substringBefore(key, ".http");
  246.         }

  247.         @Override
  248.         public String toString() {
  249.             StringBuilder hist = new StringBuilder("        -> " + getKey());
  250.             for (String coord : coordinated) {
  251.                 hist.append(System.lineSeparator()).append("           +--> ").append(coord);
  252.             }
  253.             return hist.toString();
  254.         }
  255.     }
  256. }