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", "[email protected]");
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 <[email protected]>
> 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, Марат Камалов <[email protected]>
> 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: [email protected]
> For additional commands, e-mail: [email protected]
>
>