Package ca.carleton.gcrc.couch.app.impl

Source Code of ca.carleton.gcrc.couch.app.impl.DocumentManifest

package ca.carleton.gcrc.couch.app.impl;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.json.JSONObject;
import ca.carleton.gcrc.couch.app.DigestComputer;
import ca.carleton.gcrc.couch.app.Document;
import ca.carleton.gcrc.couch.app.DocumentDigest;
import ca.carleton.gcrc.json.JSONSupport;

public class DocumentManifest {

  final static public String MANIFEST_KEY = "nunaliit_manifest";

  static private DigestComputerSha1 sha1DigestComputer = new DigestComputerSha1();
 
  /**
   * Analyzes a document and determines if the document has been modified
   * since the time the manifest was computed.
   * @param targetDoc Remote JSON document to test for modification
   * @param digestComputer Digest computer used to verify the compute intermediate
   * digest and verify authenticity of given manifest.
   * @return True, if remote document appears to have been modified since
   * the manifest was computed.
   */
  static public boolean hasDocumentBeenModified(
      JSONObject targetDoc
      ) {

    JSONObject targetManifest = targetDoc.optJSONObject(MANIFEST_KEY);
    if( null == targetManifest ) {
      // Can not verify digest on target document. Let's assume it has
      // been modified
      return true;
    }

    String targetDigest = targetManifest.optString("digest");
    if( null == targetDigest ) {
      // Can not verify digest on target document, let's assume it
      // has changed
      return true;
    }
   
    // Check type
    DigestComputer digestComputer = null;
    {
      String type = targetManifest.optString("type");
      if( null == type ){
        // Has been modified sine type is missing
        return true;
       
      } else if( DigestComputerSha1.DIGEST_COMPUTER_TYPE.equals(type) ) {
        digestComputer = sha1DigestComputer;
       
      } else {
        // Unrecognized type. Assume it was changed
        return true;
      }
    }
   
    try {
      String computedDigest = digestComputer.computeDigestFromJsonObject(targetDoc);
      if( false == computedDigest.equals(targetDigest) ) {
        // Digests are different. It was changed
        return true;
      }
    } catch(Exception e) {
      // Error computing digest, let's assume it was changed
      return true;
    }
   
    // Verify attachments by checking each attachment in manifest and verifying
    // each attachment has not changed
    Set<String> attachmentNamesInManifest = new HashSet<String>();
    JSONObject targetAttachmentManifests = targetManifest.optJSONObject("attachments");
    if( null != targetAttachmentManifests ){
      Iterator<?> it = targetAttachmentManifests.keys();
      while( it.hasNext() ){
        Object keyObj = it.next();
        if( keyObj instanceof String ){
          String attachmentName = (String)keyObj;
          attachmentNamesInManifest.add(attachmentName);
         
          JSONObject attachmentManifest = targetAttachmentManifests.optJSONObject(attachmentName);
          if( null == attachmentManifest ) {
            // Error. Must have been changed
            return true;
          } else if( false == JSONSupport.containsKey(attachmentManifest,"revpos") ) {
            // revpos is gone, document must have been modified
            return true;
          } else {
            int revpos = attachmentManifest.optInt("revpos",0);
            Integer actualRevPos = getAttachmentPosition(targetDoc,attachmentName);
            if( null == actualRevPos ) {
              // Attachment is gone
              return true;
            } else if( revpos != actualRevPos.intValue() ){
              // Attachment has changed
              return true;
            }
          }
        }
      }
    }
   
    // Verify that each attachment has a manifest declared for it
    JSONObject targetAttachments = targetDoc.optJSONObject("_attachments");
    if( null != targetAttachments ) {
      Iterator<?> it = targetAttachments.keys();
      while( it.hasNext() ){
        Object keyObj = it.next();
        if( keyObj instanceof String ){
          String attachmentName = (String)keyObj;

          if( false == attachmentNamesInManifest.contains(attachmentName) ){
            // This attachment was added since manifest was computed
            return true;
          }
        }
      }
    }

    // If we make it here, the document has not changed according to the manifest
    return false;
  }
 
  /**
   * Given a JSON document and an attachment name, returns the
   * revision position associated with the attachment.
   * @param targetDoc
   * @param attachmentName
   * @return
   */
  static public Integer getAttachmentPosition(
      JSONObject targetDoc
      ,String attachmentName
      ) {
   
    JSONObject targetAttachments = targetDoc.optJSONObject("_attachments");
    if( null == targetAttachments ) {
      // No attachment on target doc
      return null;
    }
   
    JSONObject targetAttachment = targetAttachments.optJSONObject(attachmentName);
    if( null == targetAttachment ) {
      // Target document does not have an attachment with this name
      return null;
    }
   
    if( JSONSupport.containsKey(targetAttachment,"revpos") ){
      int revPos = targetAttachment.optInt("revpos",-1);
      if( revPos < 0 ){
        return null;
      }
      return revPos;
    }
   
    return null;
  }
 
