001: package org.mockejb;
002:
003: import javax.jms.*;
004:
005: import javax.naming.*;
006:
007: import org.mockejb.interceptor.*;
008: import org.mockejb.jms.*;
009:
010: /**
011: * Provides methods to "deploy" EJBs. Most deploy
012: * methods simply create Home proxy and bind it to the JNDI.
013: * Since MockEjb is not a container in a true sense, the purpose of this class
014: * is to provide an abstraction that emulates EJB deployment.
015: *
016: * @author Alexander Ananiev
017: */
018: public class MockContainer {
019:
020: private Context context;
021:
022: private EntityDatabase entityDatabase;
023:
024: /**
025: * We store the security context (principal) on a thread. The field is static to make sure that
026: * there is only one per thread
027: */
028: private static ThreadLocal threadContext = new ThreadLocal();
029:
030: /**
031: * Returns the user that was passed to the
032: * MockContainer.login method. Note that the user
033: * is stored as a ThreadLocal variable, so it is available to all classes.
034: * Returns the anonymous user if
035: * login was not called.
036: *
037: * @return the current user of the MockContainer
038: *
039: */
040: public static MockUser getUser() {
041:
042: // check if we have the principal on the thread
043: MockUser user = (MockUser) threadContext.get();
044:
045: if (user == null)
046: user = MockUser.ANONYMOUS_USER;
047:
048: return user;
049:
050: }
051:
052: /**
053: * Simulates the login operation of the container. This method does
054: * not perform any security checks.
055: * It simply stores the given MockUser object for further use by EJBContext's security-related methods.
056: *
057: * @param user user of the MockContainer
058: */
059: public void login(MockUser user) {
060: threadContext.set(user);
061: }
062:
063: /**
064: * Creates a new instance of the MockContainer for
065: * the given context, deletes all aspects from the AspectSystem and adds the system interceptors, such as
066: * ExceptionHandler.
067: * Clears the EntityDatabase as well.
068: * Creates a default (anonymous) user which is used for all EJB operations unless "login"
069: * is called explicitly.
070: * @param context JNDI context to use for all "bind" operations
071: */
072: public MockContainer(final Context context) {
073: this .context = context;
074: threadContext.set(MockUser.ANONYMOUS_USER);
075: setupDefaultInterceptors();
076: }
077:
078: /**
079: * Add the interceptors that should always be present to
080: * the AspectSystem.
081: */
082: protected void setupDefaultInterceptors() {
083:
084: // create the entity cache
085: loadEntityDatabase();
086:
087: AspectSystem aspectSystem = AspectSystemFactory
088: .getAspectSystem();
089: aspectSystem.clear();
090:
091: aspectSystem.addFirst(new BMPFinderHandler(entityDatabase));
092: aspectSystem.addFirst(new CMPFindByPrimaryKeyHandler(
093: entityDatabase));
094:
095: // All beans should have the exception handler
096: aspectSystem.addFirst(new EjbExceptionHandler());
097:
098: }
099:
100: /**
101: * Deploys session bean specified by the given descriptor.
102: * <code>MockContainer</code> creates the proxy implementing
103: * session bean home interface and binds it to the JNDI context
104: * using <code>rebind()</code> method.
105: * Clients can subsequently lookup the home and invoke <code>create()</code>.
106: * @param descriptor descriptor of the session bean to deploy
107: * As of MockEJB 0.6, this method does not return MockEjbObject since
108: * the direct use of MockEjbObject is deprecated. AspectSystem should be used instead.
109: */
110: public void deploy(SessionBeanDescriptor descriptor)
111: throws NamingException {
112:
113: SessionBeanHome home = new SessionBeanHome(descriptor);
114:
115: context.rebind(descriptor.getJndiName(), home.createProxy());
116:
117: }
118:
119: /**
120: * Deploys entity bean specified by the given descriptor.
121: *
122: */
123: public void deploy(EntityBeanDescriptor descriptor)
124: throws NamingException {
125:
126: EntityBeanHome home = new EntityBeanHome(descriptor,
127: entityDatabase);
128:
129: context.rebind(descriptor.getJndiName(), home.createProxy());
130:
131: }
132:
133: /**
134: * If "isAlreadyBound" is "false" in deployment descriptor,
135: * creates mock connection factory and destination and bind them to JNDI.
136: * Otherwise, assumes that the connection factory and destination were created
137: * previously and available from JNDI. The default is "false".
138: * Creates MDB and sets it to listen on the destination.
139: * Handles both queues and topics, depending on "isTopic" setting of the
140: * deployment descriptor.
141: * Note that mock JMS implementation is synchronous, in other words the message sent
142: * to the destination is delivered to MDB right away.
143: * @param descriptor deployment descriptor of the MDB specifying JNDI names of
144: * connection factory and destinations as well as the bean implementation object.
145: *
146: * @throws NamingException in case of problems binding to JNDI or retrieving objects from JNDI
147: * @throws JMSException in case of problems with mock connection factory or destination.
148: */
149: public void deploy(MDBDescriptor descriptor)
150: throws NamingException, JMSException {
151:
152: // Create connection factory and destination using JMS 1.0 way
153: MessageConsumer consumer;
154: Connection connection;
155:
156: if (descriptor.isTopic()) {
157:
158: TopicConnectionFactory topicConnectionFactory = (TopicConnectionFactory) createJMSObject(
159: descriptor.getConnectionFactoryJndiName(),
160: descriptor.isAlreadyBound(),
161: new TopicConnectionFactoryImpl());
162:
163: Topic topic = (Topic) createJMSObject(descriptor
164: .getDestinationJndiName(), descriptor
165: .isAlreadyBound(), new MockTopic(descriptor
166: .getDestinationJndiName()));
167:
168: TopicConnection topicConnection = topicConnectionFactory
169: .createTopicConnection();
170: connection = topicConnection;
171: // TODO: implement transactions and acknowledgements
172: TopicSession topicSession = topicConnection
173: .createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
174: consumer = topicSession.createSubscriber(topic);
175:
176: } else {
177:
178: QueueConnectionFactory queueConnectionFactory = (QueueConnectionFactory) createJMSObject(
179: descriptor.getConnectionFactoryJndiName(),
180: descriptor.isAlreadyBound(),
181: new QueueConnectionFactoryImpl());
182:
183: Queue queue = (Queue) createJMSObject(descriptor
184: .getDestinationJndiName(), descriptor
185: .isAlreadyBound(), new MockQueue(descriptor
186: .getDestinationJndiName()));
187:
188: QueueConnection queueConnection = queueConnectionFactory
189: .createQueueConnection();
190: connection = queueConnection;
191: QueueSession queueSession = queueConnection
192: .createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
193: consumer = queueSession.createReceiver(queue);
194: }
195:
196: // Same routine as for session bean
197: MDBHome home = new MDBHome(descriptor);
198: MDBHomeIface mdbHome = (MDBHomeIface) home.createProxy();
199:
200: MessageListener messageListener = mdbHome.create();
201:
202: consumer.setMessageListener(messageListener);
203: connection.start();
204:
205: }
206:
207: /**
208: * Returns an instance of the EntityDatabase that will be used by the
209: * system interceptors (Finder Handlers) used by this instance of the container.
210: *
211: * @return an instance of the entity database
212: */
213: public EntityDatabase getEntityDatabase() {
214:
215: return entityDatabase;
216:
217: }
218:
219: /**
220: * Helper method to create connection factory or destination based on the settings
221: *
222: * @param jndiName
223: * @param isAlreadyBound if true, get one from JNDI
224: * @param defaultImpl
225: * @return created object
226: */
227: private Object createJMSObject(String jndiName,
228: boolean isAlreadyBound, Object defaultImpl)
229: throws NamingException {
230:
231: Object obj = defaultImpl;
232: if (isAlreadyBound)
233: obj = context.lookup(jndiName);
234: else
235: context.rebind(jndiName, defaultImpl);
236:
237: return obj;
238:
239: }
240:
241: /**
242: * Creates message-driven bean. This method emulates <code>create()</code>
243: * method of the Session bean home interface.
244: * Since MDBs don't have home interface, <code>MockContainer</code> provides this
245: * service for MDB clients.
246: * @param ejbObject MockEjbObject of the message bean created by <code>deployMessageBean()</code>
247: * @return implementation of the MessageListener interface. The interface is
248: * implemented by a proxy provided by MockEjbObject.
249: *
250: * @deprecated use deploy instead
251: */
252: public MessageListener createMessageBean(MockEjbObject ejbObject) {
253:
254: // we use local home to avoid remote exception
255: MDBHomeIface home = (MDBHomeIface) (ejbObject.getHomeImpl());
256:
257: return home.create();
258:
259: }
260:
261: /**
262: * Tests if the given throwable is the system exception in terms of the container.
263: * Currently we consider all runtime exceptions and transaction-related exceptions
264: * system exceptions.
265: * <br>Note that the spec is vague on
266: * what is system and non-system exception, so this method might change in the future.
267: *
268: * @param throwable exception in question
269: * @return true if the given throwable is "system" exception
270: */
271: // TODO: provide the facility for setting an array of exception types.
272: public static boolean isSystemException(Throwable throwable) {
273:
274: return (throwable instanceof RuntimeException
275: || throwable instanceof java.rmi.RemoteException
276: || throwable instanceof javax.transaction.SystemException
277: || throwable instanceof javax.transaction.NotSupportedException
278: || throwable instanceof javax.transaction.InvalidTransactionException || throwable instanceof java.lang.reflect.InvocationTargetException);
279: }
280:
281: protected void loadEntityDatabase() {
282:
283: String entityDatabaseClassName = System
284: .getProperty("mockejb.entity.database");
285:
286: if (entityDatabaseClassName != null) {
287: try {
288:
289: Class entityDatabaseClass = Class.forName(
290: entityDatabaseClassName, true, this .getClass()
291: .getClassLoader());
292: entityDatabase = (EntityDatabase) entityDatabaseClass
293: .newInstance();
294:
295: } catch (ClassNotFoundException cnfe) {
296: throw new MockEjbSystemException(cnfe);
297: } catch (InstantiationException ie) {
298: throw new MockEjbSystemException(ie);
299: } catch (IllegalAccessException iae) {
300: throw new MockEjbSystemException(iae);
301: }
302: } else
303: entityDatabase = new EntityDatabaseImpl();
304:
305: }
306:
307: }
|