Hi Matt, Ok, i can reproduce the bug.
1) I generated a new project with Appfuse 2.0.2(Spring-MVC). 2) Downloaded full sources to new project. (mvn appfuse:full-source) 3) Configured database connection properties. 4) I choose SigninController for reproducing a bug. And added a new test to SigninControllerTest. So, I have the following code in SigninControllerTest: package com.mycompany.webapp.controller; import java.util.HashSet; import java.util.Set; import javax.mail.Address; import javax.mail.Message.RecipientType; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.context.SecurityContextHolder; import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.web.servlet.ModelAndView; import org.subethamail.wiser.Wiser; import org.subethamail.wiser.WiserMessage; import com.mycompany.Constants; public class SignupControllerTest extends BaseControllerTestCase { private SignupController c = null; public void setSignupController(SignupController signup) { this.c = signup; } public void testDisplayForm() throws Exception { MockHttpServletRequest request = newGet("/signup.html"); HttpServletResponse response = new MockHttpServletResponse(); ModelAndView mv = c.handleRequest(request, response); assertTrue("returned correct view name", mv.getViewName().equals( "signup")); } public void testSignupUser() throws Exception { MockHttpServletRequest request = newPost("/signup.html"); request.addParameter("username", "self-registered"); request.addParameter("password", "Password1"); request.addParameter("confirmPassword", "Password1"); request.addParameter("firstName", "First"); request.addParameter("lastName", "Last"); request.addParameter("address.city", "Denver"); request.addParameter("address.province", "Colorado"); request.addParameter("address.country", "USA"); request.addParameter("address.postalCode", "80210"); request.addParameter("email", "self-registe...@raibledesigns.com"); request.addParameter("website", "http://raibledesigns.com"); request.addParameter("passwordHint", "Password is one with you."); HttpServletResponse response = new MockHttpServletResponse(); // start SMTP Server Wiser wiser = new Wiser(); wiser.setPort(getSmtpPort()); wiser.start(); ModelAndView mv = c.handleRequest(request, response); Errors errors = (Errors) mv.getModel().get( BindException.MODEL_KEY_PREFIX + "user"); assertTrue("no errors returned in model", errors == null); // verify an account information e-mail was sent wiser.stop(); assertTrue(wiser.getMessages().size() == 1); // verify that success messages are in the request assertNotNull(request.getSession().getAttribute("successMessages")); assertNotNull(request.getSession().getAttribute(Constants.REGISTERED)); SecurityContextHolder.getContext().setAuthentication(null); } private HttpServletRequest buildRequest(int requestNumber) { final String requestNumberString = String.valueOf(requestNumber); MockHttpServletRequest request = newPost("/signup.html"); request.addParameter("username", "self-registered" + requestNumberString); // We should have unique username for every request. request.addParameter("password", "Password1"); request.addParameter("confirmPassword", "Password1"); request.addParameter("firstName", "First"); request.addParameter("lastName", "Last"); request.addParameter("address.city", "Denver"); request.addParameter("address.province", "Colorado"); request.addParameter("address.country", "USA"); request.addParameter("address.postalCode", "80210"); request.addParameter("email", "self-registered" + requestNumberString + "@raibledesigns.com");// We should have unique email for every request. request.addParameter("website", "http://raibledesigns.com"); request.addParameter("passwordHint", "Password is one with you."); return request; } public void testSignupUserEmailMessageInConcurrentRequests() throws Exception { // start SMTP Server Wiser wiser = new Wiser(); wiser.setPort(getSmtpPort()); wiser.start(); // send 200 sign in requests in two separate threads. Thread thread1 = new Thread(new Runnable() { public void run() { for (int i = 0; i < 100; i++) { HttpServletRequest request = buildRequest(i); HttpServletResponse response = new MockHttpServletResponse(); try { ModelAndView mv = c.handleRequest(request, response); Errors errors = (Errors) mv.getModel().get( BindException.MODEL_KEY_PREFIX + "user"); assertTrue("no errors returned in model", errors == null); // verify that success messages are in the request assertNotNull(request.getSession().getAttribute( "successMessages")); assertNotNull(request.getSession().getAttribute( Constants.REGISTERED)); } catch (Exception e) { throw new RuntimeException(e); } } } }); Thread thread2 = new Thread(new Runnable() { public void run() { for (int i = 100; i < 200; i++) { HttpServletRequest request = buildRequest(i); HttpServletResponse response = new MockHttpServletResponse(); try { ModelAndView mv = c.handleRequest(request, response); Errors errors = (Errors) mv.getModel().get( BindException.MODEL_KEY_PREFIX + "user"); assertTrue("no errors returned in model", errors == null); // verify that success messages are in the request assertNotNull(request.getSession().getAttribute( "successMessages")); assertNotNull(request.getSession().getAttribute( Constants.REGISTERED)); } catch (Exception e) { throw new RuntimeException(e); } } } }); thread1.start(); thread2.start(); thread2.join(); thread1.join(); Set<String> emailAddresses = new HashSet<String>(200); for (WiserMessage wiserMessage : wiser.getMessages()){ Address[] addresses = wiserMessage.getMimeMessage().getRecipients(RecipientType.TO); for (Address address : addresses){ emailAddresses.add(address.toString()); } } wiser.stop(); //We should have 200 unique recipients!!!! assertTrue(emailAddresses.size() == 200); *//Fails on this line... because we have concurrent modification of email message object in controller. And some users receive more than one email.* assertTrue(wiser.getMessages().size() == 200); SecurityContextHolder.getContext().setAuthentication(null); } } 5) I run a test and it failed. If you have a very fast CPU you should add the following code to BaseFormController.java or increase requests count from 200 to 1000. *But i have failed test on my PC with 200 request every time*... protected void sendUserMessage(User user, String msg, String url) { if (log.isDebugEnabled()) { log.debug("sending e-mail to user [" + user.getEmail() + "]..."); } message.setTo(user.getFullName() + "<" + user.getEmail() + ">"); Map<String, Serializable> model = new HashMap<String, Serializable>(); model.put("user", user); // TODO: once you figure out how to get the global resource bundle in // WebWork, then figure it out here too. In the meantime, the Username // and Password labels are hard-coded into the template. // model.put("bundle", getTexts()); model.put("message", msg); model.put("applicationURL", url); * /*try{ Thread.sleep(Math.round(Math.random()*500)); }catch (InterruptedException e) { throw new RuntimeException(e); }*/* mailEngine.sendMessage(message, templateName, model); } Marat Kamalov. 2010/1/4 Matt Raible <m...@raibledesigns.com> > Have you been able to reproduce an issue with the current setup in > 2.0.2? If so, please describe the steps so we can verify it is a bug. > > Thanks, > > Matt > > On Mon, Jan 4, 2010 at 10:25 AM, Марат Камалов <mkamalo...@gmail.com> > wrote: > > Hi, > > > > I have found this bug in 1.8.2 and 2.0.2. I haven't seen the last version > > yet. > > > > We have the following bean with scope prototype > > > > <bean id="mailMessage" > > class="org.springframework.mail.SimpleMailMessage" > > scope="prototype"> > > <property name="from" value="${mail.default.from}" /> > > </bean> > > > > Then we inject this bean to every controller where we want to send some > > email message. > > > > <bean id="userFormController" > > class="ru.icl.ios.mzioppd.webapp.controller.UserFormController"> > > <property name="validator" ref="beanValidator"/> > > <property name="formView" value="userForm"/> > > <property name="successView" value="redirect:users.html"/> > > <property name="cancelView" value="redirect:mainMenu.html"/> > > <property name="userManager" ref="userManager"/> > > <property name="roleManager" ref="roleManager"/> > > <property name="mailEngine" ref="mailEngine"/> > > <property name="message" ref="mailMessage"/> > > <property name="templateName" value="accountCreated.vm"/> > > </bean> > > and > > <bean id="passwordHintController" > > class="ru.icl.ios.mzioppd.webapp.controller.PasswordHintController"> > > <property name="userManager" ref="userManager"/> > > <property name="messageSource" ref="messageSource"/> > > <property name="mailEngine" ref="mailEngine"/> > > <property name="message" ref="mailMessage"/> > > </bean> > > > > So, the object mailMessage in the different controllers will be different > > too, becouse bean mailMessage has scope="prototype". But what about > > UserFormController in concurrent requests?? UserFormController is > singleton, > > becouse singleton is default scope in spring with dtd 2.0. And a custom > > default scope isn't defined... > > > > <beans xmlns="http://www.springframework.org/schema/beans" > > xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" > > xsi:schemaLocation="http://www.springframework.org/schema/beans > > http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" > > default-lazy-init="true"> > > > > Have we concurrent modification of mailMessage object??? > > > > I think that we have. Probably, we should use the following code... Or > use > > another approach(synhronize send method or create a new message every > time > > in controller). > > > > <bean id="mailMessage" > > class="org.springframework.mail.SimpleMailMessage" > > scope="request"> > > <property name="from" value="${mail.default.from}" /> > > <aop:scoped-proxy> > > </bean> > > > > P.S > > I'm sorry for my bad English :)) > > > > Marat Kamalov. > > > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: users-unsubscr...@appfuse.dev.java.net > For additional commands, e-mail: users-h...@appfuse.dev.java.net > >