Subversion Repositories DevTools

Rev

Rev 7186 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.erggroup.buildtool.ripple;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.erggroup.buildtool.smtp.CreateUrls;
import com.erggroup.buildtool.smtp.Smtpsend;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;

/**entity class holding build exclusion data
 * 
 * There are three types of exclusion
 * Package Errors. Errors detected during planning. They may go away on the next plan
 *      These are 'hard' errors
 *          Have a rootCase string
 *          Have a null rootId
 *          
 * Build Errors
 *      These are persistent errors
 *          Have a null rootCause
 *          Have a null rootId
 *          Have a non-null root_file
 *          
 *  Indirect errors
 *      These are packages that depend on excluded packages
 *          Will have a non-null rootId
 *          Will be recalculated on each planning cycle          
 * 
 */
public class BuildExclusion
{
  /**Logger
   * @attribute
   */
  private static final Logger mLogger = LoggerFactory.getLogger(BuildExclusion.class);

  /**The pvid of the package being excluded
   */
  public int mId;

  /**root identifier is the PVID of the package that is causing the exclusion
   * A NULL (-1) value indicates that this entry(package) is the root cause of the of an exclusion
   * A Value of -2 indicates that cause is a RippleStop
   */
  private int mRootId;

  /**root cause
   * If not NULL, then this is the reason this package is being excluded
   */
  private String mRootCause;

  /**test build instruction
   * Associated package is a test build
   */
  private int mTestBuildInstruction;

  /** Indicates that the error associated with this package has been seen again
   *  Set if we discover 'Package Error'
   *  
   *  Used to determine if a 'Package Error' should be removed
   *    
   */
  private boolean mProcessed = false;
  
  /** Indicates that the entry was imported from the database
   *  Used to determine entries that are no-longer needed and can be removed from the database
   *  Really only applied to indirect exclusions where the reason for the exclusion no longer exists
   *  
   */
  private boolean mImported = false;

  /**constructor
   *    @param  identifier - pvid of the package being excluded
   *    @param  rootIdentifier  - pvid of the root package causing the exclusion. May be -1
   *    @param  rootCause - Short (< 50 Char) string explaining the cause
   *    @param  testBuildInstruction - Indicates a text build instruction
   */
  public BuildExclusion(int identifier, int rootIdentifier, String rootCause, int testBuildInstruction )
  {
    mLogger.debug("BuildExclusion");
    mId = identifier;
    mRootId = dealWithNullRootPvId(identifier, rootIdentifier);
    mRootCause = rootCause;
    mTestBuildInstruction = testBuildInstruction;
  }

  /**sets mProcessed true
   */
  void setProcessed()
  {
    mLogger.debug("process {}", mId);
    mProcessed = true;
  }

  /** @returns true if the item has been marked as processed. 
   * Appears to be an indication that the entry has been superseded.
   */
  boolean isProcessed()
  {
    mLogger.debug("isProcessed returned {}", mProcessed);
    return mProcessed;
  }
  
  /** Flag as an imported entry
   *  Used to detect indirect exclusions that are no longer valid
   */
  void setImported()
  {
      mImported = true;
  }

  /** Test the state of the imported flag
   * 
   */
  boolean isImported()
  {
      return mImported;
  }
  
  /**
   * Determine the entry type
   * Have three types - see class comments above
   */
  boolean isAPackageError()
  {
      return (mRootCause != null && mRootId < 0);
  }
  
  boolean isABuildError()
  {
      return (mRootCause == null && mRootId < 0);
  }
  
  boolean isAIndirectError()
  {
      return (mRootId >= 0);
  }
  
  /**
   * @return true if this item is the root case, and is not simply a by product of some other cause.
   * This is the same as !isAIndirectError() 
   */
  boolean isARootCause()
  {
      return (mRootId < 0);
  }
  
