com.nridge.core.base.doc.Document.java Source code

Java tutorial

Introduction

Here is the source code for com.nridge.core.base.doc.Document.java

Source

/*
 * NorthRidge Software, LLC - Copyright (c) 2019.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nridge.core.base.doc;

import com.nridge.core.base.field.Field;
import com.nridge.core.base.field.data.DataBag;
import com.nridge.core.base.field.data.DataTable;

import com.nridge.core.base.field.data.DataTextField;
import com.nridge.core.base.std.DigitalHash;
import com.nridge.core.base.std.StrUtl;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * A Document is designed to model a document that might
 * be stored in a Search Index or an Enterprise Content
 * Management System (ECM).
 * <p>
 * The class is a composite of the following:
 * </p>
 * <ul>
 *     <li>Fields {@link DataBag}</li>
 *     <li>Rows {@link DataTable}</li>
 *     <li>Typed Relationships</li>
 *     <li>ACLs</li>
 * </ul>
 *
 * @since 1.0
 * @author Al Cole
 */
public class Document {
    private String mType;
    private DataTable mTable;
    private int mSchemaVersion;
    private HashMap<String, String> mACL;
    private HashMap<String, String> mFeatures;
    private ArrayList<Relationship> mRelationships;
    private transient HashMap<String, Object> mProperties;

    /**
     * Constructor accepts a type parameter and initializes the Document
     * accordingly.
     *
     * @param aType Application defined type name.
     */
    public Document(String aType) {
        instantiate(aType, null, null);
    }

    /**
     * Constructor accepts a type and data bag instance parameter and initializes
     * the Document accordingly.
     *
     * @param aType Application defined type name.
     * @param aBag Data bag instance.
     */
    public Document(String aType, DataBag aBag) {
        instantiate(aType, aBag, null);
    }

    /**
     * Constructor accepts a type and data table  parameter
     * and initializes the Document accordingly.
     *
     * @param aType Application defined type name.
     * @param aTable Data table instance.
     */
    public Document(String aType, DataTable aTable) {
        instantiate(aType, null, aTable);
    }

    /**
     * Constructor clones an existing Document.
     *
     * @param aDocument Source document instance to clone.
     */
    public Document(Document aDocument) {
        if (aDocument != null) {
            setTable(new DataTable(aDocument.getTable()));

            setType(aDocument.getType());
            setName(aDocument.getName());
            setTitle(aDocument.getTitle());
            setSchemaVersion(aDocument.getSchemaVersion());
            mRelationships = new ArrayList<Relationship>();
            if (aDocument.getRelationships().size() > 0) {
                for (Relationship relationship : aDocument.getRelationships())
                    this.addRelationship(new Relationship(relationship));
            }
            mACL = new HashMap<String, String>(aDocument.getACL());
            mFeatures = new HashMap<String, String>(aDocument.getFeatures());
        }
    }

    /**
     * Creates a new instance of the document based on the
     * one currently populated.  The new instance will contain
     * only those fields that have been populated from previous
     * assignments.  This can be helpful when you start with a
     * schema table containing many possible fields that never
     * end up getting populated in a load operation.
     *
     * @return Document instance.
     */
    public Document collapseUnusedFields() {
        Document newDoc;

        newDoc = new Document(getType());
        String docName = getName();
        if (StringUtils.isNotEmpty(docName))
            newDoc.setName(docName);
        newDoc.setFeatures(getFeatures());
        newDoc.setTable(getTable().collapseUnusedColumns());
        for (Relationship curRelationship : mRelationships)
            newDoc.addRelationship(curRelationship.collapseUnusedFields());

        return newDoc;
    }

    private void instantiate(String aType, DataBag aBag, DataTable aTable) {
        mType = aType;
        mSchemaVersion = Doc.SCHEMA_VERSION_DEFAULT;
        if (aTable != null)
            mTable = new DataTable(aTable);
        else if (aBag != null)
            mTable = new DataTable(aBag);
        else
            mTable = new DataTable(aType);

        if (StringUtils.isEmpty(getTitle()))
            setTitle(aType);

        mACL = new HashMap<String, String>(); // Example: Name, Permission (R W U D)
        mFeatures = new HashMap<String, String>();
        mRelationships = new ArrayList<Relationship>();
    }

