BaseDataObject.java

  1. package emissary.core;

  2. import emissary.core.channels.SeekableByteChannelFactory;
  3. import emissary.core.channels.SeekableByteChannelHelper;
  4. import emissary.directory.DirectoryEntry;
  5. import emissary.pickup.Priority;
  6. import emissary.util.ByteUtil;
  7. import emissary.util.PayloadUtil;

  8. import com.google.common.collect.LinkedListMultimap;
  9. import org.apache.commons.collections4.CollectionUtils;
  10. import org.apache.commons.lang3.ArrayUtils;
  11. import org.apache.commons.lang3.StringUtils;
  12. import org.apache.commons.lang3.Validate;
  13. import org.slf4j.Logger;
  14. import org.slf4j.LoggerFactory;

  15. import java.io.IOException;
  16. import java.io.InputStream;
  17. import java.io.Serializable;
  18. import java.net.InetAddress;
  19. import java.net.UnknownHostException;
  20. import java.nio.ByteBuffer;
  21. import java.nio.channels.Channels;
  22. import java.nio.channels.SeekableByteChannel;
  23. import java.rmi.Remote;
  24. import java.time.Instant;
  25. import java.util.ArrayList;
  26. import java.util.Collection;
  27. import java.util.HashMap;
  28. import java.util.Iterator;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Set;
  32. import java.util.TreeMap;
  33. import java.util.TreeSet;
  34. import java.util.UUID;
  35. import javax.annotation.Nullable;

  36. /**
  37.  * Class to hold data, header, footer, and attributes
  38.  */
  39. public class BaseDataObject implements Serializable, Cloneable, Remote, IBaseDataObject {
  40.     protected static final Logger logger = LoggerFactory.getLogger(BaseDataObject.class);

  41.     /* Used to limit the size of a returned byte array to avoid certain edge case scenarios */
  42.     public static final int MAX_BYTE_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  43.     /* Including this here make serialization of this object faster. */
  44.     private static final long serialVersionUID = 7362181964652092657L;

  45.     /* Actual data - migrate away from this towards byte channels. */
  46.     @Nullable
  47.     protected byte[] theData;

  48.     /**
  49.      * Original name of the input data. Can only be set in the constructor of the DataObject. returned via the
  50.      * {@link #getFilename()} method. Also used in constructing the {@link #shortName()} of the document.
  51.      */
  52.     protected String theFileName;

  53.     /**
  54.      * Terminal portion of theFileName
  55.      */
  56.     protected String shortName;

  57.     /**
  58.      * The internal identifier, generated for each constructed object
  59.      */
  60.     protected UUID internalId = UUID.randomUUID();

  61.     /**
  62.      * The currentForm is a stack of the itinerary items. The contents of the list are {@link String} and map to the
  63.      * dataType portion of the keys in the emissary.DirectoryPlace.
  64.      */
  65.     protected List<String> currentForm = new ArrayList<>();

  66.     /**
  67.      * History of processing errors. Lines of text are accumulated from String and returned in-toto as a String.
  68.      */
  69.     protected StringBuilder procError;

  70.     /**
  71.      * A travelogue built up as the agent moves about. Appended to by the agent as it goes from place to place.
  72.      */
  73.     protected TransformHistory history = new TransformHistory();

  74.     /**
  75.      * The last determined language(characterset) of the data.
  76.      */
  77.     @Nullable
  78.     protected String fontEncoding = null;

  79.     /**
  80.      * Dynamic facets or metadata attributes of the data
  81.      */
  82.     protected LinkedListMultimap<String, Object> parameters = LinkedListMultimap.create(100);

  83.     /**
  84.      * If this file caused other agents to be sprouted, indicate how many
  85.      */
  86.     protected int numChildren = 0;

  87.     /**
  88.      * If this file has siblings that were sprouted at the same time, this will indicate how many total siblings there are.
  89.      * This can be used to navigate among siblings without needing to refer to the parent.
  90.      */
  91.     protected int numSiblings = 0;

  92.     /**
  93.      * What child is this in the family order
  94.      */
  95.     protected int birthOrder = 0;

  96.     /**
  97.      * Hash of alternate views of the data {@link String} current form is the key, byte[] is the value
  98.      */
  99.     protected Map<String, byte[]> multipartAlternative = new TreeMap<>();

  100.     /**
  101.      * Any header that goes along with the data
  102.      */
  103.     @Nullable
  104.     protected byte[] header = null;

  105.     /**
  106.      * Any footer that goes along with the data
  107.      */
  108.     @Nullable
  109.     protected byte[] footer = null;

  110.     /**
  111.      * If the header has some encoding scheme record it
  112.      */
  113.     @Nullable
  114.     protected String headerEncoding = null;

  115.     /**
  116.      * Record the classification scheme for the document
  117.      */
  118.     @Nullable
  119.     protected String classification = null;

  120.     /**
  121.      * Keep track of if and how the document is broken so we can report on it later
  122.      */
  123.     @Nullable
  124.     protected StringBuilder brokenDocument = null;

  125.     // Filetypes that we think are equivalent to no file type at all
  126.     protected String[] emptyFileTypes = {Form.UNKNOWN};

  127.     /**
  128.      * The integer priority of the data object. A lower number is higher priority.
  129.      */
  130.     protected int priority = Priority.DEFAULT;

  131.     /**
  132.      * The timestamp for when the BaseDataObject was created. Used in data provenance tracking.
  133.      */
  134.     protected Instant creationTimestamp;

  135.     /**
  136.      * The extracted records, if any
  137.      */
  138.     @Nullable
  139.     protected List<IBaseDataObject> extractedRecords;

  140.     /**
  141.      * Check to see if this tree is able to be written out.
  142.      */
  143.     protected boolean outputable = true;

  144.     /**
  145.      * The unique identifier of this object
  146.      */
  147.     protected String id;

  148.     /**
  149.      * The identifier of the {@link emissary.pickup.WorkBundle}
  150.      */
  151.     protected String workBundleId;

  152.     /**
  153.      * The identifier used to track the object through the system
  154.      */
  155.     protected String transactionId;

  156.     /**
  157.      * A factory to create channels for the referenced data.
  158.      */
  159.     @Nullable
  160.     protected SeekableByteChannelFactory seekableByteChannelFactory;

  161.     @Nullable
  162.     protected final IBaseDataObject tld;

  163.     protected enum DataState {
  164.         NO_DATA, CHANNEL_ONLY, BYTE_ARRAY_ONLY, BYTE_ARRAY_AND_CHANNEL
  165.     }

  166.     protected static final String INVALID_STATE_MSG = "Can't have both theData and seekableByteChannelFactory set. Object is %s";

  167.     /**
  168.      * <p>
  169.      * Determine what state we're in with respect to the byte[] of data vs a channel.
  170.      * </p>
  171.      *
  172.      * <p>
  173.      * Not exposed publicly as consumers should be moving to channels, meaning ultimately the states will be simply either a
  174.      * channel factory exists or does not exist.
  175.      * </p>
  176.      *
  177.      * <p>
  178.      * Consumers should not modify their behaviour based on the state of the BDO, if they're being modified to handle
  179.      * channels, they should only handle channels, not both channels and byte[].
  180.      * </p>
  181.      *
  182.      * @return the {@link DataState} of this BDO
  183.      */
  184.     protected DataState getDataState() {
  185.         if (theData == null) {
  186.             if (seekableByteChannelFactory == null) {
  187.                 return DataState.NO_DATA;
  188.             } else {
  189.                 return DataState.CHANNEL_ONLY;
  190.             }
  191.         } else {
  192.             if (seekableByteChannelFactory == null) {
  193.                 return DataState.BYTE_ARRAY_ONLY;
  194.             } else {
  195.                 return DataState.BYTE_ARRAY_AND_CHANNEL;
  196.             }
  197.         }
  198.     }

  199.     /**
  200.      * Create an empty BaseDataObject.
  201.      */
  202.     public BaseDataObject() {
  203.         this.theData = null;
  204.         setCreationTimestamp(Instant.now());
  205.         tld = null;
  206.     }

  207.     /**
  208.      * Create a new BaseDataObject with byte array and name passed in. WARNING: this implementation uses the passed in array
  209.      * directly, no copy is made so the caller should not reuse the array.
  210.      *
  211.      * @param newData the bytes to hold
  212.      * @param name the name of the data item
  213.      */
  214.     public BaseDataObject(final byte[] newData, final String name) {
  215.         setData(newData);
  216.         setFilename(name);
  217.         setCreationTimestamp(Instant.now());
  218.         tld = null;
  219.     }

  220.     /**
  221.      * Create a new BaseDataObject with byte array, name, and initial form WARNING: this implementation uses the passed in
  222.      * array directly, no copy is made so the caller should not reuse the array.
  223.      *
  224.      * @param newData the bytes to hold
  225.      * @param name the name of the data item
  226.      * @param form the initial form of the data
  227.      */
  228.     public BaseDataObject(final byte[] newData, final String name, @Nullable final String form) {
  229.         this(newData, name);
  230.         if (form != null) {
  231.             pushCurrentForm(form);
  232.         }
  233.     }

  234.     public BaseDataObject(final byte[] newData, final String name, final String form, @Nullable final String fileType) {
  235.         this(newData, name, form);
  236.         if (fileType != null) {
  237.             this.setFileType(fileType);
  238.         }
  239.     }

  240.     public BaseDataObject(final byte[] newData, final String name, @Nullable final String form, IBaseDataObject tld) {
  241.         setData(newData);
  242.         setFilename(name);
  243.         setCreationTimestamp(Instant.now());
  244.         if (form != null) {
  245.             pushCurrentForm(form);
  246.         }
  247.         this.tld = tld;
  248.     }

  249.     public BaseDataObject(final byte[] newData, final String name, @Nullable final String form, @Nullable final String fileType,
  250.             IBaseDataObject tld) {
  251.         this(newData, name, form, tld);
  252.         if (fileType != null) {
  253.             this.setFileType(fileType);
  254.         }
  255.     }

  256.     /**
  257.      * Set the header byte array WARNING: this implementation uses the passed in array directly, no copy is made so the
  258.      * caller should not reuse the array.
  259.      *
  260.      * @param header the byte array of header data
  261.      */
  262.     @Override
  263.     public void setHeader(final byte[] header) {
  264.         this.header = header;
  265.     }

  266.     /**
  267.      * Get the value of headerEncoding. Tells how to interpret the header information.
  268.      *
  269.      * @return Value of headerEncoding.
  270.      */
  271.     @Override
  272.     public String getHeaderEncoding() {
  273.         return this.headerEncoding;
  274.     }

  275.     /**
  276.      * Set the value of headerEncoding for proper interpretation and processing later
  277.      *
  278.      * @param v Value to assign to headerEncoding.
  279.      */
  280.     @Override
  281.     public void setHeaderEncoding(final String v) {
  282.         this.headerEncoding = v;
  283.     }

  284.     /**
  285.      * Set the footer byte array WARNING: this implementation uses the passed in array directly, no copy is made so the
  286.      * caller should not reuse the array.
  287.      *
  288.      * @param footer byte array of footer data
  289.      */
  290.     @Override
  291.     public void setFooter(final byte[] footer) {
  292.         this.footer = footer;
  293.     }

  294.     /**
  295.      * Set the filename
  296.      *
  297.      * @param f the new name of the data including path
  298.      */
  299.     @Override
  300.     public void setFilename(final String f) {
  301.         this.theFileName = f;
  302.         this.shortName = makeShortName();
  303.     }

  304.     /**
  305.      * Set the byte channel factory using whichever implementation is providing access to the data.
  306.      *
  307.      * Setting this will null out {@link #theData}
  308.      */
  309.     @Override
  310.     public void setChannelFactory(final SeekableByteChannelFactory sbcf) {
  311.         Validate.notNull(sbcf, "Required: SeekableByteChannelFactory not null");
  312.         this.theData = null;
  313.         this.seekableByteChannelFactory = sbcf;
  314.     }

  315.     /**
  316.      * Returns the seekable byte channel factory containing a reference to the data, or wraps the in-memory data on the BDO
  317.      * in a new factory.
  318.      *
  319.      * @return the factory containing the data reference or the data wrapped in a new factory
  320.      */
  321.     @Nullable
  322.     @Override
  323.     @SuppressWarnings("UnnecessaryDefaultInEnumSwitch")
  324.     public SeekableByteChannelFactory getChannelFactory() {
  325.         switch (getDataState()) {
  326.             case BYTE_ARRAY_AND_CHANNEL:
  327.                 throw new IllegalStateException(String.format(INVALID_STATE_MSG, shortName()));
  328.             case CHANNEL_ONLY:
  329.                 return seekableByteChannelFactory;
  330.             case BYTE_ARRAY_ONLY:
  331.                 return SeekableByteChannelHelper.memory(this.theData);
  332.             case NO_DATA:
  333.             default:
  334.                 return null;
  335.         }
  336.     }

  337.     /**
  338.      * {@inheritDoc}
  339.      */
  340.     @Nullable
  341.     @Override
  342.     public InputStream newInputStream() {
  343.         final SeekableByteChannelFactory sbcf = getChannelFactory();

  344.         return sbcf == null ? null : Channels.newInputStream(sbcf.create());
  345.     }

  346.     /**
  347.      * <p>
  348.      * Return BaseDataObjects byte array OR as much as we can from the reference to the data up to MAX_BYTE_ARRAY_SIZE.
  349.      * </p>
  350.      *
  351.      * <p>
  352.      * Data returned from a backing Channel will be truncated at {@link BaseDataObject#MAX_BYTE_ARRAY_SIZE}. Using
  353.      * channel-related methods is now preferred to allow handling of larger objects
  354.      * </p>
  355.      *
  356.      * <p>
  357.      * <b>WARNING</b>: There is no way for the caller to know whether the data being returned is the direct array held in
  358.      * memory, or a copy of the data from a byte channel factory, so the returned byte array should be treated as live and
  359.      * not be modified.
  360.      * </p>
  361.      *
  362.      * @see #getChannelFactory()
  363.      * @return the data as a byte array
  364.      */
  365.     @Nullable
  366.     @Override
  367.     @SuppressWarnings("UnnecessaryDefaultInEnumSwitch")
  368.     public byte[] data() {
  369.         switch (getDataState()) {
  370.             case BYTE_ARRAY_AND_CHANNEL:
  371.                 throw new IllegalStateException(String.format(INVALID_STATE_MSG, shortName()));
  372.             case BYTE_ARRAY_ONLY:
  373.                 return theData;
  374.             case CHANNEL_ONLY:
  375.                 // Max size here is slightly less than the true max size to avoid memory issues
  376.                 return SeekableByteChannelHelper.getByteArrayFromBdo(this, MAX_BYTE_ARRAY_SIZE);
  377.             case NO_DATA:
  378.             default:
  379.                 return null; // NOSONAR maintains backwards compatibility
  380.         }
  381.     }

  382.     /**
  383.      * @see #setData(byte[], int, int)
  384.      */
  385.     @Override
  386.     public void setData(@Nullable final byte[] newData) {
  387.         this.seekableByteChannelFactory = null;
  388.         this.theData = newData == null ? new byte[0] : newData;
  389.     }

  390.     /**
  391.      * <p>
  392.      * Set new data on the BDO, using a range of the provided byte array. This will remove the reference to any byte channel
  393.      * factory that backs this BDO so be careful!
  394.      * </p>
  395.      *
  396.      * <p>
  397.      * Limited in size to 2^31. Use channel-based methods for larger data.
  398.      * </p>
  399.      *
  400.      * @param newData containing the source of the new data
  401.      * @param offset where to start copying from
  402.      * @param length how much to copy
  403.      * @see #setChannelFactory(SeekableByteChannelFactory)
  404.      */
  405.     @Override
  406.     public void setData(@Nullable final byte[] newData, final int offset, final int length) {
  407.         this.seekableByteChannelFactory = null;
  408.         if (length <= 0 || newData == null) {
  409.             this.theData = new byte[0];
  410.         } else {
  411.             this.theData = new byte[length];
  412.             System.arraycopy(newData, offset, this.theData, 0, length);
  413.         }
  414.     }

  415.     /**
  416.      * Checks if the data is defined with a non-zero length.
  417.      *
  418.      * @return if data is undefined or zero length.
  419.      */
  420.     @Override
  421.     public boolean hasContent() throws IOException {
  422.         return getChannelSize() > 0;
  423.     }

  424.     /**
  425.      * Convenience method to get the size of the channel or byte array providing access to the data.
  426.      *
  427.      * @return the channel size
  428.      */
  429.     @Override
  430.     @SuppressWarnings("UnnecessaryDefaultInEnumSwitch")
  431.     public long getChannelSize() throws IOException {
  432.         switch (getDataState()) {
  433.             case BYTE_ARRAY_AND_CHANNEL:
  434.                 throw new IllegalStateException(String.format(INVALID_STATE_MSG, shortName()));
  435.             case BYTE_ARRAY_ONLY:
  436.                 return ArrayUtils.getLength(theData);
  437.             case CHANNEL_ONLY:
  438.                 try (SeekableByteChannel sbc = this.seekableByteChannelFactory.create()) {
  439.                     return sbc.size();
  440.                 }
  441.             case NO_DATA:
  442.             default:
  443.                 return 0;
  444.         }
  445.     }

  446.     /**
  447.      * Fetch the size of the payload. Prefer to use: {@link #getChannelSize}
  448.      *
  449.      * @return the length of theData, or the size of the seekable byte channel up to
  450.      *         {@link BaseDataObject#MAX_BYTE_ARRAY_SIZE}.
  451.      */
  452.     @Override
  453.     @SuppressWarnings("UnnecessaryDefaultInEnumSwitch")
  454.     public int dataLength() {
  455.         switch (getDataState()) {
  456.             case BYTE_ARRAY_AND_CHANNEL:
  457.                 throw new IllegalStateException(String.format(INVALID_STATE_MSG, shortName()));
  458.             case BYTE_ARRAY_ONLY:
  459.                 return ArrayUtils.getLength(theData);
  460.             case CHANNEL_ONLY:
  461.                 try {
  462.                     return (int) Math.min(getChannelSize(), MAX_BYTE_ARRAY_SIZE);
  463.                 } catch (final IOException ioe) {
  464.                     logger.error("Couldn't get size of channel on object {}", shortName(), ioe);
  465.                     return 0;
  466.                 }
  467.             case NO_DATA:
  468.             default:
  469.                 return 0;
  470.         }
  471.     }

  472.     @Override
  473.     public String shortName() {
  474.         return this.shortName;
  475.     }

  476.     /**
  477.      * Construct the shortname
  478.      */
  479.     private String makeShortName() {
  480.         /*
  481.          * using the file object works for most cases. It fails on the unix side if it is given a valid Windows path.
  482.          */
  483.         // File file = new File( theFileName );
  484.         // return file.getName();
  485.         // so..... we'll have to perform the check ourselves ARRRRRRRRRGH!!!!
  486.         final int unixPathIndex = this.theFileName.lastIndexOf("/");
  487.         if (unixPathIndex >= 0) {
  488.             return this.theFileName.substring(unixPathIndex + 1);
  489.         }
  490.         // check for windows path
  491.         final int windowsPathIndex = this.theFileName.lastIndexOf("\\");
  492.         if (windowsPathIndex >= 0) {
  493.             return this.theFileName.substring(windowsPathIndex + 1);
  494.         }

  495.         return this.theFileName;
  496.     }

  497.     @Override
  498.     public String getFilename() {
  499.         return this.theFileName;
  500.     }

  501.     @Override
  502.     public String currentForm() {
  503.         return currentFormAt(0);
  504.     }

  505.     @Override
  506.     public String currentFormAt(final int i) {
  507.         if (i < this.currentForm.size()) {
  508.             return this.currentForm.get(i);
  509.         }
  510.         return "";
  511.     }

  512.     @Override
  513.     public int searchCurrentForm(final String value) {
  514.         return this.currentForm.indexOf(value);
  515.     }

  516.     @Nullable
  517.     @Override
  518.     public String searchCurrentForm(final Collection<String> values) {
  519.         for (final String value : values) {
  520.             if (this.currentForm.contains(value)) {
  521.                 return value;
  522.             }
  523.         }
  524.         return null;
  525.     }

  526.     @Override
  527.     public int currentFormSize() {
  528.         return this.currentForm.size();
  529.     }

  530.     @Override
  531.     public void replaceCurrentForm(@Nullable final String form) {
  532.         this.currentForm.clear();
  533.         if (form != null) {
  534.             pushCurrentForm(form);
  535.         }
  536.     }

  537.     /**
  538.      * Remove a form from the head of the list
  539.      *
  540.      * @return The value that was removed, or {@code null} if the list was empty.
  541.      */
  542.     @Nullable
  543.     @Override
  544.     public String popCurrentForm() {
  545.         if (this.currentForm.isEmpty()) {
  546.             return null;
  547.         } else {
  548.             return this.currentForm.remove(0);
  549.         }
  550.     }

  551.     @Override
  552.     public int deleteCurrentForm(final String form) {
  553.         int count = 0;

  554.         if (this.currentForm == null || this.currentForm.isEmpty()) {
  555.             return count;
  556.         }

  557.         // Remove all matching
  558.         for (final Iterator<String> i = this.currentForm.iterator(); i.hasNext();) {
  559.             final String val = i.next();
  560.             if (val.equals(form)) {
  561.                 i.remove();
  562.                 count++;
  563.             }
  564.         }
  565.         return count;
  566.     }

  567.     @Override
  568.     public int deleteCurrentFormAt(final int i) {
  569.         // Make sure its a legal position.
  570.         if ((i >= 0) && (i < this.currentForm.size())) {
  571.             this.currentForm.remove(i);
  572.         }
  573.         return this.currentForm.size();
  574.     }

  575.     @Override
  576.     public int addCurrentFormAt(final int i, final String newForm) {
  577.         if (newForm == null) {
  578.             throw new IllegalArgumentException("caller attempted to add a null form value at position " + i);
  579.         }

  580.         checkForAndLogDuplicates(newForm, "addCurrentFormAt");
  581.         if (i < this.currentForm.size()) {
  582.             this.currentForm.add(i, newForm);
  583.         } else {
  584.             this.currentForm.add(newForm);
  585.         }
  586.         return this.currentForm.size();
  587.     }

  588.     @Override
  589.     public int enqueueCurrentForm(final String newForm) {
  590.         if (newForm == null) {
  591.             throw new IllegalArgumentException("caller attempted to enqueue a null form value");
  592.         }

  593.         checkForAndLogDuplicates(newForm, "enqueueCurrentForm");
  594.         this.currentForm.add(newForm);
  595.         return this.currentForm.size();
  596.     }

  597.     @Override
  598.     public int pushCurrentForm(final String newForm) {
  599.         if (newForm == null) {
  600.             throw new IllegalArgumentException("caller attempted to push a null form value");
  601.         } else if (!PayloadUtil.isValidForm(newForm)) {
  602.             // If there is a key separator in the form, then throw an error log as this will cause issues in routing
  603.             logger.error("INVALID FORM: The form can only contain a-z, A-Z, 0-9, '-', '_', '()', '/', '+'. Given form: {}", newForm);
  604.         }

  605.         checkForAndLogDuplicates(newForm, "pushCurrentForm");
  606.         return addCurrentFormAt(0, newForm);
  607.     }

  608.     @Override
  609.     public void setCurrentForm(final String newForm) {
  610.         setCurrentForm(newForm, false);
  611.     }

  612.     @Override
  613.     public void setCurrentForm(final String newForm, final boolean clearAllForms) {
  614.         if (StringUtils.isBlank(newForm)) {
  615.             throw new IllegalArgumentException("caller attempted to set the current form to a null value");
  616.         }

  617.         if (clearAllForms) {
  618.             replaceCurrentForm(newForm);
  619.         } else {
  620.             popCurrentForm();
  621.             pushCurrentForm(newForm);
  622.         }
  623.     }


  624.     @Override
  625.     public List<String> getAllCurrentForms() {
  626.         return new ArrayList<>(this.currentForm);
  627.     }

  628.     @Override
  629.     public void pullFormToTop(final String curForm) {
  630.         if (this.currentForm.size() > 1) {
  631.             // Delete it
  632.             final int count = deleteCurrentForm(curForm);

  633.             // If deleted, add it back on top
  634.             if (count > 0) {
  635.                 this.currentForm.add(0, curForm);
  636.             }
  637.         }
  638.     }

  639.     private void checkForAndLogDuplicates(String newForm, String method) {
  640.         if (currentForm.contains(newForm)) {
  641.             logger.info("Duplicate form {} being added through BaseDataObject.{}", newForm, method);
  642.         }
  643.     }

  644.     @Override
  645.     public String toString() {
  646.         final StringBuilder myOutput = new StringBuilder();
  647.         final String ls = System.getProperty("line.separator");

  648.         myOutput.append(ls);
  649.         myOutput.append("   currentForms: ").append(getAllCurrentForms()).append(ls);
  650.         myOutput.append("   ").append(history);

  651.         return myOutput.toString();
  652.     }

  653.     @Override
  654.     public String printMeta() {
  655.         return PayloadUtil.printFormattedMetadata(this);
  656.     }

  657.     @Override
  658.     public void addProcessingError(final String err) {
  659.         if (this.procError == null) {
  660.             this.procError = new StringBuilder();
  661.         }
  662.         this.procError.append(err).append("\n");
  663.     }

  664.     @Override
  665.     public String getProcessingError() {
  666.         String s = null;
  667.         if (this.procError != null) {
  668.             s = this.procError.toString();
  669.         }
  670.         return s;
  671.     }

  672.     @Override
  673.     public TransformHistory getTransformHistory() {
  674.         return new TransformHistory(history);
  675.     }

  676.     @Override
  677.     public List<String> transformHistory() {
  678.         return transformHistory(false);
  679.     }

  680.     @Override
  681.     public List<String> transformHistory(boolean includeCoordinated) {
  682.         return history.get(includeCoordinated);
  683.     }

  684.     @Override
  685.     public void clearTransformHistory() {
  686.         this.history.clear();
  687.     }

  688.     @Override
  689.     public void appendTransformHistory(final String key) {
  690.         appendTransformHistory(key, false);
  691.     }

  692.     @Override
  693.     public void appendTransformHistory(final String key, boolean coordinated) {
  694.         this.history.append(key, coordinated);
  695.     }

  696.     @Override
  697.     public void setHistory(TransformHistory newHistory) {
  698.         this.history.set(newHistory);
  699.     }

  700.     @Override
  701.     public String whereAmI() {
  702.         String host = null;
  703.         try {
  704.             host = InetAddress.getLocalHost().getCanonicalHostName();
  705.         } catch (UnknownHostException e) {
  706.             host = "FAILED";
  707.         }
  708.         return host;
  709.     }

  710.     @Nullable
  711.     @Override
  712.     public DirectoryEntry getLastPlaceVisited() {
  713.         TransformHistory.History entry = history.lastVisit();
  714.         return entry == null ? null : new DirectoryEntry(entry.getKey());
  715.     }

  716.     @Nullable
  717.     @Override
  718.     public DirectoryEntry getPenultimatePlaceVisited() {
  719.         TransformHistory.History entry = history.penultimateVisit();
  720.         return entry == null ? null : new DirectoryEntry(entry.getKey());
  721.     }

  722.     @Override
  723.     public boolean hasVisited(final String pattern) {
  724.         return history.hasVisited(pattern);
  725.     }

  726.     @Override
  727.     public boolean beforeStart() {
  728.         return history.beforeStart();
  729.     }

  730.     @Override
  731.     public void clearParameters() {
  732.         this.parameters.clear();
  733.     }

  734.     @Override
  735.     public boolean hasParameter(final String key) {
  736.         return this.parameters.containsKey(key);
  737.     }

  738.     @Override
  739.     public void setParameters(final Map<? extends String, ? extends Object> map) {
  740.         this.parameters.clear();
  741.         putParameters(map);
  742.     }

  743.     @Override
  744.     public void setParameter(final String key, final Object val) {
  745.         deleteParameter(key);
  746.         putParameter(key, val);
  747.     }

  748.     @Override
  749.     public void putParameter(final String key, final Object val) {
  750.         this.parameters.removeAll(key);

  751.         if (val instanceof Iterable) {
  752.             this.parameters.putAll(key, (Iterable<?>) val);
  753.         } else {
  754.             this.parameters.put(key, val);
  755.         }
  756.     }

  757.     /**
  758.      * Put a collection of parameters into the metadata map, keeping both old and new values
  759.      *
  760.      * @param m the map of new parameters
  761.      */
  762.     @Override
  763.     public void putParameters(final Map<? extends String, ? extends Object> m) {
  764.         putParameters(m, MergePolicy.KEEP_ALL);
  765.     }

  766.     /**
  767.      * Merge in new parameters using the specified policy to determine whether to keep all values, unique values, or prefer
  768.      * existing values
  769.      *
  770.      * @param m map of new parameters
  771.      * @param policy the merge policy
  772.      */
  773.     @Override
  774.     public void putParameters(final Map<? extends String, ? extends Object> m, final MergePolicy policy) {
  775.         for (final Map.Entry<? extends String, ? extends Object> entry : m.entrySet()) {
  776.             final String name = entry.getKey();

  777.             if ((policy == MergePolicy.KEEP_EXISTING) && this.parameters.containsKey(name)) {
  778.                 continue;
  779.             }

  780.             final Object value = entry.getValue();

  781.             if ((policy == MergePolicy.DROP_EXISTING)) {
  782.                 // store the provided value for this key, discarding any previously-stored value
  783.                 setParameter(name, value);
  784.                 continue;
  785.             }

  786.             if (value instanceof Iterable) {
  787.                 for (final Object v : (Iterable<?>) value) {
  788.                     if (policy == MergePolicy.KEEP_ALL || policy == MergePolicy.KEEP_EXISTING) {
  789.                         this.parameters.put(name, v);
  790.                     } else if (policy == MergePolicy.DISTINCT) {
  791.                         if (!this.parameters.containsEntry(name, v)) {
  792.                             this.parameters.put(name, v);
  793.                         }
  794.                     } else {
  795.                         throw new IllegalStateException("Unhandled parameter merge policy " + policy + " for " + name);
  796.                     }
  797.                 }
  798.             } else {
  799.                 if (policy == MergePolicy.KEEP_ALL || policy == MergePolicy.KEEP_EXISTING) {
  800.                     this.parameters.put(name, value);
  801.                 } else if (policy == MergePolicy.DISTINCT) {
  802.                     if (!this.parameters.containsEntry(name, value)) {
  803.                         this.parameters.put(name, value);
  804.                     }
  805.                 } else {
  806.                     throw new IllegalStateException("Unhandled parameter merge policy " + policy + " for " + name);
  807.                 }
  808.             }
  809.         }
  810.     }


  811.     /**
  812.      * Put a collection of parameters into the metadata map, adding only distinct k/v pairs
  813.      *
  814.      * @param m the map of new parameters
  815.      */
  816.     @Override
  817.     public void putUniqueParameters(final Map<? extends String, ? extends Object> m) {
  818.         putParameters(m, MergePolicy.DISTINCT);
  819.     }

  820.     /**
  821.      * Merge in parameters keeping existing keys unchanged
  822.      *
  823.      * @param m map of new parameters to consider
  824.      */
  825.     @Override
  826.     public void mergeParameters(final Map<? extends String, ? extends Object> m) {
  827.         putParameters(m, MergePolicy.KEEP_EXISTING);
  828.     }

  829.     @Nullable
  830.     @Override
  831.     public List<Object> getParameter(final String key) {
  832.         // Try remapping
  833.         List<Object> v = this.parameters.get(key);
  834.         if (CollectionUtils.isEmpty(v)) {
  835.             return null;
  836.         }
  837.         return v;
  838.     }

  839.     @Override
  840.     public void appendParameter(final String key, final CharSequence value) {
  841.         this.parameters.put(key, value);
  842.     }

  843.     @Override
  844.     public void appendParameter(final String key, final Iterable<? extends CharSequence> values) {
  845.         this.parameters.putAll(key, values);
  846.     }

  847.     /**
  848.      * Append data to the specified metadata element if it doesn't already exist If you expect to append a lot if things
  849.      * this way, this method might not have the performance characteristics that you expect. You can build a set and
  850.      * externally and append the values after they are uniqued.
  851.      *
  852.      * @param key name of the metadata element
  853.      * @param value the value to append
  854.      * @return true if the item is added, false if it already exists
  855.      */
  856.     @Override
  857.     public boolean appendUniqueParameter(final String key, final CharSequence value) {
  858.         if (this.parameters.containsEntry(key, value)) {
  859.             return false;
  860.         }

  861.         this.parameters.put(key, value);
  862.         return true;
  863.     }

  864.     @Nullable
  865.     @Override
  866.     public String getParameterAsString(final String key) {
  867.         final var obj = getParameterAsStrings(key);
  868.         if (obj.size() > 1) {
  869.             logger.warn("Multiple values for parameter, parameter:{}, number of values:{}", key, obj.size());
  870.             return getParameterAsConcatString(key);
  871.         }
  872.         return obj.stream().findFirst().orElse(null);
  873.     }

  874.     /**
  875.      * Retrieve all the metadata elements of this object This method returns possibly mapped metadata element names
  876.      *
  877.      * @return map of metadata elements
  878.      */
  879.     @Override
  880.     public Map<String, Collection<Object>> getParameters() {
  881.         return this.parameters.asMap();
  882.     }

  883.     /**
  884.      * Get a processed represenation of the parameters for external use
  885.      */
  886.     @Override
  887.     public Map<String, String> getCookedParameters() {
  888.         final Map<String, String> ext = new TreeMap<>();
  889.         for (final String key : this.parameters.keySet()) {
  890.             ext.put(key.toString(), getStringParameter(key));
  891.         }
  892.         return ext;
  893.     }

  894.     @Override
  895.     public Set<String> getParameterKeys() {
  896.         return this.parameters.keySet();
  897.     }

  898.     @Override
  899.     public List<Object> deleteParameter(final String key) {
  900.         return this.parameters.removeAll(key);
  901.     }

  902.     @Override
  903.     public void setNumChildren(final int num) {
  904.         this.numChildren = num;
  905.     }

  906.     @Override
  907.     public void setNumSiblings(final int num) {
  908.         this.numSiblings = num;
  909.     }

  910.     @Override
  911.     public void setBirthOrder(final int num) {
  912.         this.birthOrder = num;
  913.     }

  914.     @Override
  915.     public int getNumChildren() {
  916.         return this.numChildren;
  917.     }

  918.     @Override
  919.     public int getNumSiblings() {
  920.         return this.numSiblings;
  921.     }

  922.     @Override
  923.     public int getBirthOrder() {
  924.         return this.birthOrder;
  925.     }

  926.     /**
  927.      * Return a reference to the header byte array. WARNING: this implementation returns the actual array directly, no copy
  928.      * is made so the caller must be aware that modifications to the returned array are live.
  929.      *
  930.      * @return byte array of header information or null if none
  931.      */
  932.     @Override
  933.     public byte[] header() {
  934.         return this.header;
  935.     }

  936.     @Override
  937.     @Deprecated
  938.     public ByteBuffer headerBuffer() {
  939.         return ByteBuffer.wrap(header());
  940.     }

  941.     /**
  942.      * Return a reference to the footer byte array. WARNING: this implementation returns the actual array directly, no copy
  943.      * is made so the caller must be aware that modifications to the returned array are live.
  944.      *
  945.      * @return byte array of footer data or null if none
  946.      */
  947.     @Override
  948.     public byte[] footer() {
  949.         return this.footer;
  950.     }


  951.     @Override
  952.     @Deprecated
  953.     public ByteBuffer footerBuffer() {
  954.         return ByteBuffer.wrap(footer());
  955.     }

  956.     @Override
  957.     @Deprecated
  958.     public ByteBuffer dataBuffer() {
  959.         return ByteBuffer.wrap(data());
  960.     }

  961.     @Override
  962.     public String getFontEncoding() {
  963.         return this.fontEncoding;
  964.     }

  965.     @Override
  966.     public void setFontEncoding(final String fe) {
  967.         this.fontEncoding = fe;
  968.     }

  969.     private static final String FILETYPE = "FILETYPE";

  970.     /**
  971.      * Put the FILETYPE parameter, null to clear
  972.      *
  973.      * @param v the value to store or null
  974.      */
  975.     @Override
  976.     public void setFileType(@Nullable final String v) {
  977.         deleteParameter(FILETYPE);
  978.         if (v != null) {
  979.             setParameter(FILETYPE, v);
  980.         }
  981.     }

  982.     @Override
  983.     @Deprecated
  984.     public boolean setFileTypeIfEmpty(final String v, final String[] empties) {
  985.         if (isFileTypeEmpty(empties)) {
  986.             setFileType(v);
  987.             return true;
  988.         }
  989.         return false;
  990.     }

  991.     @Override
  992.     public boolean setFileTypeIfEmpty(final String v) {
  993.         return setFileTypeIfEmpty(v, this.emptyFileTypes);
  994.     }

  995.     @Override
  996.     public boolean isFileTypeEmpty() {
  997.         return isFileTypeEmpty(this.emptyFileTypes);
  998.     }

  999.     /**
  1000.      * Return true if the file type is null or in one of the specified set of empties
  1001.      *
  1002.      * @param empties a list of types that count as empty
  1003.      */
  1004.     protected boolean isFileTypeEmpty(@Nullable final String[] empties) {
  1005.         final String s = getFileType();

  1006.         if (StringUtils.isEmpty(s)) {
  1007.             return true;
  1008.         }

  1009.         if (s.endsWith(Form.SUFFIXES_UNWRAPPED)) {
  1010.             return true;
  1011.         }

  1012.         for (int i = 0; empties != null && i < empties.length; i++) {
  1013.             if (s.equals(empties[i])) {
  1014.                 return true;
  1015.             }
  1016.         }
  1017.         return false;
  1018.     }

  1019.     @Override
  1020.     public String getFileType() {
  1021.         return getStringParameter(FILETYPE);
  1022.     }

  1023.     @Override
  1024.     public int getNumAlternateViews() {
  1025.         return this.multipartAlternative.size();
  1026.     }

  1027.     /**
  1028.      * Return a specified multipart alternative view of the data WARNING: this implementation returns the actual array
  1029.      * directly, no copy is made so the caller must be aware that modifications to the returned array are live.
  1030.      *
  1031.      * @param s the name of the view to retrieve
  1032.      * @return byte array of alternate view data or null if none
  1033.      */
  1034.     @Override
  1035.     public byte[] getAlternateView(final String s) {
  1036.         return this.multipartAlternative.get(s);
  1037.     }

  1038.     @Override
  1039.     public void appendAlternateView(final String name, final byte[] data) {
  1040.         appendAlternateView(name, data, 0, data.length);
  1041.     }

  1042.     @Override
  1043.     public void appendAlternateView(final String name, final byte[] data, final int offset, final int length) {
  1044.         final byte[] av = getAlternateView(name);
  1045.         if (av != null) {
  1046.             addAlternateView(name, ByteUtil.glue(av, 0, av.length - 1, data, offset, offset + length - 1));
  1047.         } else {
  1048.             addAlternateView(name, data, offset, length);
  1049.         }
  1050.     }

  1051.     /**
  1052.      * Return a specified multipart alternative view of the data in a buffer
  1053.      *
  1054.      * @param s the name of the view to retrieve
  1055.      * @return buffer of alternate view data or null if none
  1056.      */
  1057.     @Nullable
  1058.     @Override
  1059.     @Deprecated
  1060.     public ByteBuffer getAlternateViewBuffer(final String s) {
  1061.         final byte[] viewdata = getAlternateView(s);
  1062.         if (viewdata == null) {
  1063.             return null;
  1064.         }
  1065.         return ByteBuffer.wrap(viewdata);
  1066.     }

  1067.     /**
  1068.      * Add a multipart alternative view of the data WARNING: this implementation returns the actual array directly, no copy
  1069.      * is made so the caller must be aware that modifications to the returned array are live.
  1070.      *
  1071.      * @param name the name of the new view
  1072.      * @param data the byte array of data for the view
  1073.      */
  1074.     @Override
  1075.     public void addAlternateView(final String name, @Nullable final byte[] data) {
  1076.         if (data == null) {
  1077.             this.multipartAlternative.remove(name);
  1078.         } else {
  1079.             this.multipartAlternative.put(name, data);
  1080.         }
  1081.     }

  1082.     @Override
  1083.     public void addAlternateView(final String name, @Nullable final byte[] data, final int offset, final int length) {
  1084.         if (data == null || length <= 0) {
  1085.             this.multipartAlternative.remove(name);
  1086.         } else {
  1087.             final byte[] mpa = new byte[length];
  1088.             System.arraycopy(data, offset, mpa, 0, length);
  1089.             this.multipartAlternative.put(name, mpa);
  1090.         }
  1091.     }

  1092.     /**
  1093.      * {@inheritDoc}
  1094.      *
  1095.      * @return an ordered set of alternate view names
  1096.      */
  1097.     @Override
  1098.     public Set<String> getAlternateViewNames() {
  1099.         return new TreeSet<>(this.multipartAlternative.keySet());
  1100.     }

  1101.     /**
  1102.      * Get the alternate view map. WARNING: this implementation returns the actual map directly, no copy is made so the
  1103.      * caller must be aware that modifications to the returned map are live.
  1104.      *
  1105.      * @return an map of alternate views ordered by name, key = String, value = byte[]
  1106.      */
  1107.     @Override
  1108.     public Map<String, byte[]> getAlternateViews() {
  1109.         return this.multipartAlternative;
  1110.     }

  1111.     @Override
  1112.     public boolean isBroken() {
  1113.         return (this.brokenDocument != null);
  1114.     }

  1115.     @Override
  1116.     public void setBroken(@Nullable final String v) {
  1117.         if (v == null) {
  1118.             this.brokenDocument = null;
  1119.             return;
  1120.         }

  1121.         if (this.brokenDocument == null) {
  1122.             this.brokenDocument = new StringBuilder();
  1123.             this.brokenDocument.append(v);
  1124.         } else {
  1125.             this.brokenDocument.append(", ").append(v);
  1126.         }
  1127.     }

  1128.     @Nullable
  1129.     @Override
  1130.     public String getBroken() {
  1131.         if (this.brokenDocument == null) {
  1132.             return null;
  1133.         }
  1134.         return this.brokenDocument.toString();
  1135.     }

  1136.     @Override
  1137.     public void setClassification(final String classification) {
  1138.         this.classification = classification;
  1139.     }

  1140.     @Override
  1141.     public String getClassification() {
  1142.         return this.classification;
  1143.     }

  1144.     @Override
  1145.     public int getPriority() {
  1146.         return this.priority;
  1147.     }

  1148.     @Override
  1149.     public void setPriority(final int priority) {
  1150.         this.priority = priority;
  1151.     }

  1152.     /**
  1153.      * Clone this payload
  1154.      */
  1155.     @Deprecated
  1156.     @Override
  1157.     public IBaseDataObject clone() throws CloneNotSupportedException {
  1158.         final BaseDataObject c = (BaseDataObject) super.clone();
  1159.         if ((this.theData != null) && (this.theData.length > 0)) {
  1160.             c.setData(this.theData, 0, this.theData.length);
  1161.         }

  1162.         if (this.seekableByteChannelFactory != null) {
  1163.             c.setChannelFactory(this.seekableByteChannelFactory);
  1164.         }

  1165.         c.currentForm = new ArrayList<>(this.currentForm);
  1166.         c.history = new TransformHistory(this.history);
  1167.         c.multipartAlternative = new HashMap<>(this.multipartAlternative);
  1168.         c.priority = this.priority;
  1169.         c.creationTimestamp = this.creationTimestamp;

  1170.         if ((this.extractedRecords != null) && !this.extractedRecords.isEmpty()) {
  1171.             c.clearExtractedRecords(); // remove super.clone copy
  1172.             for (final IBaseDataObject r : this.extractedRecords) {
  1173.                 c.addExtractedRecord(r.clone());
  1174.             }
  1175.         }
  1176.         // This creates a deep copy Guava style
  1177.         c.parameters = LinkedListMultimap.create(this.parameters);

  1178.         return c;
  1179.     }

  1180.     @Override
  1181.     public Instant getCreationTimestamp() {
  1182.         return this.creationTimestamp;
  1183.     }

  1184.     /**
  1185.      * The creation timestamp is part of the provenance of the event represented by this instance. It is normally set from
  1186.      * the constructor
  1187.      *
  1188.      * @param creationTimestamp when this item was created
  1189.      */
  1190.     @Override
  1191.     public void setCreationTimestamp(final Instant creationTimestamp) {
  1192.         if (creationTimestamp == null) {
  1193.             throw new IllegalArgumentException("Timestamp must not be null");
  1194.         }

  1195.         this.creationTimestamp = creationTimestamp;
  1196.     }

  1197.     @Override
  1198.     public List<IBaseDataObject> getExtractedRecords() {
  1199.         return this.extractedRecords;
  1200.     }

  1201.     @Override
  1202.     public void setExtractedRecords(final List<? extends IBaseDataObject> records) {
  1203.         if (records == null) {
  1204.             throw new IllegalArgumentException("Record list must not be null");
  1205.         }

  1206.         for (final IBaseDataObject r : records) {
  1207.             if (r == null) {
  1208.                 throw new IllegalArgumentException("No added record may be null");
  1209.             }
  1210.         }

  1211.         this.extractedRecords = new ArrayList<>(records);
  1212.     }

  1213.     @Override
  1214.     public void addExtractedRecord(final IBaseDataObject record) {
  1215.         if (record == null) {
  1216.             throw new IllegalArgumentException("Added record must not be null");
  1217.         }

  1218.         if (this.extractedRecords == null) {
  1219.             this.extractedRecords = new ArrayList<>();
  1220.         }

  1221.         this.extractedRecords.add(record);
  1222.     }

  1223.     @Override
  1224.     public void addExtractedRecords(final List<? extends IBaseDataObject> records) {
  1225.         if (records == null) {
  1226.             throw new IllegalArgumentException("ExtractedRecord list must not be null");
  1227.         }

  1228.         for (final IBaseDataObject r : records) {
  1229.             if (r == null) {
  1230.                 throw new IllegalArgumentException("No ExctractedRecord item may be null");
  1231.             }
  1232.         }

  1233.         if (this.extractedRecords == null) {
  1234.             this.extractedRecords = new ArrayList<>();
  1235.         }

  1236.         this.extractedRecords.addAll(records);
  1237.     }

  1238.     @Override
  1239.     public boolean hasExtractedRecords() {
  1240.         return (this.extractedRecords != null) && !this.extractedRecords.isEmpty();
  1241.     }

  1242.     @Override
  1243.     public void clearExtractedRecords() {
  1244.         this.extractedRecords = null;
  1245.     }

  1246.     @Override
  1247.     public int getExtractedRecordCount() {
  1248.         return (this.extractedRecords == null) ? 0 : this.extractedRecords.size();
  1249.     }

  1250.     @Override
  1251.     public UUID getInternalId() {
  1252.         return this.internalId;
  1253.     }

  1254.     @Override
  1255.     public boolean isOutputable() {
  1256.         return outputable;
  1257.     }

  1258.     @Override
  1259.     public void setOutputable(boolean outputable) {
  1260.         this.outputable = outputable;
  1261.     }

  1262.     @Override
  1263.     public String getId() {
  1264.         return id;
  1265.     }

  1266.     @Override
  1267.     public void setId(String id) {
  1268.         this.id = id;
  1269.     }

  1270.     @Override
  1271.     public String getWorkBundleId() {
  1272.         return workBundleId;
  1273.     }

  1274.     @Override
  1275.     public void setWorkBundleId(String workBundleId) {
  1276.         this.workBundleId = workBundleId;
  1277.     }

  1278.     @Override
  1279.     public String getTransactionId() {
  1280.         return transactionId;
  1281.     }

  1282.     @Override
  1283.     public void setTransactionId(String transactionId) {
  1284.         this.transactionId = transactionId;
  1285.     }

  1286.     @Override
  1287.     public IBaseDataObject getTld() {
  1288.         return tld;
  1289.     }

  1290. }