  /**
   * Compare this item with parameters
   * @param identifier          - Related Package identifier
   * @param rootIdentifier      - Package identifier of root cause
   * @param  rootCause          - Root cause string. May be null
   * @return true if all attributes match
   */
  boolean compare( int identifier, int rootIdentifier, String rootCause)
  {
    mLogger.debug("compare {}, {}, {}, {}", mId, identifier, rootIdentifier,  rootCause);
    boolean retVal = false;
    rootIdentifier = dealWithNullRootPvId(identifier, rootIdentifier);
    
    if ( mRootCause == null )
    {
      if ( mId == identifier && mRootId == rootIdentifier && rootCause == null )
      {
        retVal = true;
      }
    }
    else
    {
      if ( mId == identifier && mRootId == rootIdentifier && mRootCause.compareTo(rootCause) == 0 )
      {
        retVal = true;
      }
    }
    
    mLogger.debug("compare returned {}", retVal);
    return retVal;
  }
  
  /**   Does this entry relate to the package with a specified mId
   * 
   * @param identifier  - Package Identifier (mId) to match
   * @return true if mId attribute matches
   */
  boolean compare( int identifier )
  {
    mLogger.debug("compare {},{}", mId,  identifier);
    boolean retVal = false;
    
    if ( mId == identifier )
    {
      retVal = true;
    }
    
    mLogger.debug("compare returned {}", retVal);
    return retVal;
  }
  
  /**runs exclude from build
   * Assumes that a connection to RM has been established
   * 
   * @param rm          Release Manager instance
   * @param rtagId      Rtag Id we are working against
   */
  void excludeFromBuild( ReleaseManager rm, int rtagId ) throws SQLException, Exception
  {
    mLogger.debug("excludeFromBuild {}", mId);
    
    // a null version and log file is passed to oracle
    // the planned version is only needed to remove a planned version from the planned version table
    // the ripple engine does not get this far ie it excludes pvs before claiming a version
    // this is the one instance where an existing build failure must be superseded in the database
    rm.excludeFromBuild(true, 
                        mId, 
                        null, 
                        rtagId, 
                            mRootId == -1 ? null : String.valueOf(mRootId), 
                            mRootCause, 
                            null, 
                            true, (mTestBuildInstruction > 0) );
  }
  
  /**runs include to build
   * Include a previously excluded package-version back into the build set
   * 
   * @param rm          Release Manager instance
   * @param rtagId      Rtag Id we are working against
   */
  void includeToBuild( ReleaseManager rm, int rtagId ) throws SQLException, Exception
  {
    mLogger.debug("includeToBuild {}", mId);
    rm.includeToBuild(mId, rtagId);
  }
    
  /**
   * Match this items mRootId against the id's provided in a collection
   * ie: Determine if any items in the collection are the root cause of this items
   * ie: Used to determine if an entry is for an indirectly excluded package where
   *     the root cause of the exclusion has been removed.
   *     
   * @param buildExclusionCollection - Collection to be processed
   * @return false: Indirectly excluded package whose root cause no longer exists
   */
  boolean isRelevant(ArrayList<BuildExclusion> buildExclusionCollection)
  {
    mLogger.debug("isRelevant {}", mId);
    boolean retVal = false;
    
    if ( mRootId == -1 )     {
        // This is a Build or Package Error
        retVal = true;
      
    } else if(mRootId == -2 ) {
        // Excluded due to Ripple Stop
        // Will be recalculated so its not relevant
        retVal = false;
        
    } else {
        
        //
        //  Must be an indirect exclusion
        //  Scan to see if the rootCause is still present. It may have been removed by the user
        
      retVal = false;
      for (Iterator<BuildExclusion> it = buildExclusionCollection.iterator(); it.hasNext(); )
      {
        BuildExclusion be = it.next();
        
        if ( be.isARootCause() && be.mRootId == mRootId )
        {
          retVal = true;
          break;
        }
      }
    }

    mLogger.info("isRelevant {} returned {}", mId, retVal);
    return retVal;
  }

  

  /**
   * Send an email notifying users about a build excluded package
   * It is user friendly, in that it does not trigger a storm of emails because a low level package 
   * has a build issue. It limits the emails to the low level package
   * 
   * i.e. only send email if the build exclusion has a null root pv id
   * and a non null root cause
   * 
   * @param    rippleEngine        - Ripple Engine Instance
   * @param    packageCollection   - Collection to process
   */
    public void email(RippleEngine rippleEngine, PackageCollection packageCollection) throws SQLException, Exception
    {
      mLogger.debug("email {}", mId);
      
      //
      //    Only process entries that are direct failures of ripple engine detected failure
      //    Do not process entries that are indirectly excluded as this will cause an email storm
      //    Direct build failure: 
      //        Have no RootId and have a rootCause
      //        Special handling for rippleStop - do not send emails
      //
      if ( isAPackageError() && mRootId != -2 )
      {
        //  Locate the associated package entry
        Package pkg= packageCollection.contains(mId);
        if ( pkg != null )
        {
            // Generate a nice subject line
            String subject;
            if (pkg.mTestBuildInstruction > 0) {
                subject = "TEST BUILD FAILED on package " + pkg.mAlias;
            } else {
                subject = "BUILD FAILURE on package " + pkg.mAlias;
            }
            
          // Is there anyone to send an email to
          String owners = pkg.emailInfoNonAntTask(rippleEngine);
          
          if ( owners != null )
          {
              
            String body =
            "Release: " + rippleEngine.mBaselineName + "<p>" +
            "Package: " + pkg.mName + "<p>" + 
            "Cause: "   + mRootCause + "<p>"+
            "RmRef: "   + CreateUrls.generateRmUrl(rippleEngine.getRtagId(), pkg.mId) +"<p>";
            
            try
            {
              Smtpsend.send(
              rippleEngine.getMailServer(),             // mailServer
              rippleEngine.getMailSender(),             // source
              owners,                                   // target
              null,                                     // cc
              null,                                     // bcc
              subject,                                  // subject
              body,                                     // body
              null                                      // attachment
              );
            }
            catch( Exception e )
            {
                mLogger.info("email send exception. {}", e);
            }
          }
          
          //    Handle test builds here
          if (pkg.mTestBuildInstruction > 0 )
          {
          
          // Having sent the build failure email, complete a test build if applicable.
          // This ensures the test build instruction is not processed indefinitely
          // as there is no notion of excluding test builds

          // Update the Release Manager Database
          rippleEngine.mReleaseManager.markDaemonInstCompleted(pkg.mTestBuildInstruction);
          
          }
        }
      }
    }
    
    /**
     * Hides how a rootPvId is treated
     * Only use rootPvId if not equal to the pvid
     * 
     * If the provided rootPvId matches the pvId, then the rootPvId will be set to null (-1)
     * This is to drive a direct build exclusion in the release manager
     * 
     * @param   pvid        - id
     * @param   rootPvId    - rootPvid
     * 
     * @returns rootPvId unless pvId matches= rootPvId, when it returns a null (-1)
    */
    private int dealWithNullRootPvId( int pvId, int rootPvId )
    {
      int retVal = rootPvId;
      
      if ( pvId == rootPvId )
      {
        retVal = -1;    // -1 == null entry
      }
      
      return retVal;
    }

    /**
     * Generate a single text line of info
     * Used within the UTF to display diagnostic info
     * @return String form of the state of the entry
     */
    public String toString()
    {
        String rv = "";
        rv += "pvid=" + mId + ",RootId=" + mRootId + ",Processed=" + mProcessed + ",TestBuild=" + mTestBuildInstruction + ",RootCause=" + mRootCause;
        if ( mImported ) {
            rv += ",Imported=" + mImported;
        }
        return rv;
    }
}