Package ca.carleton.gcrc.couch.submission.impl

Source Code of ca.carleton.gcrc.couch.submission.impl.SubmissionRobotThread

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

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.carleton.gcrc.couch.client.CouchDb;
import ca.carleton.gcrc.couch.client.CouchDesignDocument;
import ca.carleton.gcrc.couch.client.CouchDocumentOptions;
import ca.carleton.gcrc.couch.client.CouchQuery;
import ca.carleton.gcrc.couch.client.CouchQueryResults;
import ca.carleton.gcrc.couch.client.CouchUserDb;
import ca.carleton.gcrc.couch.client.CouchUserDocContext;
import ca.carleton.gcrc.couch.submission.SubmissionRobotSettings;
import ca.carleton.gcrc.couch.submission.mail.SubmissionMailNotifier;
import ca.carleton.gcrc.json.JSONSupport;
import ca.carleton.gcrc.json.patcher.JSONPatcher;
import ca.carleton.gcrc.mail.MailRecipient;

public class SubmissionRobotThread extends Thread {

  final protected Logger logger = LoggerFactory.getLogger(this.getClass());
 
  private boolean isShuttingDown = false;
  private CouchDesignDocument submissionDbDesignDocument;
  private CouchDesignDocument documentDbDesignDocument;
  private CouchUserDb userDb;
  private SubmissionMailNotifier mailNotifier = null;
  private Set<String> docIdsToSkip = new HashSet<String>();
  private String adminRole = "administrator";
  private String vetterRole = "vetter";
 
  public SubmissionRobotThread(SubmissionRobotSettings settings) {
    this.submissionDbDesignDocument = settings.getSubmissionDesignDocument();
    this.documentDbDesignDocument = settings.getDocumentDesignDocument();
    this.userDb = settings.getUserDb();
    this.mailNotifier = settings.getMailNotifier();
   
    if( null != settings.getAtlasName() ){
      adminRole = settings.getAtlasName() + "_administrator";
      vetterRole = settings.getAtlasName() + "_vetter";
    }
  }
 
  public void shutdown() {
   
    logger.info("Shutting down submission worker thread");

    synchronized(this) {
      isShuttingDown = true;
      this.notifyAll();
    }
  }
 
  @Override
  public void run() {
   
    logger.info("Start submission worker thread");
   
    boolean done = false;
    do {
      synchronized(this) {
        done = isShuttingDown;
      }
      if( false == done ) {
        activity();
      }
    } while( false == done );

    logger.info("Submission worker thread exiting");
  }
 
  private void activity() {
    CouchQuery query = new CouchQuery();
    query.setViewName("submission-work");

    CouchQueryResults results;
    try {
      results = submissionDbDesignDocument.performQuery(query);
    } catch (Exception e) {
      logger.error("Error accessing submission database",e);
      waitMillis(60 * 1000); // wait a minute
      return;
    }

    // Check for work
    String docId = null;
    for(JSONObject row : results.getRows()) {
      String id = row.optString("id");
      if( false == docIdsToSkip.contains(id) ) {
        // Found some work
        docId = id;
        break;
      }
    }

    if( null == docId ) {
      // Nothing to do, wait 4 secs
      waitMillis(4 * 1000);
      return;
    } else {
      try {
        // Handle this work
        performWork(docId);
       
      } catch(Exception e) {
        logger.error("Error processing document "+docId,e);
        docIdsToSkip.add(docId);
      }
    }
  }
 
