001    package hirondelle.fish.test.doubles;
002    
003    import java.sql.SQLException;
004    import hirondelle.web4j.database.DAOException;
005    import hirondelle.web4j.database.DuplicateException;
006    import hirondelle.web4j.util.Util;
007    
008    /** 
009     Change the exception behavior of fake DAOs.
010     One of the advantages of using fake DAOs is that they can be made to 
011     throw specific exceptions when desired. 
012     By default, fake DAO operations will always succeed, in the sense of not throwing 
013     an explicit exception. 
014     
015     <P>You control the behavior of a fake DAO using these methods :
016    <ul>
017     <li>call {@link #setBehavior(DbOperation, DbOperationResult)} to specify the desired behavior 
018     for a given operation. If you don't call this method, then by default the fake DAO operation 
019     will succeed.
020     <li>call {@link #possiblyThrowExceptionFor(DbOperation)} on the first line of your fake 
021     DAO method. If the operation has been set to fail, then an exception will be thrown. 
022     If not, then the operation will complete successfully.
023    </ul>
024    
025     <P>For an illustration, see {@link hirondelle.fish.main.member.MemberDAOFake} and 
026     {@link hirondelle.fish.main.member.TESTMemberDAO}.
027      
028    <P><b>Implementation Note</b><br>
029     This class uses simple <tt>System</tt> properties to store the desired behavior. Such an implementation 
030     is suitable only for a single threaded environment.
031    */
032    public final class FakeDAOBehavior {
033    
034      /**
035       Name of a <tt>System</tt> property used to swap implementations 
036       between real and fake DAOs. When the property has the value <tt>true</tt>,
037       then fake DAOs are used. See 
038       {@link hirondelle.fish.main.member.MemberDAOFake} for an illustration.
039       
040       Value - {@value}.
041      */
042      public static final String USE_FAKE_DAOS = "useFakeDAOs";
043      
044      /**
045       Enumeration of the basic DAO operations.
046      */
047      public static enum DbOperation {
048        Add("FakeDAOAdd"), 
049        Change("FakeDAOChange"), 
050        Delete("FakeDAODelete"), 
051        FetchForChange("FakeDAOFetchForChange"), 
052        List("FakeDAOList");
053        public String toString(){
054          return fName;
055        }
056        private DbOperation(String aName){
057          fName = aName;
058        }
059        private String fName;
060      }
061      
062      /**  Enumeration of how a basic DAO operation may succeed or fail.  */
063      public static enum DbOperationResult {Succeed, ThrowDAOException, ThrowDuplicateException}
064      
065      /**
066       Possibly throw an exception for the given operation.
067       
068       <P>An exception is thrown only if it has been indicated by calling 
069       {@link #setBehavior(DbOperation, DbOperationResult)}.
070      */
071      public static void possiblyThrowExceptionFor(DbOperation aOperation) throws DAOException, DuplicateException {
072        DbOperationResult opResult = getOperationResult(aOperation);
073        if (FakeDAOBehavior.DbOperationResult.Succeed == opResult) return;
074        if (FakeDAOBehavior.DbOperationResult.ThrowDAOException == opResult) throw new DAOException(FORCED_FAILURE, FORCED_EXCEPTION);
075        if (FakeDAOBehavior.DbOperationResult.ThrowDuplicateException == opResult) throw new DuplicateException(FORCED_FAILURE, FORCED_EXCEPTION);
076      }
077      
078      /** Set the desired behavior of a fake DAO operation.  */
079      public static void setBehavior(DbOperation aDbOperation, DbOperationResult aDbOperationResult){
080        System.setProperty(aDbOperation.toString(), aDbOperationResult.toString() ); 
081      }
082      
083      // PRIVATE //
084      private static final String FORCED_FAILURE = "Forced failure in fake DAO.";
085      private static final Throwable FORCED_EXCEPTION = new SQLException(FORCED_FAILURE);
086    
087      private static DbOperationResult getOperationResult(DbOperation aOperation){
088        DbOperationResult result = DbOperationResult.Succeed; //default value, if absent
089        String property = System.getProperty(aOperation.toString());
090        if ( Util.textHasContent(property)) {
091          result = DbOperationResult.valueOf(property);
092        }
093        return result;
094      }
095    }