| 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();
}
}