    /**
     * Returns a string representation of a Document.
     *
     * @return String summary representation of this Document.
     */
    @Override
    public String toString() {
        String msgStr = mTable.getColumnBag().toString();
        int count = mTable.rowCount();
        if (count > 0)
            msgStr += String.format(" - [%d rows]", count);
        count = mRelationships.size();
        if (count > 0)
            msgStr += String.format(" - [%d rels]", count);
        count = mACL.size();
        if (count > 0)
            msgStr += String.format(" - [%d acls]", count);

        return msgStr;
    }

    /**
     * Returns the application defined document type.
     *
     * @return Application defined type name.
     */
    public String getType() {
        return mType;
    }

    /**
     * Assigns the application defined document type.
     *
     * @param aType Application defined type name.
     */
    public void setType(String aType) {
        mType = aType;
    }

    /**
     * Returns the application defined schema version number.
     *
     * @return Schema version number.
     */
    public int getSchemaVersion() {
        return mSchemaVersion;
    }

    /**
     * Assigns an application defined schema version number.
     *
     * @param aSchemaVersion Schema version number.
     */
    public void setSchemaVersion(int aSchemaVersion) {
        mSchemaVersion = aSchemaVersion;
    }

    /**
     * Returns the name of the document.
     *
     * @return Name of the document.
     */
    public String getName() {
        return mTable.getColumnBag().getName();
    }

    /**
     * Assigns the document name.
     *
     * @param aName Document name.
     */
    public void setName(String aName) {
        mTable.getColumnBag().setName(aName);
    }

    /**
     * Returns the document title.
     *
     * @return Document title.
     */
    public String getTitle() {
        return mTable.getColumnBag().getTitle();
    }

    /**
     * Assigns the document title.
     *
     * @param aTitle Document title.
     */
    public void setTitle(String aTitle) {
        mTable.getColumnBag().setTitle(aTitle);
    }

    /**
     * Returns the {@link DataBag} representation of the fields
     * defined for the document.
     *
     * @return Data bag representation of the document fields.
     */
    public DataBag getBag() {
        return mTable.getColumnBag();
    }

    /**
     * Assigns a {@link DataBag} of fields to the document.
     *
     * @param aBag Document fields.
     */
    public void setBag(final DataBag aBag) {
        if (aBag != null)
            mTable = new DataTable(aBag);
    }

    /**
     * Returns the {@link DataTable} representation of the rows
     * defined for the document.
     *
     * @return Data bag representation of the document fields.
     */
    public DataTable getTable() {
        return mTable;
    }

    /**
     * Assigns a {@link DataTable} of rows to the document.
     *
     * @param aTable Table instance.
     */
    public void setTable(final DataTable aTable) {
        if (aTable != null)
            mTable = new DataTable(aTable);
    }

    /**
     * Adds the document instance to the related documents.
     *
     * @param aRelationship Relationship instance.
     */
    public void addRelationship(Relationship aRelationship) {
        if (aRelationship != null)
            mRelationships.add(aRelationship);
    }

    /**
     * Adds a relationship document of a type specified to
     * the document.
     * <p>
     * <b>Note:</b> This method will examine the list of existing
     * relationships to determine if one already exists of the
     * type specified.  If it does, then this document will be
     * added to existing list of the matching relationship.
     * </p>
     *
     * @param aType Relationship type.
     * @param aDocument Destination document instance.
     */
    public void addRelationship(String aType, Document aDocument) {
        boolean isAdded = false;
        for (Relationship docRelationship : mRelationships) {
            if (docRelationship.getType().equals(aType)) {
                docRelationship.add(aDocument);
                isAdded = true;
                break;
            }

        }
        if (!isAdded)
            mRelationships.add(new Relationship(aType, aDocument));
    }

