/**
* <code>ApplicationContext</code> provides information about the running
* context of the application. Each context is shared by all applications that
- * are open for one user. In web-environment this corresponds to HttpSession.
+ * are open for one user. In a web-environment this corresponds to a
+ * HttpSession.
*
* @author IT Mill Ltd.
* @version
/**
* Returns application context base directory.
*
- * Typically an application is deployed in a such way that is has
+ * Typically an application is deployed in a such way that is has an
* application directory. For web applications this directory is the root
- * directory of the web applications. In some cases application might not
- * have application directory (for example web applications running inside
- * of war).
+ * directory of the web applications. In some cases applications might not
+ * have an application directory (for example web applications running
+ * inside a war).
*
- * @return The application base directory
+ * @return The application base directory or null if the application has no
+ * base directory.
*/
public File getBaseDirectory();
/**
- * Gets the applications in this context.
+ * Returns a collection of all the applications in this context.
*
- * Gets all applications in this context. Each application context contains
- * all applications that are open for one user.
+ * Each application context contains all active applications for one user.
*
- * @return Collection containing all applications in this context
+ * @return A collection containing all the applications in this context.
*/
public Collection getApplications();
/**
- * Adds transaction listener to this context.
+ * Adds a transaction listener to this context.
*
* @param listener
* the listener to be added.
public void addTransactionListener(TransactionListener listener);
/**
- * Removes transaction listener from this context.
+ * Removes a transaction listener from this context.
*
* @param listener
* the listener to be removed.
public void removeTransactionListener(TransactionListener listener);
/**
- * Interface for listening the application transaction events.
- * Implementations of this interface can be used to listen all transactions
- * between the client and the application.
+ * Interface for listening to transaction events. Implement this interface
+ * to listen to all transactions between the client and the application.
*
*/
public interface TransactionListener extends Serializable {
/**
* Invoked at the beginning of every transaction.
*
+ * The transaction is linked to the context, not the application so if
+ * you have multiple applications running in the same context you need
+ * to check that the request is associated with the application you are
+ * interested in. This can be done looking at the application parameter.
+ *
* @param application
* the Application object.
* @param transactionData
/**
* Invoked at the end of every transaction.
*
+ * The transaction is linked to the context, not the application so if
+ * you have multiple applications running in the same context you need
+ * to check that the request is associated with the application you are
+ * interested in. This can be done looking at the application parameter.
+ *
* @param applcation
* the Application object.
* @param transactionData
public class WebApplicationContext implements ApplicationContext,
HttpSessionBindingListener, Serializable {
- protected List<TransactionListener> listeners;
+ protected List<TransactionListener> listeners = Collections
+ .synchronizedList(new LinkedList<TransactionListener>());
protected transient HttpSession session;
}
/**
- * Gets the application context for HttpSession.
+ * Gets the application context for an HttpSession.
*
* @param session
* the HTTP session.
}
/**
- * Adds the transaction listener to this context.
+ * Adds a transaction listener to this context. The transaction listener is
+ * called before and after each each HTTP request related to this session
+ * except when serving static resources.
+ *
*
* @see com.vaadin.service.ApplicationContext#addTransactionListener(com.vaadin.service.ApplicationContext.TransactionListener)
*/
public void addTransactionListener(TransactionListener listener) {
- if (listeners == null) {
- listeners = new LinkedList<TransactionListener>();
- }
listeners.add(listener);
}
/**
- * Removes the transaction listener from this context.
+ * Removes a transaction listener from this context. The transaction
+ * listener is called before and after each each HTTP request related to
+ * this session except when serving static resources.
*
* @see com.vaadin.service.ApplicationContext#removeTransactionListener(com.vaadin.service.ApplicationContext.TransactionListener)
*/
public void removeTransactionListener(TransactionListener listener) {
- if (listeners != null) {
- listeners.remove(listener);
- }
+ listeners.remove(listener);
}
/**
- * Notifies the transaction start.
+ * Sends a notification that a transaction is starting.
*
* @param application
+ * The application associated with the transaction.
* @param request
- * the HTTP request.
+ * the HTTP request that triggered the transaction.
*/
protected void startTransaction(Application application,
HttpServletRequest request) {
- if (listeners == null) {
- return;
- }
- for (final Iterator i = listeners.iterator(); i.hasNext();) {
- ((ApplicationContext.TransactionListener) i.next())
- .transactionStart(application, request);
+ synchronized (listeners) {
+ for (TransactionListener listener : listeners) {
+ listener.transactionStart(application, request);
+ }
}
}
/**
- * Notifies the transaction end.
+ * Sends a notification that a transaction has ended.
*
* @param application
+ * The application associated with the transaction.
* @param request
- * the HTTP request.
+ * the HTTP request that triggered the transaction.
*/
protected void endTransaction(Application application,
HttpServletRequest request) {
- if (listeners == null) {
- return;
- }
-
LinkedList<Exception> exceptions = null;
- for (final Iterator i = listeners.iterator(); i.hasNext();) {
- try {
- ((ApplicationContext.TransactionListener) i.next())
- .transactionEnd(application, request);
- } catch (final RuntimeException t) {
- if (exceptions == null) {
- exceptions = new LinkedList<Exception>();
+
+ synchronized (listeners) {
+ for (TransactionListener listener : listeners) {
+ try {
+ listener.transactionEnd(application, request);
+ } catch (final RuntimeException t) {
+ if (exceptions == null) {
+ exceptions = new LinkedList<Exception>();
+ }
+ exceptions.add(t);
}
- exceptions.add(t);
}
}
--- /dev/null
+package com.vaadin.tests.server;\r
+\r
+import static org.easymock.EasyMock.createMock;\r
+\r
+import java.lang.Thread.UncaughtExceptionHandler;\r
+import java.lang.reflect.InvocationTargetException;\r
+import java.lang.reflect.Method;\r
+import java.net.MalformedURLException;\r
+import java.net.URL;\r
+import java.util.ArrayList;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Properties;\r
+import java.util.Random;\r
+\r
+import javax.servlet.http.HttpServletRequest;\r
+import javax.servlet.http.HttpSession;\r
+\r
+import junit.framework.TestCase;\r
+\r
+import org.easymock.EasyMock;\r
+\r
+import com.vaadin.Application;\r
+import com.vaadin.service.ApplicationContext.TransactionListener;\r
+import com.vaadin.terminal.gwt.server.WebApplicationContext;\r
+\r
+public class TransactionListenersConcurrency extends TestCase {\r
+\r
+ /**\r
+ * This test starts N threads concurrently. Each thread creates an\r
+ * application which adds a transaction listener to the context. A\r
+ * transaction is then started for each application. Some semi-random delays\r
+ * are included so that calls to addTransactionListener and\r
+ * WebApplicationContext.startTransaction are mixed.\r
+ * \r
+ */\r
+ public void testTransactionListeners() throws Exception {\r
+ final List<Throwable> exceptions = new ArrayList<Throwable>();\r
+\r
+ HttpSession session = createSession();\r
+ final WebApplicationContext context = WebApplicationContext\r
+ .getApplicationContext(session);\r
+ List<Thread> threads = new ArrayList<Thread>();\r
+\r
+ for (int i = 0; i < 5; i++) {\r
+ Thread t = new Thread(new Runnable() {\r
+\r
+ public void run() {\r
+ Application app = new Application() {\r
+\r
+ @Override\r
+ public void init() {\r
+ // Sleep 0-1000ms so another transaction has time to\r
+ // start before we add the transaction listener.\r
+ try {\r
+ Thread.sleep((long) (1000 * new Random()\r
+ .nextDouble()));\r
+ } catch (InterruptedException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+\r
+ getContext().addTransactionListener(\r
+ new DelayTransactionListener(2000));\r
+ }\r
+\r
+ };\r
+\r
+ // Start the application so the transaction listener is\r
+ // called later on.\r
+ try {\r
+\r
+ app.start(new URL("http://localhost/"),\r
+ new Properties(), context);\r
+ } catch (MalformedURLException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+\r
+ try {\r
+ // Call the transaction listener using reflection as\r
+ // startTransaction is protected.\r
+\r
+ Method m = context.getClass().getDeclaredMethod(\r
+ "startTransaction", Application.class,\r
+ HttpServletRequest.class);\r
+ m.setAccessible(true);\r
+ m.invoke(context, app, null);\r
+ } catch (Exception e) {\r
+ throw new RuntimeException(e);\r
+ }\r
+ }\r
+\r
+ });\r
+\r
+ threads.add(t);\r
+ t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {\r
+\r
+ public void uncaughtException(Thread t, Throwable e) {\r
+ e = e.getCause();\r
+ exceptions.add(e);\r
+ }\r
+ });\r
+ }\r
+\r
+ // Start the threads and wait for all of them to finish\r
+ for (Thread t : threads) {\r
+ t.start();\r
+ }\r
+ int running = threads.size();\r
+\r
+ while (running > 0) {\r
+ for (Iterator<Thread> i = threads.iterator(); i.hasNext();) {\r
+ Thread t = i.next();\r
+ if (!t.isAlive()) {\r
+ running--;\r
+ i.remove();\r
+ }\r
+ }\r
+ }\r
+\r
+ for (Throwable t : exceptions) {\r
+ if (t instanceof InvocationTargetException) {\r
+ t = t.getCause();\r
+ }\r
+ t.printStackTrace(System.err);\r
+ fail(t.getClass().getName());\r
+ }\r
+\r
+ System.out.println("Done, all ok");\r
+\r
+ }\r
+\r
+ /**\r
+ * Creates a HttpSession mock\r
+ * \r
+ */\r
+ private static HttpSession createSession() {\r
+ HttpSession session = createMock(HttpSession.class);\r
+ EasyMock.expect(\r
+ session.getAttribute(WebApplicationContext.class.getName()))\r
+ .andReturn(null).anyTimes();\r
+ session.setAttribute(\r
+ EasyMock.eq(WebApplicationContext.class.getName()), EasyMock\r
+ .anyObject());\r
+\r
+ EasyMock.replay(session);\r
+ return session;\r
+ }\r
+\r
+ /**\r
+ * A transaction listener that just sleeps for the given amount of time in\r
+ * transactionStart and transactionEnd.\r
+ * \r
+ */\r
+ public static class DelayTransactionListener implements TransactionListener {\r
+\r
+ private int delay;\r
+\r
+ public DelayTransactionListener(int delay) {\r
+ this.delay = delay;\r
+ }\r
+\r
+ public void transactionStart(Application application,\r
+ Object transactionData) {\r
+ try {\r
+ Thread.sleep(delay);\r
+ } catch (InterruptedException e) {\r
+ e.printStackTrace();\r
+ }\r
+\r
+ }\r
+\r
+ public void transactionEnd(Application application,\r
+ Object transactionData) {\r
+ try {\r
+ Thread.sleep(delay);\r
+ } catch (InterruptedException e) {\r
+ e.printStackTrace();\r
+ }\r
+\r
+ }\r
+ }\r
+\r
+}\r