  /**
   * Computes a manifest to be associated with a document stored in
   * a database. This manifest represents the state of the document
   * and can be verified. The aim of the manifest is to:
   * 1. Verify if another document is equivalent to another one, when
   *    a document digest has been computed on the latter.
   * 2. Verify that an document has not been modified in the database
   *    since a manifest was computed for it.
   * @param sourceDoc Document that is to be uploaded to a database
   * @param documentDigest Digest computed for the sourceDoc
   * @param previousDocument Document currently stored in the database. Specify this
   * parameter only when performing a document update, not during creation.
   * @param updateSpecifier Report that specifies the update actions that
   * will be taken during the document upload. Specify this parameter only when
   * performing a document update, not during creation.
   * @return The manifest that should be installed in the document
   * @throws Exception Invalid parameters. Invalid object state.
   */
  static public JSONObject computeManifest(
      Document sourceDoc
      ,DocumentDigest documentDigest
      ,JSONObject previousDocument
      ,UpdateSpecifier updateSpecifier
      ) throws Exception {
   
    // Verify parameters
    if( null == sourceDoc ) {
      throw new Exception("Parameter 'sourceDoc' must be provided to compute a manifest");
    }
    if( null == documentDigest ) {
      throw new Exception("Parameter 'documentDigest' must be provided to compute a manifest");
    }
    if( null == updateSpecifier ) {
      throw new Exception("Parameter 'updateSpecifier' must be provided to compute a manifest");
    }

    // Compute the revision that will be associated with the
    // next object update
    int nextRevision = 1; // creation
    if( null != previousDocument ) {
      String revString = previousDocument.getString("_rev");
      if( null == revString ) {
        throw new Exception("Previous document offered to compute a manifest does not contain a '_rev' attribute.");
      }
      int dashIndex = revString.indexOf('-');
      if( dashIndex < 1 ) {
        throw new Exception("Does not understand rev attribute while computing a manifest: "+revString);
      }
      revString = revString.substring(0, dashIndex);
      nextRevision = Integer.parseInt(revString);
      if( nextRevision < 1 ){
        throw new Exception("Invalid revision found while computing a manifest: "+revString);
      }
      ++nextRevision;
    }
   
    // Quick access
    Set<String> attachmentsToUpload = null;
    Set<String> attachmentsNotModified = null;
    if( null != updateSpecifier ) {
      attachmentsToUpload = updateSpecifier.getAttachmentsToUpload();
      attachmentsNotModified = updateSpecifier.getAttachmentsNotModified();
    }
   
    JSONObject manifest = new JSONObject();
   
    manifest.put("type", documentDigest.getType());
    manifest.put("digest", documentDigest.getDocDigest());
   
    JSONObject attachments = new JSONObject();
    for(String attachmentName : documentDigest.getAttachmentNames()){
      String attachmentDigest = documentDigest.getAttachmentDigest(attachmentName);
     
      // Figure out revision position for this attachment
      int attachmentPosition = 1; // creation
      if( null != previousDocument ) {
        // Update case. Either, the attachment will be uploaded, or not.
        // If uploaded, take the next revision.
        // If not modified, keep the revision that was previously reported.
       
        if( attachmentsToUpload.contains(attachmentName) ){
          // This attachment will be uploaded. Therefore, guess the revision
          // position that will be associated with this attachment
          attachmentPosition = nextRevision;
         
        } else if( attachmentsNotModified.contains(attachmentName) ){
          // This attachment already exists on the target document and will
          // not be updated. Keep the revision that was previously reported
          attachmentPosition = DocumentManifest.getAttachmentPosition(previousDocument, attachmentName);
         
        } else {
          throw new Exception("Error while computing manifest revision for attachment: "+attachmentName);
        }
      }
     
      JSONObject att = new JSONObject();
      att.put("digest", attachmentDigest);
      att.put("revpos", attachmentPosition);
     
      attachments.put(attachmentName, att);
    }
    manifest.put("attachments", attachments);
   
    return manifest;
  }
}
TOP

Related Classes of ca.carleton.gcrc.couch.app.impl.DocumentManifest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.