IBaseDataObjectHelper.java

package emissary.core;

import emissary.core.channels.SeekableByteChannelFactory;
import emissary.directory.KeyManipulator;
import emissary.kff.KffDataObjectHandler;
import emissary.parser.SessionParser;

import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable;

/**
 * Utility methods that assist with working with IBaseDataObject's.
 */
public final class IBaseDataObjectHelper {
    /**
     * A logger instance.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(IBaseDataObjectHelper.class);

    private static class InternalIdBaseDataObject extends BaseDataObject {
        private InternalIdBaseDataObject(final UUID internalId) {
            this.internalId = internalId;
        }
    }

    private IBaseDataObjectHelper() {}

    /**
     * Clones an IBaseDataObject.
     * 
     * @param iBaseDataObject the IBaseDataObject to be cloned.
     * @return the clone of the IBaseDataObject passed in.
     */
    public static IBaseDataObject clone(final IBaseDataObject iBaseDataObject) {
        return clone(iBaseDataObject, true);
    }

    /**
     * Clones an IBaseDataObject equivalently to emissary.core.BaseDataObject.clone(), which duplicates some attributes.
     * 
     * A "fullClone" duplicates all attributes.
     * 
     * @deprecated prefer {@link #clone(IBaseDataObject)}
     * @param iBaseDataObject the IBaseDataObject to be cloned.
     * @param fullClone specifies if all fields should be cloned.
     * @return the clone of the IBaseDataObject passed in.
     */
    @Deprecated
    public static IBaseDataObject clone(final IBaseDataObject iBaseDataObject, final boolean fullClone) {
        Validate.notNull(iBaseDataObject, "Required: iBaseDataObject not null");

        final IBaseDataObject bdo = fullClone ? new InternalIdBaseDataObject(iBaseDataObject.getInternalId()) : DataObjectFactory.getInstance();

        final SeekableByteChannelFactory sbcf = iBaseDataObject.getChannelFactory();
        if (sbcf != null) {
            bdo.setChannelFactory(sbcf);
        }

        final List<String> allCurrentForms = iBaseDataObject.getAllCurrentForms();
        for (int i = 0; i < allCurrentForms.size(); i++) {
            bdo.enqueueCurrentForm(allCurrentForms.get(i));
        }
        bdo.setHistory(iBaseDataObject.getTransformHistory());
        bdo.putParameters(iBaseDataObject.getParameters());
        for (final Map.Entry<String, byte[]> entry : iBaseDataObject.getAlternateViews().entrySet()) {
            bdo.addAlternateView(entry.getKey(), entry.getValue());
        }
        bdo.setPriority(iBaseDataObject.getPriority());
        bdo.setCreationTimestamp(iBaseDataObject.getCreationTimestamp());
        if (iBaseDataObject.getExtractedRecords() != null) {
            bdo.setExtractedRecords(iBaseDataObject.getExtractedRecords());
        }
        if (iBaseDataObject.getFilename() != null) {
            bdo.setFilename(iBaseDataObject.getFilename());
        }

        if (fullClone) {
            final String processingError = iBaseDataObject.getProcessingError();
            if (processingError != null) {
                bdo.addProcessingError(processingError.substring(0, processingError.length() - 1));
            }
            bdo.setFontEncoding(iBaseDataObject.getFontEncoding());
            bdo.setNumChildren(iBaseDataObject.getNumChildren());
            bdo.setNumSiblings(iBaseDataObject.getNumSiblings());
            bdo.setBirthOrder(iBaseDataObject.getBirthOrder());
            bdo.setHeader(iBaseDataObject.header() == null ? null : iBaseDataObject.header().clone());
            bdo.setFooter(iBaseDataObject.footer() == null ? null : iBaseDataObject.footer().clone());
            bdo.setHeaderEncoding(iBaseDataObject.getHeaderEncoding());
            bdo.setClassification(iBaseDataObject.getClassification());
            bdo.setBroken(iBaseDataObject.getBroken());
            bdo.setOutputable(iBaseDataObject.isOutputable());
            bdo.setId(iBaseDataObject.getId());
            bdo.setWorkBundleId(iBaseDataObject.getWorkBundleId());
            bdo.setTransactionId(iBaseDataObject.getTransactionId());
        }

        return bdo;
    }

    /**
     * Used to propagate needed parent information to a sprouted child. NOTE: This is taken from
     * emissary.place.MultiFileServerPlace.
     * 
     * @param parentIBaseDataObject the source of parameters to be copied
     * @param childIBaseDataObject the destination for parameters to be copied
     * @param nullifyFileType if true the child fileType is nullified after the copy
     * @param alwaysCopyMetadataKeys set of metadata keys to always copy from parent to child.
     * @param placeKey the place key to be added to the transform history.
     * @param kffDataObjectHandler the kffDataObjectHandler to use to create the kff hashes.
     */
    public static void addParentInformationToChild(final IBaseDataObject parentIBaseDataObject,
            final IBaseDataObject childIBaseDataObject, final boolean nullifyFileType,
            final Set<String> alwaysCopyMetadataKeys, final String placeKey,
            final KffDataObjectHandler kffDataObjectHandler) {
        Validate.notNull(parentIBaseDataObject, "Required: parentIBaseDataObject not null");
        Validate.notNull(childIBaseDataObject, "Required: childIBaseDataObject not null");
        Validate.notNull(alwaysCopyMetadataKeys, "Required: alwaysCopyMetadataKeys not null");
        Validate.notNull(placeKey, "Required: placeKey not null");
        Validate.notNull(kffDataObjectHandler, "Required: kffDataObjectHandler not null");

        // Copy over the classification
        if (parentIBaseDataObject.getClassification() != null) {
            childIBaseDataObject.setClassification(parentIBaseDataObject.getClassification());
        }

        // And some other things we configure to be always copied
        for (final String meta : alwaysCopyMetadataKeys) {
            final List<Object> parentVals = parentIBaseDataObject.getParameter(meta);

            if (parentVals != null) {
                childIBaseDataObject.putParameter(meta, parentVals);
            }
        }

        // Copy over the transform history up to this point
        childIBaseDataObject.setHistory(parentIBaseDataObject.getTransformHistory());
        childIBaseDataObject.appendTransformHistory(KeyManipulator.makeSproutKey(placeKey));
        try {
            childIBaseDataObject.putParameter(SessionParser.ORIG_DOC_SIZE_KEY,
                    Long.toString(childIBaseDataObject.getChannelSize()));
        } catch (IOException e) {
            // Do not add the ORIG_DOC_SIZE_KEY parameter.
        }

        // start over with no FILETYPE if so directed
        if (nullifyFileType) {
            childIBaseDataObject.setFileType(null);
        }

        // Set up the proper KFF/HASH information for the child
        // Change parent hit so it doesn't look like hit on the child
        KffDataObjectHandler.parentToChild(childIBaseDataObject);

        // Hash the new child data, overwrites parent hashes if any
        kffDataObjectHandler.hash(childIBaseDataObject, true);
    }

    /**
     * Used to propagate needed parent information to a sprouted child. NOTE: This is taken from
     * emissary.place.MultiFileServerPlace.
     * 
     * @param parent the source of parameters to be copied
     * @param children the destination for parameters to be copied
     * @param nullifyFileType if true the child fileType is nullified after the copy
     * @param alwaysCopyMetadataKeys set of metadata keys to always copy from parent to child.
     * @param placeKey the place key to be added to the transform history.
     * @param kffDataObjectHandler the kffDataObjectHandler to use to create the kff hashes.
     */
    public static void addParentInformationToChildren(final IBaseDataObject parent, @Nullable final List<IBaseDataObject> children,
            final boolean nullifyFileType, final Set<String> alwaysCopyMetadataKeys, final String placeKey,
            final KffDataObjectHandler kffDataObjectHandler) {
        Validate.notNull(parent, "Required: parent not null");
        Validate.notNull(alwaysCopyMetadataKeys, "Required: alwaysCopyMetadataKeys not null");
        Validate.notNull(placeKey, "Required: placeKey not null");
        Validate.notNull(kffDataObjectHandler, "Required: kffDataObjectHandler not null");

        if (children != null) {
            int birthOrder = 1;

            final int totalNumSiblings = children.size();
            for (final IBaseDataObject child : children) {
                if (child == null) {
                    LOGGER.warn("addParentInformation with null child");
                    continue;
                }
                addParentInformationToChild(parent, child, nullifyFileType, alwaysCopyMetadataKeys, placeKey,
                        kffDataObjectHandler);
                child.setBirthOrder(birthOrder++);
                child.setNumSiblings(totalNumSiblings);
            }
        }
    }

    /**
     * Used to propagate needed parent metadata parameter information to a sprouted child except for those parameters whose
     * names are specified in a regular expression pattern.
     *
     * @param parentIBaseDataObject the source of parameters to be copied
     * @param childIBaseDataObject the destination for parameters to be copied
     * @param excludedParametersPattern compiled regular expression specifying parameter names not to copy down
     */

    public static void addParentInformationToChildExcluding(final IBaseDataObject parentIBaseDataObject,
            final IBaseDataObject childIBaseDataObject,
            final Pattern excludedParametersPattern) {
        Validate.notNull(parentIBaseDataObject, "Required: parentIBaseDataObject not null");
        Validate.notNull(childIBaseDataObject, "Required: childIBaseDataObject not null");
        Validate.notNull(excludedParametersPattern, "Required: excludedParametersPattern not null");
        StreamSupport.stream(parentIBaseDataObject.getParameterKeys().spliterator(), false)
                .filter(key -> !excludedParametersPattern.matcher(key).matches())
                .forEach(key -> childIBaseDataObject.putParameter(key, parentIBaseDataObject.getParameter(key)));
    }

    /**
     * Search for the first preferred view by regular expression or use the primary data if none match
     *
     * @param payload the payload to pull data from
     * @param preferredViewNamePatterns the list of referred view regular expression patterns (null returns data)
     */
    public static byte[] findPreferredDataByRegex(final IBaseDataObject payload, List<Pattern> preferredViewNamePatterns) {
        Validate.isTrue(payload != null, "Required: payload != null");

        return Optional.ofNullable(preferredViewNamePatterns).orElse(Collections.emptyList()).stream()
                .map(preferredViewNamePattern -> findFirstAlternameViewNameByRegex(payload, preferredViewNamePattern))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .map(payload::getAlternateView)
                .findFirst().orElse(payload.data());
    }

    private static Optional<String> findFirstAlternameViewNameByRegex(IBaseDataObject payload, Pattern preferredViewNamePattern) {
        return payload.getAlternateViewNames().stream()
                .filter(altViewName -> preferredViewNamePattern.matcher(altViewName).find())
                .findFirst();
    }

    /**
     * Search for the first preferred view that is present or use the primary data if none
     *
     * @param payload the payload to pull data from
     * @param preferredViews the list of preferred views (null returns data)
     */
    public static byte[] findPreferredData(final IBaseDataObject payload, List<String> preferredViews) {
        Validate.isTrue(payload != null, "Required: payload != null");

        final Set<String> altViewNames = payload.getAlternateViewNames();

        if (preferredViews != null) {
            for (final String view : preferredViews) {
                if (altViewNames.contains(view)) {
                    return payload.getAlternateView(view);
                }
            }
        }

        return payload.data();
    }
}