001    package com.mockrunner.ejb;
002    
003    import javax.ejb.EJBHome;
004    import javax.ejb.EJBLocalHome;
005    import javax.jms.ConnectionFactory;
006    import javax.jms.Destination;
007    import javax.jms.Topic;
008    import javax.naming.InitialContext;
009    import javax.naming.NamingException;
010    
011    import org.apache.commons.beanutils.MethodUtils;
012    import org.apache.commons.logging.Log;
013    import org.apache.commons.logging.LogFactory;
014    import org.mockejb.BasicEjbDescriptor;
015    import org.mockejb.EntityBeanDescriptor;
016    import org.mockejb.MDBDescriptor;
017    import org.mockejb.SessionBeanDescriptor;
018    import org.mockejb.TransactionManager;
019    import org.mockejb.TransactionPolicy;
020    import org.mockejb.interceptor.AspectSystemFactory;
021    import org.mockejb.interceptor.ClassPointcut;
022    
023    import com.mockrunner.base.VerifyFailedException;
024    import com.mockrunner.mock.ejb.EJBMockObjectFactory;
025    import com.mockrunner.mock.ejb.MockUserTransaction;
026    import com.mockrunner.util.common.ClassUtil;
027    
028    /**
029     * Module for EJB tests.
030     */
031    public class EJBTestModule
032    {
033        private final static Log log = LogFactory.getLog(EJBTestModule.class);
034        private EJBMockObjectFactory mockFactory;
035        private String impSuffix;
036        private String homeInterfaceSuffix;
037        private String businessInterfaceSuffix;
038        private String homeInterfacePackage;
039        private String businessInterfacePackage;
040        
041        public EJBTestModule(EJBMockObjectFactory mockFactory)
042        {
043            this.mockFactory = mockFactory;
044            impSuffix = "Bean";
045            homeInterfaceSuffix = "Home";
046            businessInterfaceSuffix = "";
047        }
048        
049        /**
050         * Sets the suffix of the bean implementation class. The
051         * default is <i>"Bean"</i>, i.e. if the remote interface has
052         * the name <code>Test</code> the implementation class is
053         * <code>TestBean</code>.
054         * @param impSuffix the bean implementation suffix
055         */
056        public void setImplementationSuffix(String impSuffix)
057        {
058            this.impSuffix = impSuffix;
059        }
060        
061        /**
062         * Sets the suffix of the remote (resp. local) interface. The
063         * default is an empty string, i.e. if the implementation class is
064         * <code>TestBean</code>, the remote interface is <code>Test</code>
065         * @param businessInterfaceSuffix the bean remote interface suffix
066         */
067        public void setBusinessInterfaceSuffix(String businessInterfaceSuffix)
068        {
069            this.businessInterfaceSuffix = businessInterfaceSuffix;
070        }
071        
072        /**
073         * Sets the suffix of the home (resp. local home) interface. The
074         * default is <i>"Home"</i>, i.e. if the implementation class is
075         * <code>TestBean</code>, the home interface is <code>TestHome</code>
076         * @param homeInterfaceSuffix the bean home interface suffix
077         */
078        public void setHomeInterfaceSuffix(String homeInterfaceSuffix)
079        {
080            this.homeInterfaceSuffix = homeInterfaceSuffix;
081        }
082        
083        /**
084         * Sets the package for the bean home and remote interfaces. Per
085         * default, the framework expects that the interfaces are in the
086         * same package as the bean implementation classes.
087         * @param interfacePackage the package name for home and remote interfaces
088         */
089        public void setInterfacePackage(String interfacePackage)
090        {
091            setHomeInterfacePackage(interfacePackage);
092            setBusinessInterfacePackage(interfacePackage);
093        }
094        
095        /**
096         * Sets the package for the bean home (resp. local home) interface. Per
097         * default, the framework expects that the interfaces are in the
098         * same package as the bean implementation classes.
099         * @param homeInterfacePackage the package name for home interface
100         */
101        public void setHomeInterfacePackage(String homeInterfacePackage)
102        {
103            this.homeInterfacePackage = homeInterfacePackage;
104        }
105        
106        /**
107         * Sets the package for the bean remote (resp. local) interface. Per
108         * default, the framework expects that the interfaces are in the
109         * same package as the bean implementation classes.
110         * @param businessInterfacePackage the package name for remote interface
111         */
112        public void setBusinessInterfacePackage(String businessInterfacePackage)
113        {
114            this.businessInterfacePackage = businessInterfacePackage;
115        }
116        
117        /**
118         * Deploys a bean to the mock container using the specified
119         * descriptor. Sets the transaction policy <i>SUPPORTS</i>.
120         * Determines the type of bean (session, entity, message driven)
121         * using the descriptor.
122         * @param descriptor the descriptor
123         */
124        public void deploy(BasicEjbDescriptor descriptor)
125        {
126            deploy(descriptor, TransactionPolicy.SUPPORTS);
127        }
128        
129        /**
130         * Deploys a bean to the mock container using the specified
131         * descriptor. The specified transaction policy will be automatically set.
132         * Determines the type of bean (session, entity, message driven)
133         * using the descriptor.
134         * @param descriptor the descriptor
135         * @param policy the transaction policy
136         */
137        public void deploy(BasicEjbDescriptor descriptor, TransactionPolicy policy)
138        {
139            try
140            {
141                if(descriptor instanceof SessionBeanDescriptor)
142                {
143                                    mockFactory.getMockContainer().deploy((SessionBeanDescriptor)descriptor);
144                }
145                else if(descriptor instanceof EntityBeanDescriptor)
146                {
147                                    mockFactory.getMockContainer().deploy((EntityBeanDescriptor)descriptor);
148                }
149                            else if(descriptor instanceof MDBDescriptor)
150                            {
151                                    mockFactory.getMockContainer().deploy((MDBDescriptor)descriptor);
152                            }
153                            AspectSystemFactory.getAspectSystem().add(new ClassPointcut(descriptor.getIfaceClass(), false), new TransactionManager(policy));
154            }
155            catch(Exception exc)
156            {
157                log.error(exc.getMessage(), exc);
158            } 
159        }
160        
161        /**
162         * Deploys a stateless session bean to the mock container. You have to specify
163         * the implementation class and the JNDI name. The frameworks
164         * determines the home and remote interfaces based on the
165         * information specified with the <code>setSuffix</code>
166         * and <code>setPackage</code> methods.
167         * Sets the transaction policy <i>SUPPORTS</i>.
168         * @param jndiName the JNDI name
169         * @param beanClass the bean implementation class
170         */
171        public void deploySessionBean(String jndiName, Class beanClass)
172        {
173                    deploySessionBean(jndiName, beanClass, false, TransactionPolicy.SUPPORTS);
174        }
175        
176        /**
177         * Deploys a session bean to the mock container. You have to specify
178         * the implementation class and the JNDI name. The frameworks
179         * determines the home and remote interfaces based on the
180         * information specified with the <code>setSuffix</code>
181         * and <code>setPackage</code> methods.
182         * Sets the transaction policy <i>SUPPORTS</i>.
183         * @param jndiName the JNDI name
184         * @param beanClass the bean implementation class
185         * @param stateful is the bean stateful
186         */
187        public void deploySessionBean(String jndiName, Class beanClass, boolean stateful)
188        {
189                    deploySessionBean(jndiName, beanClass, stateful, TransactionPolicy.SUPPORTS);
190        }
191        
192        /**
193         * Deploys a stateless session bean to the mock container. You have to specify
194         * the implementation class and the JNDI name. The frameworks
195         * determines the home and remote interfaces based on the
196         * information specified with the <code>setSuffix</code>
197         * and <code>setPackage</code> methods.
198         * The specified transaction policy will be automatically set.
199         * @param jndiName the JNDI name
200         * @param beanClass the bean implementation class
201         * @param policy the transaction policy
202         */
203        public void deploySessionBean(String jndiName, Class beanClass, TransactionPolicy policy)
204        {
205                    deploySessionBean(jndiName, beanClass, false, policy);
206        }
207        
208        /**
209         * Deploys a session bean to the mock container. You have to specify
210         * the implementation class and the JNDI name. The frameworks
211         * determines the home and remote interfaces based on the
212         * information specified with the <code>setSuffix</code>
213         * and <code>setPackage</code> methods.
214         * The specified transaction policy will be automatically set.
215         * @param jndiName the JNDI name
216         * @param beanClass the bean implementation class
217         * @param stateful is the bean stateful
218         * @param policy the transaction policy
219         */
220        public void deploySessionBean(String jndiName, Class beanClass, boolean stateful, TransactionPolicy policy)
221        {
222            SessionBeanDescriptor descriptor = new SessionBeanDescriptor(jndiName, getHomeClass(beanClass), getRemoteClass(beanClass), beanClass);
223            descriptor.setStateful(stateful);
224            deploy(descriptor, policy);
225        }
226        
227            /**
228             * Deploys a stateless session bean to the mock container. You have to specify
229             * the implementation class and the JNDI name. The frameworks
230             * determines the home and remote interfaces based on the
231             * information specified with the <code>setSuffix</code>
232             * and <code>setPackage</code> methods.
233             * Sets the transaction policy <i>SUPPORTS</i>.
234             * @param jndiName the JNDI name
235             * @param bean the bean implementation
236             */
237            public void deploySessionBean(String jndiName, Object bean)
238            {
239                    deploySessionBean(jndiName, bean, false, TransactionPolicy.SUPPORTS);
240            }
241    
242            /**
243             * Deploys a session bean to the mock container. You have to specify
244             * the implementation class and the JNDI name. The frameworks
245             * determines the home and remote interfaces based on the
246             * information specified with the <code>setSuffix</code>
247             * and <code>setPackage</code> methods.
248             * Sets the transaction policy <i>SUPPORTS</i>.
249             * @param jndiName the JNDI name
250             * @param bean the bean implementation
251             * @param stateful is the bean stateful
252             */
253            public void deploySessionBean(String jndiName, Object bean, boolean stateful)
254            {
255                    deploySessionBean(jndiName, bean, stateful, TransactionPolicy.SUPPORTS);
256            }
257    
258            /**
259             * Deploys a stateless session bean to the mock container. You have to specify
260             * the implementation class and the JNDI name. The frameworks
261             * determines the home and remote interfaces based on the
262             * information specified with the <code>setSuffix</code>
263             * and <code>setPackage</code> methods.
264             * The specified transaction policy will be automatically set.
265             * @param jndiName the JNDI name
266             * @param bean the bean implementation
267             * @param policy the transaction policy
268             */
269            public void deploySessionBean(String jndiName, Object bean, TransactionPolicy policy)
270            {
271                    deploySessionBean(jndiName, bean, false, policy);
272            }
273    
274            /**
275             * Deploys a session bean to the mock container. You have to specify
276             * the implementation class and the JNDI name. The frameworks
277             * determines the home and remote interfaces based on the
278             * information specified with the <code>setSuffix</code>
279             * and <code>setPackage</code> methods.
280             * The specified transaction policy will be automatically set.
281             * @param jndiName the JNDI name
282             * @param bean the bean implementation
283             * @param stateful is the bean stateful
284             * @param policy the transaction policy
285             */
286            public void deploySessionBean(String jndiName, Object bean, boolean stateful, TransactionPolicy policy)
287            {
288                    SessionBeanDescriptor descriptor = new SessionBeanDescriptor(jndiName, getHomeClass(bean.getClass()), getRemoteClass(bean.getClass()), bean);
289                    descriptor.setStateful(stateful);
290                    deploy(descriptor, policy);
291            }
292        
293            /**
294             * Deploys an entity bean to the mock container. You have to specify
295             * the implementation class and the JNDI name. The frameworks
296             * determines the home and remote interfaces based on the
297             * information specified with the <code>setSuffix</code>
298             * and <code>setPackage</code> methods.
299             * Sets the transaction policy <i>SUPPORTS</i>.
300             * @param jndiName the JNDI name
301             * @param beanClass the bean implementation class
302             */
303            public void deployEntityBean(String jndiName, Class beanClass)
304            {
305                    deployEntityBean(jndiName, beanClass, TransactionPolicy.SUPPORTS);
306            }
307        
308            /**
309             * Deploys an entity bean to the mock container. You have to specify
310             * the implementation class and the JNDI name. The frameworks
311             * determines the home and remote interfaces based on the
312             * information specified with the <code>setSuffix</code>
313             * and <code>setPackage</code> methods.
314             * The specified transaction policy will be automatically set.
315             * @param jndiName the JNDI name
316             * @param beanClass the bean implementation class
317             * @param policy the transaction policy
318             */
319            public void deployEntityBean(String jndiName, Class beanClass, TransactionPolicy policy)
320            {
321                    EntityBeanDescriptor descriptor = new EntityBeanDescriptor(jndiName, getHomeClass(beanClass), getRemoteClass(beanClass), beanClass);
322                    deploy(descriptor, policy);
323            }
324            
325            /**
326             * Deploys a message driven bean to the mock container.
327             * You have to specify JNDI names for connection factory and
328             * destination. For creating connection factory and destination 
329             * objects you can use {@link com.mockrunner.mock.jms.JMSMockObjectFactory}
330             * and {@link com.mockrunner.jms.DestinationManager}.
331             * The specified objects are automatically bound to JNDI using
332             * the specified names. The mock container automatically creates
333             * a connection and session.
334             * Sets the transaction policy <i>NOT_SUPPORTED</i>.
335             * @param connectionFactoryJndiName the JNDI name of the connection factory
336             * @param destinationJndiName the JNDI name of the destination
337             * @param connectionFactory the connection factory
338             * @param destination the destination
339             * @param bean the message driven bean instance
340             */
341            public void deployMessageBean(String connectionFactoryJndiName, String destinationJndiName, ConnectionFactory connectionFactory, Destination destination, Object bean)
342            {
343                    deployMessageBean(connectionFactoryJndiName, destinationJndiName, connectionFactory, destination, bean, TransactionPolicy.NOT_SUPPORTED);
344            }
345            
346            /**
347             * Deploys a message driven bean to the mock container.
348             * You have to specify JNDI names for connection factory and
349             * destination. For creating connection factory and destination 
350             * objects you can use {@link com.mockrunner.mock.jms.JMSMockObjectFactory}
351             * and {@link com.mockrunner.jms.DestinationManager}.
352             * The specified objects are automatically bound to JNDI using
353             * the specified names. The mock container automatically creates
354             * a connection and session.
355             * The specified transaction policy will be automatically set.
356             * @param connectionFactoryJndiName the JNDI name of the connection factory
357             * @param destinationJndiName the JNDI name of the destination
358             * @param connectionFactory the connection factory
359             * @param destination the destination
360             * @param bean the message driven bean instance
361             * @param policy the transaction policy
362             */
363            public void deployMessageBean(String connectionFactoryJndiName, String destinationJndiName, ConnectionFactory connectionFactory, Destination destination, Object bean, TransactionPolicy policy)
364            {
365                    bindToContext(connectionFactoryJndiName, connectionFactory);
366                    bindToContext(destinationJndiName, destination);
367                    MDBDescriptor descriptor = new MDBDescriptor(connectionFactoryJndiName, destinationJndiName, bean);
368                    descriptor.setIsAlreadyBound(true);
369                    descriptor.setIsTopic(destination instanceof Topic);
370                    deploy(descriptor, policy);
371            }
372        
373        /**
374         * Adds an object to the mock context by calling <code>rebind</code>
375         * @param name JNDI name of the object
376         * @param object the object to add
377         */
378        public void bindToContext(String name, Object object)
379        {
380            try
381            {
382                InitialContext context = new InitialContext();
383                context.rebind(name, object);
384            }
385            catch(NamingException exc)
386            {
387                throw new RuntimeException("Object with name " + name + " not found.");
388            }
389        }
390        
391        /**
392         * Lookup an object. If the object is not bound to the <code>InitialContext</code>,
393         * a <code>RuntimeException</code> will be thrown.
394         * @param name JNDI name of the object
395         * @return the object
396         * @throws RuntimeException if an object with the specified name cannot be found.
397         */
398        public Object lookup(String name)
399        {
400            try
401            {
402                InitialContext context = new InitialContext();
403                return context.lookup(name);
404            }
405            catch(NamingException exc)
406            {
407                throw new RuntimeException("Object with name " + name + " not found.");
408            }
409        }
410        
411        /**
412         * @deprecated use {@link #createBean(String)}
413         */
414        public Object lookupBean(String name)
415        {
416            return createBean(name);
417        }
418        
419        /**
420         * Create an EJB. The method looks up the home interface, calls
421         * the <code>create</code> method and returns the result, which
422         * you can cast to the remote interface. This method only works
423         * with <code>create</code> methods that have an empty parameter list.
424         * The <code>create</code> method must have the name <code>create</code>
425         * with no postfix.
426         * It works with the mock container but may fail with a real remote container.
427         * This method throws a <code>RuntimeException</code> if no object with the 
428         * specified name can be found. If the found object is no EJB home interface,
429         * or if the corresponding <code>create</code> method cannot be found, this
430         * method returns <code>null</code>.
431         * @param name JNDI name of the bean
432         * @return the bean
433         * @throws RuntimeException in case of error
434         */
435        public Object createBean(String name)
436        {
437            return createBean(name, new Object[0]);
438        }
439        
440        /**
441         * @deprecated use {@link #createBean(String, Object[])}
442         */
443        public Object lookupBean(String name, Object[] parameters)
444        {
445            return createBean(name, parameters);
446        }
447        
448        /**
449         * Create an EJB. The method looks up the home interface, calls
450         * the <code>create</code> method with the specified parameters
451         * and returns the result, which you can cast to the remote interface.
452         * The <code>create</code> method must have the name <code>create</code>
453         * with no postfix.
454         * This method works with the mock container but may fail with
455         * a real remote container.
456         * This method throws a <code>RuntimeException</code> if no object with the 
457         * specified name can be found. If the found object is no EJB home interface,
458         * or if the corresponding <code>create</code> method cannot be found, this
459         * method returns <code>null</code>.
460         * This method does not allow <code>null</code> as a parameter, because
461         * the type of the parameter cannot be determined in this case.
462         * @param name JNDI name of the bean
463         * @param parameters the parameters, <code>null</code> parameters are not allowed,
464         *  primitive types are automatically unwrapped
465         * @return the bean 
466         * @throws RuntimeException in case of error
467         */
468        public Object createBean(String name, Object[] parameters)
469        {
470            return createBean(name, "create", parameters);
471        }
472        
473        /**
474         * @deprecated use {@link #createBean(String, String, Object[])}
475         */
476        public Object lookupBean(String name, String createMethod, Object[] parameters)
477        {
478            return createBean(name, createMethod, parameters);
479        }
480        
481        /**
482         * Create an EJB. The method looks up the home interface, calls
483         * the <code>create</code> method with the specified parameters
484         * and returns the result, which you can cast to the remote interface.
485         * This method works with the mock container but may fail with
486         * a real remote container.
487         * This method throws a <code>RuntimeException</code> if no object with the 
488         * specified name can be found. If the found object is no EJB home interface,
489         * or if the corresponding <code>create</code> method cannot be found, this
490         * method returns <code>null</code>.
491         * This method does not allow <code>null</code> as a parameter, because
492         * the type of the parameter cannot be determined in this case.
493         * @param name JNDI name of the bean
494         * @param createMethod the name of the create method
495         * @param parameters the parameters, <code>null</code> parameters are not allowed,
496         *  primitive types are automatically unwrapped
497         * @return the bean 
498         * @throws RuntimeException in case of error
499         */
500        public Object createBean(String name, String createMethod, Object[] parameters)
501        {
502            Object home = lookupHome(name);
503            return invokeHomeMethod(home, createMethod, parameters, null);
504        }
505        
506        /**
507         * Create an EJB. The method looks up the home interface, calls
508         * the <code>create</code> method with the specified parameters
509         * and returns the result, which you can cast to the remote interface.
510         * This method works with the mock container but may fail with
511         * a real remote container.
512         * This method throws a <code>RuntimeException</code> if no object with the 
513         * specified name can be found. If the found object is no EJB home interface,
514         * or if the corresponding <code>create</code> method cannot be found, this
515         * method returns <code>null</code>.
516         * This method does allow <code>null</code> as a parameter.
517         * @param name JNDI name of the bean
518         * @param createMethod the name of the create method
519         * @param parameters the parameters, <code>null</code> is allowed as a parameter
520         * @param parameterTypes the type of the specified parameters
521         * @return the bean 
522         * @throws RuntimeException in case of error
523         */
524        public Object createBean(String name, String createMethod, Object[] parameters, Class[] parameterTypes)
525        {
526            Object home = lookupHome(name);
527            return invokeHomeMethod(home, createMethod, parameters, parameterTypes);
528        }
529        
530        /**
531         * Create an entity EJB. The method looks up the home interface, calls
532         * the <code>create</code> method and returns the result, which
533         * you can cast to the remote interface. This method only works
534         * with <code>create</code> methods that have an empty parameter list.
535         * The <code>create</code> method must have the name <code>create</code>
536         * with no postfix.
537         * It works with the mock container but may fail with a real remote container.
538         * This method throws a <code>RuntimeException</code> if no object with the 
539         * specified name can be found. If the found object is no EJB home interface,
540         * or if the corresponding <code>create</code> method cannot be found, this
541         * method returns <code>null</code>.
542         * The created entity EJB is added to the mock database automatically
543         * using the provided primary key.
544         * @param name JNDI name of the bean
545         * @param primaryKey the primary key
546         * @return the bean
547         * @throws RuntimeException in case of error
548         */
549        public Object createEntityBean(String name, Object primaryKey)
550        {
551            return createEntityBean(name, new Object[0], primaryKey);
552        }
553        
554        /**
555         * Create an entity EJB. The method looks up the home interface, calls
556         * the <code>create</code> method with the specified parameters
557         * and returns the result, which you can cast to the remote interface.
558         * The <code>create</code> method must have the name <code>create</code>
559         * with no postfix.
560         * This method works with the mock container but may fail with
561         * a real remote container.
562         * This method throws a <code>RuntimeException</code> if no object with the 
563         * specified name can be found. If the found object is no EJB home interface,
564         * or if the corresponding <code>create</code> method cannot be found, this
565         * method returns <code>null</code>.
566         * The created entity EJB is added to the mock database automatically
567         * using the provided primary key.
568         * This method does not allow <code>null</code> as a parameter, because
569         * the type of the parameter cannot be determined in this case.
570         * @param name JNDI name of the bean
571         * @param parameters the parameters, <code>null</code> parameters are not allowed,
572         *  primitive types are automatically unwrapped
573         * @param primaryKey the primary key
574         * @return the bean 
575         * @throws RuntimeException in case of error
576         */
577        public Object createEntityBean(String name, Object[] parameters, Object primaryKey)
578        {
579            return createEntityBean(name, "create", parameters, primaryKey);
580        }
581        
582        /**
583         * Create an entity EJB. The method looks up the home interface, calls
584         * the <code>create</code> method with the specified parameters
585         * and returns the result, which you can cast to the remote interface.
586         * This method works with the mock container but may fail with
587         * a real remote container.
588         * This method throws a <code>RuntimeException</code> if no object with the 
589         * specified name can be found. If the found object is no EJB home interface,
590         * or if the corresponding <code>create</code> method cannot be found, this
591         * method returns <code>null</code>.
592         * The created entity EJB is added to the mock database automatically
593         * using the provided primary key.
594         * This method does not allow <code>null</code> as a parameter, because
595         * the type of the parameter cannot be determined in this case.
596         * @param name JNDI name of the bean
597         * @param createMethod the name of the create method
598         * @param parameters the parameters, <code>null</code> parameters are not allowed,
599         *  primitive types are automatically unwrapped
600         * @param primaryKey the primary key
601         * @return the bean 
602         * @throws RuntimeException in case of error
603         */
604        public Object createEntityBean(String name, String createMethod, Object[] parameters, Object primaryKey)
605        {
606            return createEntityBean(name, createMethod, parameters, (Class[])null, primaryKey);
607        }
608        
609        /**
610         * Create an entity EJB. The method looks up the home interface, calls
611         * the <code>create</code> method with the specified parameters
612         * and returns the result, which you can cast to the remote interface.
613         * This method works with the mock container but may fail with
614         * a real remote container.
615         * This method throws a <code>RuntimeException</code> if no object with the 
616         * specified name can be found. If the found object is no EJB home interface,
617         * or if the corresponding <code>create</code> method cannot be found, this
618         * method returns <code>null</code>.
619         * The created entity EJB is added to the mock database automatically
620         * using the provided primary key.
621         * This method does allow <code>null</code> as a parameter.
622         * @param name JNDI name of the bean
623         * @param createMethod the name of the create method
624         * @param parameters the parameters, <code>null</code> is allowed as a parameter
625         * @param primaryKey the primary key
626         * @return the bean 
627         * @throws RuntimeException in case of error
628         */
629        public Object createEntityBean(String name, String createMethod, Object[] parameters, Class[] parameterTypes, Object primaryKey)
630        {
631            Object home = lookupHome(name);
632            Object remote = invokeHomeMethod(home, createMethod, parameters, parameterTypes);
633            Class[] interfaces = home.getClass().getInterfaces();
634            Class homeInterface = getHomeInterfaceClass(interfaces);
635            if(null != homeInterface && null != remote)
636            {
637                mockFactory.getMockContainer().getEntityDatabase().add(homeInterface, primaryKey, remote);
638            }
639            return remote;
640        }
641        
642        /**
643         * Finds an entity EJB by its primary key. The method looks up the home interface, 
644         * calls the <code>findByPrimaryKey</code> method and returns the result, 
645         * which you can cast to the remote interface.
646         * This method works with the mock container but may fail with
647         * a real remote container.
648         * This method throws a <code>RuntimeException</code> if no object with the 
649         * specified name can be found. If the found object is no EJB home interface,
650         * or if the <code>findByPrimaryKey</code> method cannot be found, this
651         * method returns <code>null</code>.
652         * If the mock container throws an exception because the primary key
653         * cannot be found in the entity database, this method returns <code>null</code>.
654         * @param name JNDI name of the bean
655         * @param primaryKey the primary key
656         * @return the bean 
657         * @throws RuntimeException in case of error
658         */
659        public Object findByPrimaryKey(String name, Object primaryKey)
660        {
661            Object home = lookupHome(name);
662            return invokeHomeMethod(home, "findByPrimaryKey", new Object[] {primaryKey}, null);
663        }
664        
665        private Class getHomeInterfaceClass(Class[] interfaces)
666        {
667            for(int ii = 0; ii < interfaces.length; ii++)
668            {
669                Class current = interfaces[ii];
670                if(EJBHome.class.isAssignableFrom(current) || EJBLocalHome.class.isAssignableFrom(current))
671                {
672                     return current;
673                }
674            }
675            return null;
676        }
677    
678        private Object lookupHome(String name)
679        {
680            Object object = lookup(name);
681            if(null == object) return null;
682            if(!(object instanceof EJBHome || object instanceof EJBLocalHome)) return null;
683            return object;
684        }
685        
686        private Object invokeHomeMethod(Object home, String createMethod, Object[] parameters, Class[] parameterTypes)
687        {
688            if(null == parameterTypes)
689            {
690                checkNullParameters(createMethod, parameters);
691            }
692            try
693            {
694                if(null == parameterTypes)
695                {
696                    return MethodUtils.invokeMethod(home, createMethod, parameters);
697                }
698                else
699                {
700                    return MethodUtils.invokeExactMethod(home, createMethod, parameters, parameterTypes);
701                }
702            }
703            catch(Exception exc)
704            {
705                log.error(exc.getMessage(), exc);
706                return null;
707            }
708        }
709        
710        private void checkNullParameters(String createMethod, Object[] parameters)
711        {
712            for(int ii = 0; ii < parameters.length; ii++)
713            {
714                if(null == parameters[ii])
715                {
716                    String message = "Calling method " + createMethod + " failed. ";
717                    message += "Null is not allowed if the parameter types are not specified.";
718                    throw new IllegalArgumentException(message);
719                }
720            }
721        }
722    
723        /**
724         * Resets the {@link com.mockrunner.mock.ejb.MockUserTransaction}.
725         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
726         * implementation, this method does nothing.
727         */
728        public void resetUserTransaction()
729        {
730            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
731            if(null == transaction) return;
732            transaction.reset();
733        }
734        
735        /**
736         * Verifies that the transaction was committed. If you are using
737         * container managed transactions, you have to set an appropriate 
738         * transaction policy, e.g. <i>REQUIRED</i>. Otherwise the container
739         * will not commit the mock transaction.
740         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
741         * implementation, this method throws a <code>VerifyFailedException</code>.
742         * @throws VerifyFailedException if verification fails
743         */
744        public void verifyCommitted()
745        {
746            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
747            if(null == transaction)
748            {
749                throw new VerifyFailedException("MockTransaction is null.");
750            }
751            if(!transaction.wasCommitCalled())
752            {
753                throw new VerifyFailedException("Transaction was not committed.");
754            }
755        }
756        
757        /**
758         * Verifies that the transaction was not committed. If you are using
759         * container managed transactions, you have to set an appropriate 
760         * transaction policy, e.g. <i>REQUIRED</i>.
761         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
762         * implementation, this method throws a <code>VerifyFailedException</code>.
763         * @throws VerifyFailedException if verification fails
764         */
765        public void verifyNotCommitted()
766        {
767            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
768            if(null == transaction)
769            {
770                throw new VerifyFailedException("MockTransaction is null.");
771            }
772            if(transaction.wasCommitCalled())
773            {
774                throw new VerifyFailedException("Transaction was committed.");
775            }
776        }
777        
778        /**
779         * Verifies that the transaction was rolled back. If you are using
780         * container managed transactions, you have to set an appropriate 
781         * transaction policy, e.g. <i>REQUIRED</i>. Otherwise the container
782         * will not rollback the mock transaction.
783         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
784         * implementation, this method throws a <code>VerifyFailedException</code>.
785         * @throws VerifyFailedException if verification fails
786         */
787        public void verifyRolledBack()
788        {
789            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
790            if(null == transaction)
791            {
792                throw new VerifyFailedException("MockTransaction is null.");
793            }
794            if(!transaction.wasRollbackCalled())
795            {
796                throw new VerifyFailedException("Transaction was not rolled back");
797            }
798        }
799    
800        /**
801         * Verifies that the transaction was not rolled back. If you are using
802         * container managed transactions, you have to set an appropriate 
803         * transaction policy, e.g. <i>REQUIRED</i>.
804         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
805         * implementation, this method throws a <code>VerifyFailedException</code>.
806         * @throws VerifyFailedException if verification fails
807         */
808        public void verifyNotRolledBack()
809        {
810            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
811            if(null == transaction)
812            {
813                throw new VerifyFailedException("MockTransaction is null.");
814            }
815            if(transaction.wasRollbackCalled())
816            {
817                throw new VerifyFailedException("Transaction was rolled back");
818            }
819        }
820        
821        /**
822         * Verifies that the transaction was marked for rollback using
823         * the method <code>setRollbackOnly()</code>.
824         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
825         * implementation, this method throws a <code>VerifyFailedException</code>.
826         * @throws VerifyFailedException if verification fails
827         */
828        public void verifyMarkedForRollback()
829        {
830            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
831            if(null == transaction)
832            {
833                throw new VerifyFailedException("MockTransaction is null.");
834            }
835            if(!transaction.wasRollbackOnlyCalled())
836            {
837                throw new VerifyFailedException("Transaction was not marked for rollback");
838            }
839        }
840    
841        /**
842         * Verifies that the transaction was not marked for rollback.
843         * Note: If you do not use the {@link com.mockrunner.mock.ejb.MockUserTransaction}
844         * implementation, this method throws a <code>VerifyFailedException</code>.
845         * @throws VerifyFailedException if verification fails
846         */
847        public void verifyNotMarkedForRollback()
848        {
849            MockUserTransaction transaction = mockFactory.getMockUserTransaction();
850            if(null == transaction)
851            {
852                throw new VerifyFailedException("MockTransaction is null.");
853            }
854            if(transaction.wasRollbackOnlyCalled())
855            {
856                throw new VerifyFailedException("Transaction was marked for rollback");
857            }
858        }
859        
860        private Class getHomeClass(Class beanClass)
861        {
862            String classPackage = ClassUtil.getPackageName(beanClass);
863            String className = ClassUtil.getClassName(beanClass);
864            className = truncateImplClassName(className);
865            if(null != homeInterfaceSuffix && 0 != homeInterfaceSuffix.length())
866            {
867                className += homeInterfaceSuffix;
868            }
869            if(null != homeInterfacePackage && 0 != homeInterfacePackage.length())
870            {
871                classPackage = homeInterfacePackage;
872            }
873            try
874            {
875                return Class.forName(getClassName(classPackage, className), true, beanClass.getClassLoader());
876            }
877            catch(ClassNotFoundException exc)
878            {
879                throw new RuntimeException("Home interface not found: " + exc.getMessage());
880            }
881        }
882        
883        private Class getRemoteClass(Class beanClass)
884        {
885            String classPackage = ClassUtil.getPackageName(beanClass);
886            String className = ClassUtil.getClassName(beanClass);
887            className = truncateImplClassName(className);
888            if(null != businessInterfaceSuffix && 0 != businessInterfaceSuffix.length())
889            {
890                className += businessInterfaceSuffix;
891            }
892            if(null != businessInterfacePackage && 0 != businessInterfacePackage.length())
893            {
894                classPackage = businessInterfacePackage;
895            }
896            try
897            {
898                return Class.forName(getClassName(classPackage, className), true, beanClass.getClassLoader());
899            }
900            catch(ClassNotFoundException exc)
901            {
902                throw new RuntimeException("Interface not found: " + exc.getMessage());
903            }
904        }
905        
906        private String getClassName(String packageName, String className)
907        {
908            if(null == packageName || packageName.length() == 0) return className;
909            return packageName + "." + className;
910        }
911    
912        private String truncateImplClassName(String className)
913        {
914            if(null != impSuffix && className.endsWith(impSuffix))
915            {
916                className = className.substring(0, className.length() - impSuffix.length());
917            }
918            return className;
919        }
920    }