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