MockContainer.java |
package org.mockejb; import javax.jms.*; import javax.naming.*; import org.mockejb.interceptor.*; import org.mockejb.jms.*; /** * Provides methods to "deploy" EJBs. Most deploy * methods simply create Home proxy and bind it to the JNDI. * Since MockEjb is not a container in a true sense, the purpose of this class * is to provide an abstraction that emulates EJB deployment. * * @author Alexander Ananiev */ public class MockContainer { private Context context; private EntityDatabase entityDatabase; /** * We store the security context (principal) on a thread. The field is static to make sure that * there is only one per thread */ private static ThreadLocal threadContext = new ThreadLocal(); /** * Returns the user that was passed to the * MockContainer.login method. Note that the user * is stored as a ThreadLocal variable, so it is available to all classes. * Returns the anonymous user if * login was not called. * * @return the current user of the MockContainer * */ public static MockUser getUser() { // check if we have the principal on the thread MockUser user = (MockUser) threadContext.get(); if ( user == null ) user = MockUser.ANONYMOUS_USER; return user; } /** * Simulates the login operation of the container. This method does * not perform any security checks. * It simply stores the given MockUser object for further use by EJBContext's security-related methods. * * @param user user of the MockContainer */ public void login( MockUser user ){ threadContext.set( user ); } /** * Creates a new instance of the MockContainer for * the given context, deletes all aspects from the AspectSystem and adds the system interceptors, such as * ExceptionHandler. * Clears the EntityDatabase as well. * Creates a default (anonymous) user which is used for all EJB operations unless "login" * is called explicitly. * @param context JNDI context to use for all "bind" operations */ public MockContainer( final Context context ){ this.context = context; threadContext.set( MockUser.ANONYMOUS_USER ); setupDefaultInterceptors(); } /** * Add the interceptors that should always be present to * the AspectSystem. */ protected void setupDefaultInterceptors(){ // create the entity cache loadEntityDatabase(); AspectSystem aspectSystem = AspectSystemFactory.getAspectSystem(); aspectSystem.clear(); aspectSystem.addFirst( new BMPFinderHandler( entityDatabase ) ); aspectSystem.addFirst( new CMPFindByPrimaryKeyHandler( entityDatabase ) ); // All beans should have the exception handler aspectSystem.addFirst( new EjbExceptionHandler()); } /** * Deploys session bean specified by the given descriptor. * <code>MockContainer</code> creates the proxy implementing * session bean home interface and binds it to the JNDI context * using <code>rebind()</code> method. * Clients can subsequently lookup the home and invoke <code>create()</code>. * @param descriptor descriptor of the session bean to deploy * As of MockEJB 0.6, this method does not return MockEjbObject since * the direct use of MockEjbObject is deprecated. AspectSystem should be used instead. */ public void deploy( SessionBeanDescriptor descriptor ) throws NamingException { SessionBeanHome home = new SessionBeanHome( descriptor ); context.rebind( descriptor.getJndiName(), home.createProxy() ); } /** * Deploys entity bean specified by the given descriptor. * */ public void deploy( EntityBeanDescriptor descriptor ) throws NamingException { EntityBeanHome home = new EntityBeanHome( descriptor, entityDatabase ); context.rebind( descriptor.getJndiName(), home.createProxy() ); } /** * If "isAlreadyBound" is "false" in deployment descriptor, * creates mock connection factory and destination and bind them to JNDI. * Otherwise, assumes that the connection factory and destination were created * previously and available from JNDI. The default is "false". * Creates MDB and sets it to listen on the destination. * Handles both queues and topics, depending on "isTopic" setting of the * deployment descriptor. * Note that mock JMS implementation is synchronous, in other words the message sent * to the destination is delivered to MDB right away. * @param descriptor deployment descriptor of the MDB specifying JNDI names of * connection factory and destinations as well as the bean implementation object. * * @throws NamingException in case of problems binding to JNDI or retrieving objects from JNDI * @throws JMSException in case of problems with mock connection factory or destination. */ public void deploy( MDBDescriptor descriptor ) throws NamingException, JMSException { // Create connection factory and destination using JMS 1.0 way MessageConsumer consumer; Connection connection; if ( descriptor.isTopic() ){ TopicConnectionFactory topicConnectionFactory = (TopicConnectionFactory) createJMSObject( descriptor.getConnectionFactoryJndiName(), descriptor.isAlreadyBound(), new TopicConnectionFactoryImpl() ); Topic topic = (Topic) createJMSObject( descriptor.getDestinationJndiName(), descriptor.isAlreadyBound(), new MockTopic( descriptor.getDestinationJndiName() ) ); TopicConnection topicConnection = topicConnectionFactory.createTopicConnection(); connection = topicConnection; // TODO: implement transactions and acknowledgements TopicSession topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); consumer = topicSession.createSubscriber(topic); } else { QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory) createJMSObject( descriptor.getConnectionFactoryJndiName(), descriptor.isAlreadyBound(), new QueueConnectionFactoryImpl() ); Queue queue = (Queue) createJMSObject( descriptor.getDestinationJndiName(), descriptor.isAlreadyBound(), new MockQueue( descriptor.getDestinationJndiName() ) ); QueueConnection queueConnection = queueConnectionFactory.createQueueConnection(); connection = queueConnection; QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); consumer = queueSession.createReceiver( queue ); } // Same routine as for session bean MDBHome home = new MDBHome( descriptor ); MDBHomeIface mdbHome = (MDBHomeIface)home.createProxy(); MessageListener messageListener = mdbHome.create(); consumer.setMessageListener( messageListener ); connection.start(); } /** * Returns an instance of the EntityDatabase that will be used by the * system interceptors (Finder Handlers) used by this instance of the container. * * @return an instance of the entity database */ public EntityDatabase getEntityDatabase(){ return entityDatabase; } /** * Helper method to create connection factory or destination based on the settings * * @param jndiName * @param isAlreadyBound if true, get one from JNDI * @param defaultImpl * @return created object */ private Object createJMSObject( String jndiName, boolean isAlreadyBound, Object defaultImpl ) throws NamingException { Object obj = defaultImpl; if ( isAlreadyBound ) obj = context.lookup( jndiName ); else context.rebind( jndiName, defaultImpl ); return obj; } /** * Creates message-driven bean. This method emulates <code>create()</code> * method of the Session bean home interface. * Since MDBs don't have home interface, <code>MockContainer</code> provides this * service for MDB clients. * @param ejbObject MockEjbObject of the message bean created by <code>deployMessageBean()</code> * @return implementation of the MessageListener interface. The interface is * implemented by a proxy provided by MockEjbObject. * * @deprecated use deploy instead */ public MessageListener createMessageBean( MockEjbObject ejbObject ) { // we use local home to avoid remote exception MDBHomeIface home = (MDBHomeIface) (ejbObject.getHomeImpl()); return home.create(); } /** * Tests if the given throwable is the system exception in terms of the container. * Currently we consider all runtime exceptions and transaction-related exceptions * system exceptions. * <br>Note that the spec is vague on * what is system and non-system exception, so this method might change in the future. * * @param throwable exception in question * @return true if the given throwable is "system" exception */ // TODO: provide the facility for setting an array of exception types. public static boolean isSystemException( Throwable throwable ){ return ( throwable instanceof RuntimeException || throwable instanceof java.rmi.RemoteException || throwable instanceof javax.transaction.SystemException || throwable instanceof javax.transaction.NotSupportedException || throwable instanceof javax.transaction.InvalidTransactionException || throwable instanceof java.lang.reflect.InvocationTargetException); } protected void loadEntityDatabase() { String entityDatabaseClassName = System.getProperty( "mockejb.entity.database" ); if ( entityDatabaseClassName != null ) { try { Class entityDatabaseClass = Class.forName( entityDatabaseClassName, true, this.getClass().getClassLoader()); entityDatabase = (EntityDatabase) entityDatabaseClass.newInstance(); } catch ( ClassNotFoundException cnfe ) { throw new MockEjbSystemException( cnfe ); } catch ( InstantiationException ie ) { throw new MockEjbSystemException( ie ); } catch ( IllegalAccessException iae ) { throw new MockEjbSystemException( iae ); } } else entityDatabase = new EntityDatabaseImpl(); } }