  /*
   * submitted (robot)
   * -> complete (if target document is deleted)
   * -> approved (if submitted by someone who is automatically approved)
   * -> waiting_for_approval (otherwise)
   *
   * approved (robot)
   * -> complete (if target document is deleted)
   * -> complete (if target document can be updated automatically)
   * -> collision (if merging to target document performs a collision)
   *
   * waiting_for_approval (user)
   * -> approved (when an administrator agrees with changes)
   * -> denied (when an administrator disagrees with changes)
   *
   * collision (user)
   * -> resolved (when an administrator fixes the changes to avoid collision)
   * -> denied (when administrator decides changes are no longer wanted)
   *
   * resolved (robot)
   * -> complete (if target document is deleted)
   * -> complete (if changes are merged on target document)
   * -> collision (if changes can not be merged on target document)
   *
   * denied (user)
   * -> approved (when administrator decides that changes are needed)
   *
   * complete
   */
  public void performWork(String submissionDocId) throws Exception {
    // Get submission document
    CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
    JSONObject submissionDoc = submissionDb.getDocument(submissionDocId);
   
    // Get document id
    String docId = submissionDoc
      .getJSONObject("nunaliit_submission")
      .getJSONObject("original_reserved")
      .getString("id");
    String revision = submissionDoc
      .getJSONObject("nunaliit_submission")
      .getJSONObject("original_reserved")
      .optString("rev",null);
   
    // Check if denial email must be sent
    boolean sendDenialEmail = false;
    JSONObject denialEmail = submissionDoc
      .getJSONObject("nunaliit_submission")
      .optJSONObject("denial_email");
    if( null != denialEmail ){
      boolean requested = denialEmail.optBoolean("requested",false);
      boolean sent = denialEmail.optBoolean("sent",false);
     
      if( requested && !sent ){
        sendDenialEmail = true;
      }
    }
   
    // Get document in document database
    CouchDb documentDb = documentDbDesignDocument.getDatabase();
    JSONObject doc = null;
    try {
      doc = documentDb.getDocument(docId);
    } catch(Exception e) {
      // ignore
    }
    if( null == doc
     && null != revision ) {
      // Referenced document no longer exists
      submissionDoc.getJSONObject("nunaliit_submission")
        .put("state", "complete");
      submissionDb.updateDocument(submissionDoc);
    } else {
      String stateStr = null;
      JSONObject jsonSubmission = submissionDoc.getJSONObject("nunaliit_submission");
      stateStr = jsonSubmission.optString("state",null);
     
      if( null == stateStr ) {
        performSubmittedWork(submissionDoc, doc);
       
      } else if( "submitted".equals(stateStr) ) {
        performSubmittedWork(submissionDoc, doc);

      } else if( "approved".equals(stateStr) ) {
        performApprovedWork(submissionDoc, doc);

      } else if( sendDenialEmail ) {
        performDenialEmail(submissionDoc, doc);

      } else {
        throw new Exception("Unexpected state for submission document: "+stateStr);
      }
    }
  }

  public void performSubmittedWork(JSONObject submissionDoc, JSONObject targetDoc) throws Exception {
    // Find roles associated with the user who submitted the change
    String userId = submissionDoc
      .getJSONObject("nunaliit_last_updated")
      .getString("name");
    CouchUserDocContext userDoc = null;
    try {
      userDoc = userDb.getUserFromName(userId);
    } catch(Exception e) {
      // Ignore if we can not find user
    }

    // Check if submission should be automatically approved
    boolean approved = false;
    if( null != userDoc ) {
      List<String> roles = userDoc.getRoles();
      for(String role : roles){
        if( "_admin".equals(role) ){
          approved = true;
          break;
        } else if( "administrator".equals(role) ){
          approved = true;
          break;
        } else if( "vetter".equals(role) ){
          approved = true;
          break;
        } else if( adminRole.equals(role) ){
          approved = true;
          break;
        } else if( vetterRole.equals(role) ){
          approved = true;
          break;
        }
      }
    }

    if( approved ) {
      CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
      submissionDoc.getJSONObject("nunaliit_submission")
        .put("state", "approved");
      submissionDb.updateDocument(submissionDoc);
    } else {
      CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
      submissionDoc.getJSONObject("nunaliit_submission")
        .put("state", "waiting_for_approval");
      submissionDb.updateDocument(submissionDoc);
     
logger.error("Sending waiting for approval notification for submission");     
      this.mailNotifier.sendSubmissionWaitingForApprovalNotification(submissionDoc);
    }
  }