    /**
     * Adds multiple relationship documents of a type specified to
     * the document.
     * <p>
     * <b>Note:</b> This method will examine the list of existing
     * relationships to determine if one already exists of the
     * type specified.  If it does, then this document will be
     * added to existing list of the matching relationship.
     * </p>
     *
     * @param aType Relationship type.
     * @param aDocuments Destination document instances.
     */
    public void addRelationship(String aType, Document... aDocuments) {
        boolean isAdded = false;
        for (Relationship docRelationship : mRelationships) {
            if (docRelationship.getType().equals(aType)) {
                docRelationship.add(aDocuments);
                isAdded = true;
                break;
            }

        }
        if (!isAdded)
            mRelationships.add(new Relationship(aType, aDocuments));
    }

    /**
     * Adds a relationship document of a type specified to
     * the document.  In addition, the relationship will be
     * assigned the bag of fields specified.
     * <p>
     * <b>Note:</b> All relationships added this way will
     * be added to the list as unique instances.  No attempt
     * will be made to match the relationship to an existing
     * type.
     * </p>
     *
     * @param aType Relationship type.
     * @param aBag Data fields.
     * @param aDocument Destination document.
     */
    public void addRelationship(String aType, DataBag aBag, Document aDocument) {
        mRelationships.add(new Relationship(aType, aBag, aDocument));
    }

    /**
     * Adds multiple relationship documents of a type specified to
     * the document.  In addition, the relationship will be
     * assigned the bag of fields specified.
     * <p>
     * <b>Note:</b> All relationships added this way will
     * be added to the list as unique instances.  No attempt
     * will be made to match the relationship to an existing
     * type.
     * </p>
     *
     * @param aType Relationship type.
     * @param aBag Data fields.
     * @param aDocuments Destination documents.
     */
    public void addRelationship(String aType, DataBag aBag, Document... aDocuments) {
        mRelationships.add(new Relationship(aType, aBag, aDocuments));
    }

    /**
     * Returns the count of related documents.
     *
     * @return Count of documents.
     */
    public int relationshipCount() {
        return mRelationships.size();
    }

    /**
     * Assigns a list of relationships to the document.
     *
     * @param aRelationships Relationship list.
     */
    public void setRelationships(ArrayList<Relationship> aRelationships) {
        if (aRelationships != null)
            mRelationships = aRelationships;
    }

    /**
     * Returns a collection of relationships associated with this
     * document.
     *
     * @return Collection of all relationships.
     */
    public ArrayList<Relationship> getRelationships() {
        return mRelationships;
    }

    /**
     * Returns a collection of relationships associated with this
     * document that matches the type.
     *
     * @param aType Type of relationship.
     *
     * @return List of relationships matching the type parameter.
     */
    public ArrayList<Relationship> getRelationships(String aType) {
        ArrayList<Relationship> relationshipList = new ArrayList<Relationship>();
        for (Relationship relationship : mRelationships) {
            if (relationship.getType().equals(aType))
                relationshipList.add(relationship);
        }

        return relationshipList;
    }

    /**
     * Convenience method that will return the first relationship
     * that matches the relationship type.
     *
     * @param aType Type of relationship.
     *
     * @return Relationship instance or <i>null</i> if none match.
     */
    public Relationship getFirstRelationship(String aType) {
        ArrayList<Relationship> relationshipList = getRelationships(aType);
        if (relationshipList.size() > 0)
            return relationshipList.get(0);
        else
            return null;
    }

    /**
     * Returns a collection of relationship properties (data bag)
     * matching the type parameter.
     *
     * @param aType Type of relationship.
     *
     * @return List of relationship properties (data bags).
     */
    public ArrayList<DataBag> getRelatedProperties(String aType) {
        ArrayList<DataBag> dataBagList = new ArrayList<>();
        ArrayList<Relationship> relationshipList = getRelationships(aType);
        for (Relationship relationship : relationshipList)
            dataBagList.add(relationship.getBag());

        return dataBagList;
    }

