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