  public void performApprovedWork(JSONObject submissionDoc, JSONObject currentDoc) throws Exception {
    String docId = submissionDoc
      .getJSONObject("nunaliit_submission")
      .getJSONObject("original_reserved")
      .getString("id");
    boolean isDeletion = submissionDoc
      .getJSONObject("nunaliit_submission")
      .optBoolean("deletion",false);
   
    if( null == currentDoc ) {
      // New document. Create.
      JSONObject originalDoc = SubmissionUtils.getApprovedDocumentFromSubmission(submissionDoc);
     
      CouchDb targetDb = documentDbDesignDocument.getDatabase();
      targetDb.createDocument(originalDoc);
     
      CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
      submissionDoc.getJSONObject("nunaliit_submission")
        .put("state", "complete");
      submissionDb.updateDocument(submissionDoc);
     
    } else if( isDeletion ) {
      CouchDb targetDb = documentDbDesignDocument.getDatabase();
      JSONObject toDeleteDoc = targetDb.getDocument(docId);
      targetDb.deleteDocument(toDeleteDoc);
     
      CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
      submissionDoc.getJSONObject("nunaliit_submission")
        .put("state", "complete");
      submissionDb.updateDocument(submissionDoc);
     
    } else {
      String currentVersion = currentDoc.getString("_rev");
     
      JSONObject approvedDoc = SubmissionUtils.getApprovedDocumentFromSubmission(submissionDoc);
      String approvedVersion = approvedDoc.optString("_rev",null);
     
      if( currentVersion.equals(approvedVersion) ) {
        // No changes since approval. Simply update the document
        // database.
        CouchDb targetDb = documentDbDesignDocument.getDatabase();
        targetDb.updateDocument(approvedDoc);
       
        CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
        submissionDoc.getJSONObject("nunaliit_submission")
          .put("state", "complete");
        submissionDb.updateDocument(submissionDoc);
      } else {
        // Get document that the changes were made against
        CouchDb couchDb = documentDbDesignDocument.getDatabase();
        CouchDocumentOptions options = new CouchDocumentOptions();
        options.setRevision(approvedVersion);
        JSONObject rootDoc = couchDb.getDocument(docId, options);
       
        // Compute patch from submission
        JSONObject submissionPatch = JSONPatcher.computePatch(rootDoc, approvedDoc);
        JSONObject databasePatch = JSONPatcher.computePatch(rootDoc, currentDoc);
       
        // Detect collision. Apply patches in different order, if result
        // is same, then no collision
        JSONObject doc1 = JSONSupport.copyObject(rootDoc);
        JSONPatcher.applyPatch(doc1, submissionPatch);
        JSONPatcher.applyPatch(doc1, databasePatch);
        JSONObject doc2 = JSONSupport.copyObject(rootDoc);
        JSONPatcher.applyPatch(doc2, databasePatch);
        JSONPatcher.applyPatch(doc2, submissionPatch);
        if( 0 == JSONSupport.compare(doc1, doc2) ) {
          // No collision
          logger.error("rootDoc: "+rootDoc);
          logger.error("submissionPatch: "+submissionPatch);
          logger.error("databasePatch: "+databasePatch);
          logger.error("no collision: "+doc1);
          CouchDb targetDb = documentDbDesignDocument.getDatabase();
          targetDb.updateDocument(doc1);
         
          CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
          submissionDoc.getJSONObject("nunaliit_submission")
            .put("state", "complete");
          submissionDb.updateDocument(submissionDoc);
        } else {
          // Collision case
          CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
          submissionDoc.getJSONObject("nunaliit_submission")
            .put("state", "collision");
          submissionDb.updateDocument(submissionDoc);
        }
      }
    }
  }
 
  public void performDenialEmail(JSONObject submissionDoc, JSONObject currentDoc) throws Exception {
    JSONObject submissionInfo = submissionDoc.getJSONObject("nunaliit_submission");
    JSONObject denial_email = submissionInfo.getJSONObject("denial_email");
   
    // Find user that submitted the update
    String userId = null;
    JSONObject created = submissionDoc.optJSONObject("nunaliit_created");
    if( null != created ){
      userId = created.optString("name", null);
    }

    // Get user document
    CouchUserDocContext userDocContext = null;
    if( null != userId ){
      try {
        userDocContext = userDb.getUserFromName(userId);
      } catch(Exception e) {
        // Ignore if we can not find user
      }
    }
   
    // Get list of e-mails
    List<String> emails = new Vector<String>();
    String userName = null;
    if( null != userDocContext ){
      JSONObject userDoc = userDocContext.getUserDoc();
     
      Set<String> validatedEmails = new HashSet<String>();
      JSONArray jsonValidated = userDoc.optJSONArray("nunaliit_validated_emails");
      if( null != jsonValidated ){
        for(int i=0; i<jsonValidated.length(); ++i){
          String email = jsonValidated.getString(i);
          validatedEmails.add(email);
        }
      }
     
      JSONArray jsonEmails = userDoc.optJSONArray("nunaliit_emails");
      if( null != jsonEmails ){
        for(int i=0; i<jsonEmails.length(); ++i){
          String email = jsonEmails.getString(i);
          if( validatedEmails.contains(email) ){
            emails.add(email);
          }
        }
      }
     
      userName = userDoc.optString("display",null);
      if( null == userName ){
        userName = userDoc.optString("name",null);
      }
    }
   
    // If no e-mails, just quit
    if( emails.size() < 1 ){
      CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
      denial_email.put("sent", true);
      submissionDb.updateDocument(submissionDoc);
      return;
    }
   
    // Convert e-mail addresses into recipient
    List<MailRecipient> recipients = new ArrayList<MailRecipient>(emails.size());
    for(String email : emails){
      MailRecipient recipient = null;
      if( null != userName ){
        recipient = new MailRecipient(email, userName);
      } else {
        recipient = new MailRecipient(email);
      }
      recipients.add(recipient);
    }
   
    // Send notification
    mailNotifier.sendSubmissionRejectionNotification(submissionDoc, recipients);

    // Remember it was sent
    CouchDb submissionDb = submissionDbDesignDocument.getDatabase();
    denial_email.put("sent", true);
    submissionDb.updateDocument(submissionDoc);
  }

  private boolean waitMillis(int millis) {
    synchronized(this) {
      if( true == isShuttingDown ) {
        return false;
      }
     
      try {
        this.wait(millis);
      } catch (InterruptedException e) {
        // Interrupted
        return false;
      }
    }
   
    return true;
  }
}
TOP

Related Classes of ca.carleton.gcrc.couch.submission.impl.SubmissionRobotThread

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.