    /**
     * Convenience method that will return the properties (data bag)
     * from the first matching relationship type.
     *
     * @param aType Type of relationship.
     *
     * @return Relationship properties (data bag) or <i>null</i> if none match.
     */
    public DataBag getFirstRelatedProperties(String aType) {
        ArrayList<DataBag> dataBagList = getRelatedProperties(aType);
        if (dataBagList.size() > 0)
            return dataBagList.get(0);
        else
            return null;
    }

    /**
     * Returns a collection of related documents that match the
     * relationship type parameter.  If the type is matched with
     * more than one relationship, then the list will be a union
     * of those related documents.
     *
     * @param aType Type of relationship.
     *
     * @return List of related documents.
     */
    public ArrayList<Document> getRelatedDocuments(String aType) {
        ArrayList<Document> documentList = new ArrayList<>();
        ArrayList<Relationship> relationshipList = getRelationships(aType);
        for (Relationship relationship : relationshipList) {
            for (Document document : relationship.getDocuments())
                documentList.add(document);
        }

        return documentList;
    }

    /**
     * Returns a collection of related documents.
     *
     * @return List of related documents.
     */
    public ArrayList<Document> getRelatedDocuments() {
        ArrayList<Document> documentList = new ArrayList<>();
        for (Relationship relationship : getRelationships()) {
            for (Document document : relationship.getDocuments())
                documentList.add(document);
        }

        return documentList;
    }

    /**
     * Convenience method that will return the first document within
     * the relationship that matches the type parameter.
     *
     * @param aType Type of relationship.
     *
     * @return Document instance or or <i>null</i> if none match.
     */
    public Document getFirstRelatedDocument(String aType) {
        ArrayList<Document> documentList = getRelatedDocuments(aType);
        if (documentList.size() > 0)
            return documentList.get(0);
        else
            return null;
    }

    /**
     * Returns a map of an ACL for the document.
     *
     * @return Map of document ACL.
     */
    public HashMap<String, String> getACL() {
        return mACL;
    }

    /**
     * Assigns a map of an ACL to the document.
     *
     * @param anACL Document ACL.
     */
    public void setACL(HashMap<String, String> anACL) {
        mACL = anACL;
    }

    /**
     * Returns a {@link DataTable} representation of the document ACL.
     *
     * @return User ACL table.
     */
    public DataTable getACLTable() {
        DataTable persistTable = new DataTable(mType + "_acls");
        persistTable.add(new DataTextField(Doc.FIELD_ACL_NAME, Doc.FIELD_ACL_TITLE));
        persistTable.add(new DataTextField(Doc.FIELD_PERMISSION_NAME, Doc.FIELD_PERMISSION_TITLE));

        Map<String, String> aclMap = mACL;
        for (Map.Entry<String, String> aclEntry : aclMap.entrySet()) {
            persistTable.newRow();
            persistTable.setValueByName(Doc.FIELD_ACL_NAME, aclEntry.getKey());
            persistTable.setValueByName(Doc.FIELD_PERMISSION_NAME, aclEntry.getValue());
            persistTable.addRow();
        }

        return persistTable;
    }

    /**
     * Process the document information (properties, relationships)
     * through the digital hash algorithm.
     *
     * @param aHash Digital hash instance.
     * @param anIsFeatureIncluded Should features be included.
     *
     * @throws IOException Triggered by hash algorithm.
     */
    public void processHash(DigitalHash aHash, boolean anIsFeatureIncluded) throws IOException {
        DataBag relBag;

        aHash.processBuffer(getType());
        aHash.processBuffer(getName());
        if (anIsFeatureIncluded) {
            for (Map.Entry<String, String> featureEntry : getFeatures().entrySet()) {
                aHash.processBuffer(featureEntry.getKey());
                aHash.processBuffer(featureEntry.getValue());
            }
        }
        mTable.processHash(aHash, anIsFeatureIncluded);
        for (Relationship relationship : mRelationships) {
            relBag = relationship.getBag();
            relBag.processHash(aHash, anIsFeatureIncluded);
            for (Document document : relationship.getDocuments())
                document.processHash(aHash, anIsFeatureIncluded);
        }
    }

