package org.mockejb.test.entity;

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

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

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


/**
 * Demonstrates CMP Entity bean support.
 * MockEJB uses CGLIB to dynamically generate implementation classes 
 * for abstract entity bean classes. 
 * MockEJB fully supports CMP field getters/setters.
 * The rest of the container's support (such as finders and CMR) has to be implemented using 
 * Aspect/Interceptor framework as shown below.
 */  
public class EntityBeanTest 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 EntityBeanTest(String testName) {
        super( testName );
    }
    

    /**
     * Sets up mock container, JNDI context and deploy EJBs that we need.   
     */
    public void setUp() throws Exception  {
        
        // we use aspects in most of the tests here
        aspectSystem =  AspectSystemFactory.getAspectSystem();

        // make MockContextFactory 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, PersonBean.class );
         
        mockContainer.deploy( personDescriptor );

        /* Create aspect that will handle automatic PK generation for the PersonBean.
         * PersonBean has auto-sequencing PK that would be handled by the container
         * (although it is not part of the spec, almost all EJB containers support it).
         */  
        aspectSystem.add( new PersonCreateAspect() );
        
        // Deploy the address bean. In this simple example person has many addresses.
        EntityBeanDescriptor addressDescriptor = 
            new EntityBeanDescriptor( Address.JNDI_NAME, 
            AddressHome.class, Address.class, AddressBean.class );
         
        mockContainer.deploy( addressDescriptor );
        
        // Obtain personHome for use in the tests
        personHome = (PersonHome)context.lookup(Person.JNDI_NAME);
        
    }
    
    public void tearDown() {
        // cleanup JNDI settings
        MockContextFactory.revertSetAsInitial();
    }

    
    /**
     * Demonstrates the create/ejbCreate support
     */
    public void testCreate( ) throws Exception {
        
        // Create a Person instance
        Person person = personHome.create("John", "Doe");
        // Make sure that CMP fields were set correctly
        assertEquals("John", person.getFirstName());
        assertEquals("Doe", person.getLastName());

        // Check that id (PK) was generated by the PersonCreateAspect
        assertTrue( person.getId()>0);
        // Check that PK was set correctly
        assertEquals( new Long(person.getId()), person.getPrimaryKey() );
    }
    
    /**
     * Demonstrates the usage of a finder (other than findByPrimaryKey)
     */ 
    public void testFinder() throws Exception {
        
        // Create aspect that returns mock data for the finder
        aspectSystem.add( new PersonFinderAspect() );
        // call the finder
        Person person = personHome.findByName("John", "Doe" );
        assertNotNull(person);
        assertEquals("Doe",person.getLastName());
    }

    /**
     * Demonstrates the use of findByprimaryKey
     */ 
    public void testFindByPrimaryKey() throws Exception {
        
        
        /* MockEJB provides a primitive in-memory database implementation (EntityDatabase class)
         * that automatically handles findByPrimaryKey calls.
         * It can be populated directly (see "testEasyMockWithFindByPrimaryKey")
         * or by simply calling "create". If ejbCreate is intercepted and returns
         * a PK (without interception it would return null because of the spec), 
         * MockEJB automatically adds the newly created bean to the EntityDatabase.
         * Since our CreateAspect is active, it will make sure that the PK
         * is returned from the ejbCreate method. 
         */ 
        Person createdPerson = personHome.create("John", "Doe");
        
        // find person -- EntityDatabase is used
        Person foundPerson = personHome.findByPrimaryKey( createdPerson.getId() );
        // make sure that we got the same person
        assertEquals(createdPerson, foundPerson );
    }
    
    /**
     * Demonstrates how mock entities can be setup using 
     * EasyMock and EntityDatabase.
     */ 
    public void testEasyMockWithFindByPrimaryKey() throws Exception {

        // Create mock entity. Note that since this object is created outside of
        // MockEJB, you won't be able to intercept calls to it or to use MockEJB-provided
        // exception translation and CMT transaction support.
        // Here we just need test data, we don't care about container's services 
        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 EntityDatabase so we can find it using findByPrimaryKey
        mockContainer.getEntityDatabase().add( PersonHome.class, new Long(1), createdPerson );

        // now we can call findByPrimaryKey
        Person foundPerson = personHome.findByPrimaryKey( 1 );
        
        assertNotNull( foundPerson);
        assertEquals( createdPerson, foundPerson);
   
    }

    
    /**
     * Demonstrates the use of CMR using standard EJB API. 
     * First, we populate CMR collection using "create" and "set"
     * methods, after that we can begin using it. 
     * For example, you can setup CMR this way in the "setUp" method of a test class and 
     * then call Session facade that manipulates CMR collections in the test.
     * Alternatively, you can setup CMR using interceptors ( see "testCMRUsingInterceptor").
     * 
     */
    public void testCMRUsingAdd() throws Exception {
        // Find the person we need 

        // add aspect that handles finders
        aspectSystem.add( new PersonFinderAspect() );

        Person person = personHome.findByName("John", "Doe" );
        // now we can create Address for the person
        AddressHome addressHome = (AddressHome)context.lookup(Address.JNDI_NAME);
        
        // Get the current list of addresses for this person
        // Note that MockEJB returns an empty collection (not null) if the 
        // collection is not initialized (as per the spec)
        // MockEJB also supports java.util.Set.
        Collection addresses = person.getAddresses();
        Address address = addressHome.create("1001 Main St", "Washington", "DC",
                "22222","USA", person);
        
        /* We need to set the ID of the address. Real container would do it for us
         * if we define "id" field as auto-sequence in the deployment descriptor, 
         * but with MockEJB it is the responsibility of the setup code.
         * In our case, setId is not even part of the business interface, so we 
         * need to get to the implementation bean object. 
         * In MockEJB any business interface can be cast to EjbBeanAccess which
         * provides access to the bean and its context.  
         */
        AddressBean addressBean = (AddressBean)  ((EjbBeanAccess)address).getBean();
        // now we can set the ID directly
        addressBean.setId(123);
        addresses.add( address);
        
        /* Since MockEJB does everything in memory, you don't need to call setter.
         * But it won't hurt either.
         */
        person.setAddresses(addresses);    

        // CMR is now setup, we can begin using it. In the real life, 
        // the code above will probably be in the setUp method (unless you're 
        // testing the CMR creation functionality)
        
        // make sure that the address was added
        assertEquals(1, person.getAddresses().size());
        // make sure that M:1 is set
        address = (Address)person.getAddresses().iterator().next();
        assertNotNull( address.getPerson() );
        // check the PK of the address
        assertEquals( 123, address.getId());
        
    }
    
    /**
     * Demonstrates how to set up CMR using interceptors/aspects.
     */ 
    public void testCMRUsingInterceptor() throws Exception {

        
        // Add aspect that handles CMR methods on the Person bean
        aspectSystem.add( new PersonCMRAspect() );
        
        // Aspect will do all the work for us, the setup code is now in the interceptor
        
        // add aspect that handles finders so we can find the person
        aspectSystem.add( new PersonFinderAspect() );
        
        Person person = personHome.findByName("John", "Doe" );
       
        // make sure that the address exists
        assertEquals(1, person.getAddresses().size());
        // make sure that M:1 is set
        Address address = (Address)person.getAddresses().iterator().next();
        assertNotNull( address.getPerson() );
        // check the PK of the address
        assertEquals( 123, address.getId());
        
    }

    /**
     * Demonstrates the support of ejbHome and ejbSelect methods
     */ 
    public void testEjbHomeEjbSelect() throws Exception {
        
        // Create interceptor for the ejbSelectAll method of PersonBean
        SelectAllAspect selectAllAspect = new SelectAllAspect();
        // Intercept calls to the "ejbSelectAll" method of the personBean
        aspectSystem.add( selectAllAspect );
       
        // Call the home method which in turn calls ejbSelectAll method
        personHome.updateNames();
        // make sure that the data has been updated
        List people = selectAllAspect.getPeople();
        assertEquals( "Smith", ((Person)people.get(0)).getLastName() );
      
    }

    
    
    
    // *** Interceptors used by this test case
    
    
    /**
     * Supports auto-sequenced PK for the Person bean
     */ 
    class PersonCreateAspect implements Aspect {
       
        private long pkSequence =0;
        
        /**
         * Intercept all calls to the ejbCreate method of the PersonBean.
         * Notice how the pointcut can match interface methods as well as the methods 
         * of the implementation class.
         */ 
        public Pointcut getPointcut(){
            
            /* Note that we have to use ".*" in the regexp since 
             * the actual concrete class (subclass) is provided by CGLIB, so its name is different from 
             * the is the PersonBean (the actual name is PersonBean$Enhanced...) 
             */
            return new MethodPatternPointcut( "PersonBean.*ejbCreate" );
        }
        
        public void intercept( InvocationContext invocationContext ) throws Exception {
            // Proceed to call ejbCreate method of the PersonBean
            invocationContext.proceed( );
            // generate the id for this bean. The real container can do it using Oracle sequence, for instance. 
            long id = ++pkSequence;
            PersonBean personBean = (PersonBean)invocationContext.getTargetObject();
            // Set the id
            personBean.setId(id);
            
            // return the PK value. Without this interceptor, this method would return null.
            invocationContext.setReturnObject( new Long( id ) );
        }
    }

    /**
     * Handles findByName calls
     */ 
    class PersonFinderAspect implements Aspect {
        
        /**
         * Intercept findByName method
         */ 
        public Pointcut getPointcut(){
            return new MethodPatternPointcut( "PersonHome\\.findByName" );
        }
        
        public void intercept( InvocationContext invocationContext ) throws Exception {

            Object [] paramVals = invocationContext.getParamVals(); 
            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 its "create" method 
         */
        private Person create( String firstName, String lastName ) throws Exception {
            Context context = new InitialContext();
            
            PersonHome personHome = (PersonHome)context.lookup(Person.JNDI_NAME);
            
            Person person = personHome.create(firstName, lastName);
            
            return person;
        }
    }
    
    
    /**
     * Intercepts calls to the CMR methods of the Person bean (getAddresses), 
     * and populates and returns the collection of address objects. 
     */
    class PersonCMRAspect implements Aspect {
        
        public Pointcut getPointcut(){
            return new MethodPatternPointcut( "Person\\.getAddresses()" );
        }
       
        public void intercept( InvocationContext invocationContext ) throws Exception {

            // note that since this aspect only handles one method, we don't need to check 
            // the method name that we intercepted
            
            // Create the addresses we want using AddressHome
            AddressHome addressHome = (AddressHome)context.lookup(Address.JNDI_NAME);
            // Create empty collection
            Collection addresses = new ArrayList();
            /*
             * Create the address and add it to the collection.
             * We need to pass the parent object (person), this is the 
             * Person interface that we intercepted. 
             */
            Address address =  addressHome.create("1001 Main St", "Washington", "DC",
                    "22222","USA", (Person)invocationContext.getProxyObject() );
            // Set the PK of the address by getting the address bean and calling its setter 
            AddressBean addressBean = (AddressBean)  ((EjbBeanAccess)address).getBean();
            addressBean.setId(123);
            addresses.add( address);
            
            invocationContext.setReturnObject( addresses);
        }
    }
    
    /**
     * Intercepts ejbSelectAll method and populates and returns 
     * the data for this method.
     */
    class SelectAllAspect implements Aspect {
        
        private List people;

        /**
         * Intercepts ejbSelectAll of the PersonBean
         * @see org.mockejb.interceptor.Aspect#getPointcut()
         */
        public Pointcut getPointcut(){
            // Note that the pointcut here is applied to the bean, not to the interface, since ejbSelect is only defined in the bean
            return new MethodPatternPointcut( "PersonBean\\.ejbSelectAll()" );
        }
        
        public void intercept( InvocationContext invocationContext ) throws Exception {
            // we don't need to call "proceed" since the PersonBean does not know 
            // how to handle it anyway.
            
            PersonHome personHome = (PersonHome)context.lookup(Person.JNDI_NAME);
            people = new ArrayList();
            people.add( personHome.create("John", "Smit") );
            // Return the created collection
            invocationContext.setReturnObject( people);
        }
        
        /**
         * Returns the collection of people that was returned to the client
         * so we can check against it in the test method.
         * @return collection of people
         */
        List getPeople() {
            return people;
        }
    }
}