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
>
>

Reply via email to