    /**
     * Generates a unique hash string using the MD5 algorithm using
     * the document information.
     *
     * @param anIsFeatureIncluded Should feature name/values be included?
     *
     * @return Unique hash string.
     */
    public String generateUniqueHash(boolean anIsFeatureIncluded) {
        String hashId;

        DigitalHash digitalHash = new DigitalHash();
        try {
            processHash(digitalHash, anIsFeatureIncluded);
            hashId = digitalHash.getHashSequence();
        } catch (IOException e) {
            UUID uniqueId = UUID.randomUUID();
            hashId = uniqueId.toString();
        }

        return hashId;
    }

    /**
     * Empties the document of any assigned values (fields, acl, related).
     */
    public void reset() {
        mACL.clear();
        mFeatures.clear();
        mTable.emptyRows();
        mRelationships.clear();
        mTable.getColumnBag().resetValues();
    }

    /**
     * Convenience method examines all of the fields in the document to determine if
     * they are valid. A validation check ensures values are assigned when required
     * and do not exceed range limits (if assigned).
     * <p>
     * <b>Note:</b> If a field fails the validation check, then a property called
     * <i>Field.VALIDATION_PROPERTY_NAME</i> will be assigned a relevant message.
     * </p>
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isValid() {
        return getBag().isValid();
    }

    /**
     * Add a unique feature to this document.  A feature enhances the core
     * capability of the document.  Standard features are listed below.
     * <ul>
     *     <li>Field.FEATURE_OPERATION_NAME</li>
     * </ul>
     *
     * @param aName Name of the feature.
     * @param aValue Value to associate with the feature.
     */
    public void addFeature(String aName, String aValue) {
        mFeatures.put(aName, aValue);
    }

    /**
     * Add a unique feature to this document.  A feature enhances the core
     * capability of the document.
     *
     * @param aName Name of the feature.
     * @param aValue Value to associate with the feature.
     */
    public void addFeature(String aName, int aValue) {
        addFeature(aName, Integer.toString(aValue));
    }

    /**
     * Enabling the feature will add the name and assign it a
     * value of <i>StrUtl.STRING_TRUE</i>.
     *
     * @param aName Name of the feature.
     */
    public void enableFeature(String aName) {
        mFeatures.put(aName, StrUtl.STRING_TRUE);
    }

    /**
     * Disabling a feature will remove its name and value
     * from the internal list.
     *
     * @param aName Name of feature.
     */
    public void disableFeature(String aName) {
        mFeatures.remove(aName);
    }

    /**
     * Returns <i>true</i> if the feature was previously
     * added and assigned a value.
     *
     * @param aName Name of feature.
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isFeatureAssigned(String aName) {
        return (getFeature(aName) != null);
    }

    /**
     * Returns <i>true</i> if the feature was previously
     * added and assigned a value of <i>StrUtl.STRING_TRUE</i>.
     *
     * @param aName Name of feature.
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isFeatureTrue(String aName) {
        return StrUtl.stringToBoolean(mFeatures.get(aName));
    }

    /**
     * Returns <i>true</i> if the feature was previously
     * added and not assigned a value of <i>StrUtl.STRING_TRUE</i>.
     *
     * @param aName Name of feature.
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isFeatureFalse(String aName) {
        return !StrUtl.stringToBoolean(mFeatures.get(aName));
    }

    /**
     * Returns <i>true</i> if the feature was previously
     * added and its value matches the one provided as a
     * parameter.
     *
     * @param aName Feature name.
     * @param aValue Feature value to match.
     *
     * @return <i>true</i> or <i>false</i>
     */
    public boolean isFeatureEqual(String aName, String aValue) {
        String featureValue = getFeature(aName);
        return StringUtils.equalsIgnoreCase(featureValue, aValue);
    }

    /**
     * Count of unique features assigned to this document.
     *
     * @return Feature count.
     */
    public int featureCount() {
        return mFeatures.size();
    }

