Checksum Aspect – Part 2

Now that we know a little bit about checksums and Aspects, here’s what I wanted my Aspect to do:

  • Calculate the checksum of any dm_sysobject in the repository that has content,
  • Store the checksum, the date the checksum was calculated, and the algorithm used to calculate it,
  • Determine if the stored checksum is valid by recalculating it and comparing it to the stored value, and
  • Calculate checksums using a variety of common algorithms.

This is briefly how the implementing class is designed:

  • Getters
    • getChecksum() – String – return the checksum value stored in the Aspect attribute.
    • getChecksumDate() – DfTime – return the date the checksum was calculated from the Aspect attribute.
    • getChecksumAlgorithm() – String – return the name of the algorithm used to calculate the checksum from the Aspect attribute.
  • Setters
    • setChecksum(String) – save the checksum value to the Aspect attribute.
    • setChecksumDate(IDfTime) – save the date the checksum was calculated to the Aspect attribute.
    • setChecksumAlgorithm(String) – save the name of the algorithm used to calculate the checksum to the Aspect attribute.
  • Helpers
    • calculateChecksum(String) – String – calculate the checksum using the specified algorithm.
    • validateChecksum() – Boolean – compare the checksum value stored in the Aspect attribute with a recalculation of the checksum using the same algorithm.
    • updateChecksum(String) – primary entry point for the Aspect.  Calculate the checksum and save it, the date, and the algorithm used to the Aspect attributes.

And here is the actual code for the Aspect and its interface.

package com.dm_misc.aspect;

import java.io.ByteArrayInputStream;
import java.security.MessageDigest;

import com.documentum.fc.client.*;
import com.documentum.fc.common.*;

public class ChecksumAspect extends DfDocument implements IChecksumAspect {

	// strings to define the aspect attrs
	private static final String ATTR_CHECKSUM = "checksum_aspect.checksum";
	private static final String ATTR_CHECKSUM_DATE = "checksum_aspect.checksum_date";
	private static final String ATTR_CHECKSUM_ALGORITHM = "checksum_aspect.checksum_algorithm";

	// the default digest algorithm
	private static final String _ALGORITHM = "SHA-256";

	// valid algorithms
	private static final String[] validAlgorithms = { "SHA-256", "MD5", "MD2", "SHA-1", "SHA-256", "SHA-384", "SHA-512" };

	public String getChecksum() throws DfException {
		return this.getString(ATTR_CHECKSUM);
	}

	public IDfTime getChecksumDate() throws DfException {
		return this.getTime(ATTR_CHECKSUM_DATE);
	}

	public String getChecksumAlgorithm() throws DfException {
		return this.getString(ATTR_CHECKSUM_ALGORITHM);
	}

	private void setChecksum(String checksum) throws DfException {
		this.setString(ATTR_CHECKSUM,checksum);
	}

	private void setChecksumDate(IDfTime checksumDate) throws DfException {
		this.setTime(ATTR_CHECKSUM_DATE, checksumDate);
	}

	private void setChecksumAlgorithm(String algorithm) throws DfException {
		if ((algorithm == null) || (algorithm.length() == 0)) {
			this.setString(ATTR_CHECKSUM_ALGORITHM,_ALGORITHM);
		} else {
			this.setString(ATTR_CHECKSUM_ALGORITHM, algorithm);
		}
	}

	public boolean validateChecksum() throws DfException {
		if (this.getString(ATTR_CHECKSUM).equalsIgnoreCase(calculateChecksum(this.getChecksumAlgorithm()))) {
			return true;
		} else {
			return false;
		}
	}

	public void updateChecksum(String algorithm) throws DfException {

		// make sure we have a valid algorithm
		algorithm = validateAlgorithm(algorithm);

		// do update
		setChecksum(calculateChecksum(algorithm));
		setChecksumDate(new DfTime());
		setChecksumAlgorithm(algorithm);
	}

	public String calculateChecksum(String algorithm) throws DfException {
		ByteArrayInputStream content = null;
		String checksum = null;

		// make sure we have a valid algorithm
		algorithm = validateAlgorithm(algorithm);

		try {

			// new digest
			MessageDigest digest = MessageDigest.getInstance(algorithm);

			// get content
			if (this.getContentSize() > 0) {
				content = this.getContent();
				int numRead = 0;
				byte[] buf = new byte[4096];
		        while ((numRead = content.read(buf)) != -1)
		        {
		           digest.update(buf, 0, numRead);
		        }
			} else {
				throw new DfException ("Object " + this.getObjectId().toString() + " does not contain any content.");
			}

	        // convert checksum to string
	        checksum = convertBytesToHex(digest.digest());

		} catch (Exception e) {
			DfLogger.error(this, "ChecksumAspect Error: " + e.getMessage(), null, e);
		}

		return checksum;
	}

