package org.mockejb.test.entity;

import java.util.Collection;
import javax.naming.*;

import junit.framework.TestCase;
import org.easymock.MockControl;

import org.mockejb.*;
import org.mockejb.interceptor.*;
import org.mockejb.jndi.*;


/**
 * Demonstrates BMP Entity bean support.
 * MockEJB calls BMP entity beans same way with session beans, 
 * however finders have to be handled using aspects/interceptors framework. 
 * BMP finders always return primary key(s), so clients have to write 
 * interceptors to translate primary key to the entity.
 */  
public class BMPEntityBeanTest extends TestCase {

    // JNDI context and MockContainer instance used by all tests in this class  
    private MockContainer mockContainer;
    private Context context;    
    private AspectSystem aspectSystem;

    // We use PersonHome in all tests
    private PersonHome personHome;
    
    public BMPEntityBeanTest(String testName) {
        super(testName);
    }
    

    /**
     * Sets up our mock container, JNDI context and deploy the beans that we need.   
     */
    public void setUp() throws Exception  {

        // we use aspects in most of the tests here
        aspectSystem =  AspectSystemFactory.getAspectSystem();

        // MockContextFactory becomes the primary JNDI provider            
        MockContextFactory.setAsInitial();
        context = new InitialContext();

        // Creating MockContainer deletes all aspects from AspectSystem and clears EntityDatabase 
        mockContainer = new MockContainer( context );
       
        // Deploy the person bean
        EntityBeanDescriptor personDescriptor = 
            new EntityBeanDescriptor( Person.JNDI_NAME, 
            PersonHome.class, Person.class, PersonBMPBean.class );
         
        mockContainer.deploy( personDescriptor );

        // Obtain personHome for use in the tests
        personHome = (PersonHome)context.lookup(Person.JNDI_NAME);
        
    }
    
    public void tearDown() {
        // cleanup JNDI settings
        MockContextFactory.revertSetAsInitial();
    }

    
    /**
     * Demonstrates how to setup BMP finder support with the help of 
     * interceptors. PersonFinderAspect (see the code at bottom of this file) 
     * ignores the PK values returned by the real finder and substitutes them
     * with the Person entities that it creates.
     * This can be used to provide mock data for testing as opposed to
     * relying on the database. 
     */
    public void testFinderUsingAspect() throws Exception {
        // add aspect that handles finders
        aspectSystem.add( new PersonFinderAspect() );
        
        Person person = personHome.findByName("John", "Doe" );
        assertNotNull(person);
        assertEquals("Doe", person.getLastName());
    }
    
    /**
     * Demonstrates how to tell MockEJB to resolve the PKs returned by the 
     * finder without the use of interceptors. 
     */
    public void testFinderUsingEntityDatabase() throws Exception {
        // Create mock entity 
        MockControl personBeanControl = MockControl.createControl( Person.class); 
        Person createdPerson = (Person) personBeanControl.getMock(); 

        createdPerson.getId();
        personBeanControl.setReturnValue(1L);
        createdPerson.getFirstName();
        personBeanControl.setReturnValue("John");
        createdPerson.getLastName();
        personBeanControl.setReturnValue("Doe");
        
        personBeanControl.replay();
        
        /* Add mock entity to the "database" so MockEJB could resolve the PKs returned by the finder. 
         * For BMP, MockEJB searches the EntityDatabase for every PK returned by the finder. 
         */
        mockContainer.getEntityDatabase().add( PersonHome.class, new Long(1), createdPerson );
        PersonHome personHome = (PersonHome)context.lookup(Person.JNDI_NAME);
        Person foundPerson = personHome.findByName("John", "Doe" );
        assertEquals( createdPerson, foundPerson );
       
        // Test the finder which returns the collection
        Collection people = personHome.findByFirstName("John");
        assertTrue( people.size()>0);
        foundPerson = (Person)people.iterator().next();
        assertEquals( createdPerson, foundPerson );
        
    }

    
    /**
     * This test demonstrates how we can test BMP entities that 
     * indeed read something from the database.
     * MockEJB does the following: 
     * it calls the finder, checks if the returned PK is in the EntityDatabase.
     * If not, it creates a new entity instance and calls ejbLoad on it   
     */
    public void testFinderAndEjbLoad() throws Exception {

        PersonHome personHome = (PersonHome)context.lookup(Person.JNDI_NAME);
        
        /* BMPPersonBean finder always returns 1, so we're relying on this.
         * We'll also rely on ejbLoad to populate the entity.
         * BMPPersonBean.ejbLoad does not go to the database, 
         * it only sets the PK so we'll check that.
         */  
        Person foundPerson = personHome.findByName("John", "Doe" );
        // did the ejbLoad work?
        assertEquals( 1L, foundPerson.getId() );
      
        // Test the finder which returns the collection
        Collection people = personHome.findByFirstName("John");
        assertTrue( people.size()>0);
        foundPerson = (Person)people.iterator().next();
        assertEquals( 1L, foundPerson.getId());
    }
    

    // *** Interceptors used by this test case
    
    /**
     * Handles findByName calls
     */ 
    class PersonFinderAspect implements Aspect {
        
        /**
         * Intercept findByName method. 
         */ 
        public Pointcut getPointcut(){
            // Note that we are intecepting target method on the bean 
            // as opposed to the interface method. Unlike in CMP case, we can do it 
            // because BMP entities have defined finder methods. 
            return new MethodPatternPointcut( "PersonBMPBean\\.ejbFindByName" );
        }
        
        public void intercept( InvocationContext invocationContext ) throws Exception {
            Object [] paramVals = invocationContext.getParamVals(); 

            // now create 
            invocationContext.setReturnObject( create( (String)paramVals[0], (String)paramVals[1] ) );
            // We don't need to proceed to the next interceptor since we're done with the finder
        }
         
        /**
         * Creates Person entity using "genericCreate" method which creates an
         * instance of an entity without calling the actual "ejbCreate" 
         */
        private Person create( String firstName, String lastName ) throws Exception {

            Context context = new InitialContext();
            GenericHome home = (GenericHome)context.lookup(Person.JNDI_NAME);
            
            Person person = (Person) home.genericCreate();
            person.setFirstName(firstName);
            person.setLastName(lastName);
            
            return person;
        }
        
    }
    
    
}