    /**
     * Returns the String associated with the feature name or
     * <i>null</i> if the name could not be found.
     *
     * @param aName Feature name.
     *
     * @return Feature value or <i>null</i>
     */
    public String getFeature(String aName) {
        return mFeatures.get(aName);
    }

    /**
     * Returns the int associated with the feature name.
     *
     * @param aName Feature name.
     *
     * @return Feature value or <i>null</i>
     */
    public int getFeatureAsInt(String aName) {
        return Field.createInt(getFeature(aName));
    }

    /**
     * Removes all features assigned to this object instance.
     */
    public void clearFeatures() {
        mFeatures.clear();
    }

    /**
     * Assigns the hash map of features to the list.
     *
     * @param aFeatures Feature list.
     */
    public void setFeatures(HashMap<String, String> aFeatures) {
        if (aFeatures != null)
            mFeatures = new HashMap<String, String>(aFeatures);
    }

    /**
     * Indicates whether some other object is "equal to" this one.
     *
     * @param anObject Reference object with which to compare.
     * @return  {@code true} if this object is the same as the anObject
     *          argument; {@code false} otherwise.
     */
    @Override
    public boolean equals(Object anObject) {
        if (this == anObject)
            return true;
        if (anObject == null || getClass() != anObject.getClass())
            return false;

        Document document = (Document) anObject;

        if (mSchemaVersion != document.mSchemaVersion)
            return false;
        if (mType != null ? !mType.equals(document.mType) : document.mType != null)
            return false;
        if (mTable != null ? !mTable.equals(document.mTable) : document.mTable != null)
            return false;
        if (mACL != null ? !mACL.equals(document.mACL) : document.mACL != null)
            return false;
        if (mFeatures != null ? !mFeatures.equals(document.mFeatures) : document.mFeatures != null)
            return false;

        return !(mRelationships != null ? !mRelationships.equals(document.mRelationships)
                : document.mRelationships != null);
    }

    /**
     * Returns a hash code value for the object. This method is
     * supported for the benefit of hash tables such as those provided by
     * {@link java.util.HashMap}.
     *
     * @return A hash code value for this object.
     */
    @Override
    public int hashCode() {
        int result = mType != null ? mType.hashCode() : 0;
        result = 31 * result + (mTable != null ? mTable.hashCode() : 0);
        result = 31 * result + mSchemaVersion;
        result = 31 * result + (mACL != null ? mACL.hashCode() : 0);
        result = 31 * result + (mFeatures != null ? mFeatures.hashCode() : 0);
        result = 31 * result + (mRelationships != null ? mRelationships.hashCode() : 0);

        return result;
    }

    /**
     * Returns a read-only copy of the internal map containing
     * feature list.
     *
     * @return Internal feature map instance.
     */
    public final HashMap<String, String> getFeatures() {
        return mFeatures;
    }

    /**
     * Add an application defined property to the document.
     * <b>Notes:</b>
     * <ul>
     *     <li>The goal of the DataBag is to strike a balance between
     *     providing enough properties to adequately model application
     *     related data without overloading it.</li>
     *     <li>This method offers a mechanism to capture additional
     *     (application specific) properties that may be needed.</li>
     *     <li>Properties added with this method are transient and
     *     will not be stored when saved.</li>
     * </ul>
     *
     * @param aName Property name (duplicates are not supported).
     * @param anObject Instance of an object.
     */
    public void addProperty(String aName, Object anObject) {
        if (mProperties == null)
            mProperties = new HashMap<String, Object>();
        mProperties.put(aName, anObject);
    }

    /**
     * Returns the object associated with the property name or
     * <i>null</i> if the name could not be matched.
     *
     * @param aName Name of the property.
     * @return Instance of an object.
     */
    public Object getProperty(String aName) {
        if (mProperties == null)
            return null;
        else
            return mProperties.get(aName);
    }

    /**
     * Removes all application defined properties assigned to this bag.
     */
    public void clearBagProperties() {
        if (mProperties != null)
            mProperties.clear();
    }
}