	private String convertBytesToHex(byte[] val) {
	      char[] hexChars =
	         {'0', '1', '2', '3', '4', '5', '6', '7',  '8',
	          '9', 'a', 'b', 'c', 'd', 'e', 'f' };

	      // A hex char corresponds to 4 bits. Hence a byte (8 bits)
	      // corresponds to two hex chars.
	      StringBuffer bufHexVal = new StringBuffer(val.length * 2);
	      for (int i = 0; i < val.length; i++) { 	         byte b = val[i]; 	         int hiNibble = ((b & 0xf0) >> 4);
	         int loNibble = (b & 0x0f);
	         bufHexVal.append(hexChars[hiNibble]).append(hexChars[loNibble]);
	      }

	      String hexString = bufHexVal.toString();
	      return hexString;
        }

	private String validateAlgorithm(String algorithm) {
		boolean valid = false;

		for (int i=0; i<validAlgorithms.length; i++) {
			if (validAlgorithms[i].equalsIgnoreCase(algorithm)) {
				valid = true;
				break;
			}
		}

		if ((algorithm == null) || (algorithm.length() == 0) || valid == false) {
			algorithm = _ALGORITHM;
		}

		return algorithm;
	}

}

package com.dm_misc.aspect;
import com.documentum.fc.common.*;

public interface IChecksumAspect {
   public String getChecksum() throws DfException;
   public boolean validateChecksum() throws DfException;
   public String calculateChecksum(String algorithm) throws DfException;
   public IDfTime getChecksumDate() throws DfException;
   public String getChecksumAlgorithm() throws DfException;
   public void updateChecksum(String algorithm) throws DfException;
}

Note how the Aspect attributes are named.  As mentioned in the previous post, aspect attributes must be addresses as <aspect name>.<attribute name>.

You can download the entire checksum Aspect as a Composer project here.

Installing an Aspect is similar to installing a TBO or any other BOF module; therefore I will not cover the installation here. Instead, I refer you to my post, ‘TBO – Part 5’ for details on creating and installing BOF modules.

The next post will demonstrate how I tested my checksum Aspect because, as it turned out, testing didn’t work exactly as I had planned.

Advertisements

About Scott
I have been implementing Documentum solutions since 1997. In 2005, I published a book about developing Documentum solutions for the Documentum Desktop Client (ISBN 0595339689). In 2010, I began this blog as a record of interesting and (hopefully) helpful bits of information related to Documentum, and as a creative outlet.

7 Responses to Checksum Aspect – Part 2

  1. doquent says:

    Scott,

    Thanks for sharing these hands-on posts on aspects.

    This aspect of Documentum features (pun intended 🙂 ) has taken some time to mature. When aspects were introduced initially, the support for developing or working with them wasn’t ready. I am sure that your posts will help anyone trying to figure out the implementation details related to aspects.

    Thanks,
    Pawan

    Like

  2. Pingback: Checksum Aspect – Part 0 « dm_misc: Miscellaneous Documentum Tidbits and Information

  3. Pingback: Checksum Aspect – Part 1 « dm_misc: Miscellaneous Documentum Tidbits and Information

  4. Mark Faine says:

    Just curious as to why you store the date the checksum was applied, shouldn’t this be (or be very, very close to) the last modified date?

    Like

    • Scott says:

      In my project there is no TBO to automatically fire the checksum process. I created a menu option in Webtop that allowed the user to attach, detach and change the algorithm used to create the checksum. Therefore, saving the date could be an important piece of info.

      Like

  5. Mark Faine says:

    Actually, after giving it some thought it is for me too. I write the checksum in the doSave method but that gets written when the validation for the current checksum fails which means the content has changed but not when an attribute of the object is changed which would still change the modified date. So, I guess I wasn’t thinking. The checksum date would indicate when the checksum was written for a new object or when the content changed but would not be updated for an attribute change.

    Like

  6. Pingback: Links to All of My Source Code | dm_misc: Miscellaneous Documentum Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: