/*
 * Copyright 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.mvc;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import junit.framework.TestCase;

import org.springframework.beans.IndexedTestBean;
import org.springframework.beans.TestBean;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.Validator;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public class FormControllerTests extends TestCase {

	public void testReferenceDataOnForm() throws Exception {
		String formView = "f";
		String successView = "s";

		RefController mc = new RefController();
		mc.setFormView(formView);
		mc.setCommandName("tb");
		mc.setSuccessView(successView);
		mc.refDataCount = 0;

		HttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name", mv.getViewName().equals(formView));

		assertTrue("refDataCount == 1", mc.refDataCount == 1);

		TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
		int[] numbers = (int[]) mv.getModel().get(mc.NUMBERS_ATT);
		assertTrue("model is non null", person != null);
		assertTrue("numbers is non null", numbers != null);
	}

	public void testReferenceDataOnResubmit() throws Exception {
		String formView = "f";
		String successView = "s";

		RefController mc = new RefController();
		mc.setFormView(formView);
		mc.setCommandName("tb");
		mc.setSuccessView(successView);
		mc.refDataCount = 0;

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		request.addParameter("age", "23x");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name", mv.getViewName().equals(formView));
		assertTrue("has errors", mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName()) != null);

		assertTrue("refDataCount == 1", mc.refDataCount == 1);

		TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
		int[] numbers = (int[]) mv.getModel().get(mc.NUMBERS_ATT);
		assertTrue("model is non null", person != null);
		assertTrue("numbers is non null", numbers != null);
	}

	public void testForm() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);

		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
		request.addParameter("name", "rod");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name", mv.getViewName().equals(formView));

		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);
		assertTrue("bean age default ok", person.getAge() == TestController.DEFAULT_AGE);
		assertTrue("name not set", person.getName() == null);
	}

	public void testBindOnNewForm() throws Exception {
		String formView = "f";
		String successView = "s";
		final Integer someNumber = new Integer(12);

		TestController mc = new TestController() {
			protected void onBindOnNewForm(HttpServletRequest request, Object command) throws Exception {
				TestBean testBean = (TestBean)command;
				testBean.setSomeNumber(new Integer(12));
			}
		};
		mc.setFormView(formView);
		mc.setSuccessView(successView);
		mc.setBindOnNewForm(true);

		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/welcome.html");
		request.addParameter("name", "rod");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertEquals("returned correct view name", formView, mv.getViewName());

		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		assertNotNull("model is non null", person);
		assertEquals("bean age default ok", person.getAge(), TestController.DEFAULT_AGE);
		assertEquals("name set", "rod", person.getName());
		assertEquals("Property [someNumber] not set in onBindOnNewForm callback", someNumber, person.getSomeNumber());
	}

	public void testSubmitWithoutErrors() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);

		String name = "Rod";
		int age = 32;

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		request.addParameter("name", name);
		request.addParameter("age", "" + age);
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);

		assertEquals("returned correct view name", successView, mv.getViewName());
		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);
		assertTrue("errors is non null", errors != null);
		assertTrue("bean name bound ok", person.getName().equals(name));
		assertTrue("bean age bound ok", person.getAge() == age);
	}

	public void testSubmitWithoutValidation() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);

		String name = "Rod";
		int age = 32;

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		request.addParameter("name", name);
		request.addParameter("age", "" + age);
		request.addParameter("formChange", "true");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);

		assertEquals("returned correct view name", formView, mv.getViewName());
		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);
		assertTrue("errors is non null", errors != null);
		assertTrue("bean name bound ok", person.getName().equals(name));
		assertTrue("bean age bound ok", person.getAge() == age);
	}

	public void testSubmitWithCustomOnSubmit() throws Exception {
		String formView = "f";

		TestControllerWithCustomOnSubmit mc = new TestControllerWithCustomOnSubmit();
		mc.setFormView(formView);

		String name = "Rod";
		int age = 32;

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		request.addParameter("name", name);
		request.addParameter("age", "" + age);
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertEquals("returned correct view name", "mySuccess", mv.getViewName());
		assertTrue("no model", mv.getModel().isEmpty());
	}

	public void testSubmitPassedByValidator() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);
		mc.setValidator(new TestValidator());

		String name = "Roderick Johnson";
		int age = 32;

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		request.addParameter("name", name);
		request.addParameter("age", "" + age);
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name: expected '" + successView + "', not '" + mv.getViewName() + "'",
				mv.getViewName().equals(successView));

		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);
		assertTrue("bean name bound ok", person.getName().equals(name));
		assertTrue("bean age bound ok", person.getAge() == age);
	}

	public void testSubmit1Mismatch() throws Exception {
		String formView = "fred";
		String successView = "tony";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);

		String name = "Rod";
		String age = "xxx";

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
		request.addParameter("name", name);
		request.addParameter("age", "" + age);
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
				mv.getViewName().equals(formView));

		TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
		assertTrue("model is non null", person != null);
		assertTrue("bean name bound ok", person.getName().equals(name));
		assertTrue("bean age is default", person.getAge() == TestController.DEFAULT_AGE);
		Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
		assertTrue("errors returned in model", errors != null);
		assertTrue("One error", errors.getErrorCount() == 1);
		FieldError fe = errors.getFieldError("age");
		assertTrue("Saved invalid value", fe.getRejectedValue().equals(age));
		assertTrue("Correct field", fe.getField().equals("age"));
	}

	public void testSubmit1Mismatch1Invalidated() throws Exception {
		String formView = "fred";
		String successView = "tony";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);
		mc.setValidators(new Validator[] {new TestValidator(), new TestValidator2()});

		String name = "Rod";
		// will be rejected
		String age = "xxx";

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
		request.addParameter("name", name);
		request.addParameter("age", "" + age);
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
		mv.getViewName().equals(formView));

		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);

		// yes, but it was rejected after binding by the validator
		assertTrue("bean name bound ok", person.getName().equals(name));
		assertTrue("bean age is default", person.getAge() == TestController.DEFAULT_AGE);
		Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
		assertTrue("errors returned in model", errors != null);
		assertTrue("3 errors", errors.getErrorCount() == 3);
		FieldError fe = errors.getFieldError("age");
		assertTrue("Saved invalid value", fe.getRejectedValue().equals(age));
		assertTrue("Correct field", fe.getField().equals("age"));

		// raised by first validator
		fe = errors.getFieldError("name");
		assertTrue("Saved invalid value", fe.getRejectedValue().equals(name));
		assertTrue("Correct field", fe.getField().equals("name"));
		assertTrue("Correct validation code: expected '" +TestValidator.TOOSHORT + "', not '"  +
				fe.getCode() + "'", fe.getCode().equals(TestValidator.TOOSHORT));

		// raised by second validator
		ObjectError oe = errors.getGlobalError();
		assertEquals("test", oe.getCode());
		assertEquals("testmessage", oe.getDefaultMessage());
	}

	public void testSessionController() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);
		mc.setSessionForm(true);

		// first request: GET form
		HttpServletRequest request1 = new MockHttpServletRequest("GET", "/welcome.html");
		HttpServletResponse response1 = new MockHttpServletResponse();
		ModelAndView mv1 = mc.handleRequest(request1, response1);
		assertTrue("returned correct view name", mv1.getViewName().equals(formView));
		TestBean person = (TestBean) mv1.getModel().get(TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);
		assertTrue("Bean age default ok", person.getAge() == TestController.DEFAULT_AGE);

		// second request, using same session: POST submit
		MockHttpServletRequest request2 = new MockHttpServletRequest("POST", "/welcome.html");
		request2.setSession(request1.getSession(false));
		HttpServletResponse response2 = new MockHttpServletResponse();
		ModelAndView mv2 = mc.handleRequest(request2, response2);
		assertTrue("returned correct view name", mv2.getViewName().equals(successView));
		TestBean person2 = (TestBean) mv2.getModel().get(TestController.BEAN_NAME);
		assertTrue("model is same object", person == person2);
	}

	public void testDefaultInvalidSubmit() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController();
		mc.setFormView(formView);
		mc.setSuccessView(successView);
		mc.setSessionForm(true);

		// invalid request: POST submit
		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name", mv.getViewName().equals(successView));
		TestBean person = (TestBean) mv.getModel().get(TestController.BEAN_NAME);
		assertTrue("model is non null", person != null);
	}

	public void testSpecialInvalidSubmit() throws Exception {
		String formView = "f";
		String successView = "s";

		TestController mc = new TestController() {
			protected ModelAndView handleInvalidSubmit(HttpServletRequest request, HttpServletResponse response)
				throws ServletException, IOException {
				throw new ServletException("invalid submit");
			}
		};
		mc.setFormView(formView);
		mc.setSuccessView(successView);
		mc.setSessionForm(true);

		// invalid request: POST submit
		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/welcome.html");
		HttpServletResponse response = new MockHttpServletResponse();
		try {
			mc.handleRequest(request, response);
			fail("Should have thrown ServletException");
		}
		catch (ServletException ex) {
			// expected
		}
	}

	public void testSubmitWithIndexedProperties() throws Exception {
		String formView = "fred";
		String successView = "tony";

		SimpleFormController mc = new SimpleFormController();
		mc.setCommandClass(IndexedTestBean.class);
		mc.setFormView(formView);
		mc.setSuccessView(successView);

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
		request.addParameter("array[0].name", "name3");
		request.addParameter("array[1].age", "name2");
		request.addParameter("list[0].name", "name1");
		request.addParameter("list[1].age", "name0");
		request.addParameter("list[2]", "listobj");
		request.addParameter("map[key1]", "mapobj1");
		request.addParameter("map[key3]", "mapobj2");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
		mv.getViewName().equals(formView));

		IndexedTestBean bean = (IndexedTestBean) mv.getModel().get(mc.getCommandName());
		assertTrue("model is non null", bean != null);
		assertEquals("name3", bean.getArray()[0].getName());
		assertEquals("name1", ((TestBean) bean.getList().get(0)).getName());
		Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
		assertTrue("errors returned in model", errors != null);
		assertTrue("2 errors", errors.getErrorCount() == 2);
		FieldError fe1 = errors.getFieldError("array[1].age");
		assertTrue("Saved invalid value", fe1.getRejectedValue().equals("name2"));
		assertTrue("Correct field", fe1.getField().equals("array[1].age"));
		FieldError fe2 = errors.getFieldError("list[1].age");
		assertTrue("Saved invalid value", fe2.getRejectedValue().equals("name0"));
		assertTrue("Correct field", fe2.getField().equals("list[1].age"));

		assertEquals("listobj", bean.getList().get(2));
		assertEquals("mapobj1", bean.getMap().get("key1"));
		assertEquals("mapobj2", bean.getMap().get("key3"));
	}

	public void testFormChangeRequest() throws Exception {
		String formView = "fred";
		String successView = "tony";
	final Float myFloat = new Float("123.45");

		TestController mc = new TestController() {
			protected boolean isFormChangeRequest(HttpServletRequest request) {
				return (request.getParameter("formChange") != null);
			}

			protected void onFormChange(HttpServletRequest request, HttpServletResponse response, Object command) {
				assertNotNull("Command should not be null", command);
				assertEquals("Incorrect command class", TestBean.class, command.getClass());
				((TestBean)command).setMyFloat(myFloat);
			}
		};
		mc.setFormView(formView);
		mc.setSuccessView(successView);

		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/foo.html");
		request.addParameter("name", "Rod");
		request.addParameter("age", "99");
		request.addParameter("formChange", "true");
		HttpServletResponse response = new MockHttpServletResponse();
		ModelAndView mv = mc.handleRequest(request, response);
		assertTrue("returned correct view name: expected '" + formView + "', not '" + mv.getViewName() + "'",
		mv.getViewName().equals(formView));

		TestBean person = (TestBean) mv.getModel().get(mc.getCommandName());
		assertTrue("model is non null", person != null);
		assertTrue("bean name bound ok", person.getName().equals("Rod"));
		assertTrue("bean age is 99", person.getAge() == 99);
		assertEquals("Command property myFloat not updated in onFormChange", myFloat, person.getMyFloat());
		Errors errors = (Errors) mv.getModel().get(BindException.MODEL_KEY_PREFIX + mc.getCommandName());
		assertTrue("errors returned in model", errors != null);
		assertTrue("No errors", errors.getErrorCount() == 0);
	}

	public void testFormBindingOfNestedBooleans() throws Exception {
		BooleanBindingFormController controller = new BooleanBindingFormController();
		controller.setCommandClass(ListForm.class);
		MockHttpServletRequest req = new MockHttpServletRequest("POST", "/myurl");
		MockHttpServletResponse res = new MockHttpServletResponse();
		req.addParameter("oks[0].ok", "true");
		ModelAndView mav = controller.handleRequest(req, res);
		ListForm form = (ListForm) mav.getModelMap().get("command");
		Boolean ok = ((Ok) form.getOks().get(0)).getOk();
		assertNotNull(ok);
	}

	public void testFormControllerInWebApplicationContext() {
		StaticWebApplicationContext ctx = new StaticWebApplicationContext();
		ctx.setServletContext(new MockServletContext());
		RefController mc = new RefController();
		mc.setApplicationContext(ctx);
		try {
			mc.invokeWebSpecificStuff();
		}
		catch (IllegalStateException ex) {
			fail("Shouldn't have thrown exception: " + ex.getMessage());
		}
	}

	public void testFormControllerInNonWebApplicationContext() {
		StaticApplicationContext ctx = new StaticApplicationContext();
		RefController mc = new RefController();
		mc.setApplicationContext(ctx);
		try {
			mc.invokeWebSpecificStuff();
			fail("Should have thrown IllegalStateException");
		}
		catch (IllegalStateException ex) {
			// expected
		}
	}


	private static class TestValidator implements Validator {

		public static String TOOSHORT = "tooshort";

		public boolean supports(Class clazz) { return true; }

		public void validate(Object obj, Errors errors) {
			TestBean tb = (TestBean) obj;
			if (tb.getName() == null || "".equals(tb.getName()))
				errors.rejectValue("name", "needname", null, "need name");
			else if (tb.getName().length() < 5)
				errors.rejectValue("name", TOOSHORT, null, "need full name");
		}
	}


	private static class TestValidator2 implements Validator {

		public static String TOOSHORT = "tooshort";

		public boolean supports(Class clazz) { return true; }

		public void validate(Object obj, Errors errors) {
			errors.reject("test", "testmessage");
		}
	}


	private static class TestController extends SimpleFormController {

		public static String BEAN_NAME = "person";

		public static int DEFAULT_AGE = 52;

		public TestController() {
			setCommandClass(TestBean.class);
			setCommandName(BEAN_NAME);
		}

		protected Object formBackingObject(HttpServletRequest request) throws ServletException {
			TestBean person = new TestBean();
			person.setAge(DEFAULT_AGE);
			return person;
		}

		protected boolean isFormChangeRequest(HttpServletRequest request) {
			return (request.getParameter("formChange") != null);
		}
	}


	private static class TestControllerWithCustomOnSubmit extends TestController {

		protected ModelAndView onSubmit(Object command) throws Exception {
			return new ModelAndView("mySuccess");
		}
	}


	private static class RefController extends SimpleFormController {

		final String NUMBERS_ATT = "NUMBERS";

		static final int[] NUMBERS = { 1, 2, 3, 4 };

		int refDataCount;

		public RefController() {
			setCommandClass(TestBean.class);
		}

		protected Map referenceData(HttpServletRequest request) {
			++refDataCount;
			Map m = new HashMap();
			m.put(NUMBERS_ATT, NUMBERS);
			return m;
		}

		public void invokeWebSpecificStuff() {
			getTempDir();
		}
	}


	public static class BooleanBindingFormController extends AbstractFormController {

		protected ModelAndView processFormSubmission
				(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
			ModelAndView mav = new ModelAndView();
			mav.addObject("command", command);
			return mav;
		}

		protected ModelAndView showForm(
				HttpServletRequest req, HttpServletResponse resp, BindException err) throws Exception {
			return null;
		}
	}


	public static class Ok {

		private Boolean ok;

		public Boolean getOk () {
			return ok;
		}

		public void setOk(Boolean ok) {
			this.ok = ok;
		}
	}


	public static class ListForm {

		private List oks = new ArrayList();

		public ListForm () {
			for (int index = 0; index < 5; index++) {
				Ok ok = new Ok();
				oks.add(ok);
			}
		}

		public List getOks() {
			return oks;
		}

		public void setOks(List oks) {
			this.oks = oks;
		}
	}

}
