SWF-735 provide additional context to vaidation methods invoked by convention

This commit is contained in:
Scott Andrews
2008-10-15 20:26:51 +00:00
parent 5b9cd000b0
commit 61c405b295
12 changed files with 492 additions and 83 deletions

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2008 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.binding.validation;
import java.security.Principal;
import org.springframework.binding.message.MessageContext;
/**
* A context for validation events.
*
* @author Scott Andrews
* @since 2.0.4
*/
public interface ValidationContext {
/**
* Get the context for recording messages
*/
public MessageContext getMessageContext();
/**
* Get the current user principal
*/
public Principal getUserPrincipal();
/**
* Get the event that triggered validation
*/
public String getUserEvent();
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.faces.webflow;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -28,17 +27,13 @@ import javax.faces.event.ActionListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.expression.Expression;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.message.MessageContextErrors;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.RequestContextHolder;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.validation.ValidationHelper;
/**
* The default {@link ActionListener} implementation to be used with Web Flow.
@@ -104,7 +99,7 @@ public class FlowActionListener implements ActionListener {
RequestContext requestContext = RequestContextHolder.getRequestContext();
Object model = getModelObject(requestContext);
if (shouldValidate(requestContext, model, eventId)) {
validate(requestContext, model);
validate(requestContext, model, eventId);
if (requestContext.getMessageContext().hasErrorMessages()) {
isValid = false;
if (requestContext.getExternalContext().isAjaxRequest()) {
@@ -155,34 +150,8 @@ public class FlowActionListener implements ActionListener {
return true;
}
private void validate(RequestContext requestContext, Object model) {
String validateMethodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId());
Method validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName,
new Class[] { MessageContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { requestContext.getMessageContext() });
}
BeanFactory beanFactory = requestContext.getActiveFlow().getApplicationContext();
if (beanFactory != null) {
String validatorName = getModelExpression(requestContext).getExpressionString() + "Validator";
if (beanFactory.containsBean(validatorName)) {
Object validator = beanFactory.getBean(validatorName);
validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), MessageContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
requestContext.getMessageContext() });
} else {
validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), Errors.class });
if (validateMethod != null) {
String objectName = getModelExpression(requestContext).getExpressionString();
MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(),
objectName, model, null, null);
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, errors });
}
}
}
}
private void validate(RequestContext requestContext, Object model, String eventId) {
new ValidationHelper(model, requestContext, eventId, getModelExpression(requestContext).getExpressionString(),
null, null).validate();
}
}

View File

@@ -17,7 +17,7 @@ import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.validation.ValidationContext;
/**
* A Hotel Booking made by a User.
@@ -189,13 +189,15 @@ public class Booking implements Serializable {
this.amenities = amenities;
}
public void validateEnterBookingDetails(MessageContext context) {
public void validateEnterBookingDetails(ValidationContext context) {
context.getUserEvent();
if (checkinDate.before(today())) {
context.addMessage(new MessageBuilder().error().source("checkinDate").code(
"booking.checkinDate.beforeToday").build());
context.getMessageContext().addMessage(
new MessageBuilder().error().source("checkinDate").code("booking.checkinDate.beforeToday").build());
} else if (checkoutDate.before(checkinDate)) {
context.addMessage(new MessageBuilder().error().source("checkoutDate").code(
"booking.checkoutDate.beforeCheckinDate").build());
context.getMessageContext().addMessage(
new MessageBuilder().error().source("checkoutDate").code("booking.checkoutDate.beforeCheckinDate")
.build());
}
}

View File

@@ -18,7 +18,7 @@ import javax.persistence.TemporalType;
import javax.persistence.Transient;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.validation.ValidationContext;
/**
* A Hotel Booking made by a User.
@@ -188,13 +188,14 @@ public class Booking implements Serializable {
this.amenities = amenities;
}
public void validateEnterBookingDetails(MessageContext context) {
public void validateEnterBookingDetails(ValidationContext context) {
if (checkinDate.before(today())) {
context.addMessage(new MessageBuilder().error().source("checkinDate").code(
"booking.checkinDate.beforeToday").build());
context.getMessageContext().addMessage(
new MessageBuilder().error().source("checkinDate").code("booking.checkinDate.beforeToday").build());
} else if (checkoutDate.before(checkinDate)) {
context.addMessage(new MessageBuilder().error().source("checkoutDate").code(
"booking.checkoutDate.beforeCheckinDate").build());
context.getMessageContext().addMessage(
new MessageBuilder().error().source("checkoutDate").code("booking.checkoutDate.beforeCheckinDate")
.build());
}
}

View File

@@ -17,7 +17,6 @@ package org.springframework.webflow.mvc.view;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -26,7 +25,6 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.binding.convert.ConversionService;
import org.springframework.binding.expression.EvaluationException;
@@ -41,13 +39,8 @@ import org.springframework.binding.mapping.MappingResultsCriteria;
import org.springframework.binding.mapping.impl.DefaultMapper;
import org.springframework.binding.mapping.impl.DefaultMapping;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.message.MessageContextErrors;
import org.springframework.binding.message.MessageResolver;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.util.WebUtils;
import org.springframework.webflow.core.collection.ParameterMap;
import org.springframework.webflow.definition.TransitionDefinition;
@@ -57,6 +50,7 @@ import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.FlowExecutionKey;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.View;
import org.springframework.webflow.validation.ValidationHelper;
/**
* Base view implementation for the Spring Web MVC Servlet and Spring Web MVC Portlet frameworks.
@@ -414,34 +408,8 @@ public abstract class AbstractMvcView implements View {
}
private void validate(Object model) {
String validateMethodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId());
Method validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName,
new Class[] { MessageContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { requestContext.getMessageContext() });
}
BeanFactory beanFactory = requestContext.getActiveFlow().getApplicationContext();
if (beanFactory != null) {
String validatorName = getModelExpression().getExpressionString() + "Validator";
if (beanFactory.containsBean(validatorName)) {
Object validator = beanFactory.getBean(validatorName);
validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), MessageContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
requestContext.getMessageContext() });
} else {
validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), Errors.class });
if (validateMethod != null) {
String objectName = getModelExpression().getExpressionString();
MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(),
objectName, model, expressionParser, mappingResults);
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, errors });
}
}
}
}
new ValidationHelper(model, requestContext, eventId, getModelExpression().getExpressionString(),
expressionParser, mappingResults).validate();
}
private void determineEventId(RequestContext context) {

View File

@@ -0,0 +1,37 @@
package org.springframework.webflow.validation;
import java.security.Principal;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.validation.ValidationContext;
import org.springframework.webflow.execution.RequestContext;
public class DefaultValidationContext implements ValidationContext {
private RequestContext requestContext;
private String eventId;
public DefaultValidationContext(RequestContext requestContext, String eventId) {
this.requestContext = requestContext;
this.eventId = eventId;
}
public MessageContext getMessageContext() {
return requestContext.getMessageContext();
}
public String getUserEvent() {
if (eventId != null) {
return eventId;
} else if (requestContext.getCurrentEvent() != null) {
return requestContext.getCurrentEvent().getId();
} else {
return null;
}
}
public Principal getUserPrincipal() {
return requestContext.getExternalContext().getCurrentUser();
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright 2008 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.webflow.validation;
import java.lang.reflect.Method;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.binding.expression.ExpressionParser;
import org.springframework.binding.mapping.MappingResults;
import org.springframework.binding.message.MessageContext;
import org.springframework.binding.message.MessageContextErrors;
import org.springframework.binding.validation.ValidationContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.webflow.execution.RequestContext;
/**
* A helper class the encapsulates conventions to invoke validation logic.
*
* @author Scott Andrews
*/
public class ValidationHelper {
private final Object model;
private final RequestContext requestContext;
private final String eventId;
private final String modelName;
private final ExpressionParser expressionParser;
private final MappingResults mappingResults;
/**
* Create a throwaway validation helper object. Validation is invoked by the {@link #validate()} method.
* <p>
* Validation methods invoked include a validation method on the model object or a validator bean. The method name
* on either object is in the form of validate[viewStateId]. A ValidationContext is passed in the method signature
* along with the model object for validator beans. A MessageContext can be substituted for the ValidationContext.
* <p>
* For example: <code>model.validateEnterBookingDetails(VaticationContext)</code> or
* <code>context.getBean("modelValidator").validateEnterBookingDetails(model, VaticationContext)</code>
*
* @param model the object to validate
* @param requestContext the context for the request
* @param eventId the event triggering validation
* @param modelName the name of the model object
* @param expressionParser the expression parser
* @param mappingResults object mapping results
*/
public ValidationHelper(Object model, RequestContext requestContext, String eventId, String modelName,
ExpressionParser expressionParser, MappingResults mappingResults) {
this.model = model;
this.requestContext = requestContext;
this.eventId = eventId;
this.modelName = modelName;
this.expressionParser = expressionParser;
this.mappingResults = mappingResults;
}
/**
* Invoke the validators available by convention.
*/
public void validate() {
String validateMethodName = "validate" + StringUtils.capitalize(requestContext.getCurrentState().getId());
validateWithContext(model, validateMethodName);
BeanFactory beanFactory = requestContext.getActiveFlow().getApplicationContext();
if (beanFactory != null) {
String validatorName = modelName + "Validator";
if (beanFactory.containsBean(validatorName)) {
Object validator = beanFactory.getBean(validatorName);
if (!validateWithModelAndContext(model, validator, validateMethodName)) {
validateWithModelAndErrors(model, validator, validateMethodName);
}
}
}
}
/*
* Invoke validate method on the model for the current state passing either a MessageContext or ValidationContext.
* Preference is given to the ValidationContext method.
*/
private boolean validateWithContext(Object model, String validateMethodName) {
boolean validationInvoked = false;
Method validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName,
new Class[] { ValidationContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, model, new Object[] { new DefaultValidationContext(
requestContext, eventId) });
validationInvoked = true;
} else {
validateMethod = ReflectionUtils.findMethod(model.getClass(), validateMethodName,
new Class[] { MessageContext.class });
if (validateMethod != null) {
ReflectionUtils
.invokeMethod(validateMethod, model, new Object[] { requestContext.getMessageContext() });
validationInvoked = true;
}
}
return validationInvoked;
}
/*
* Invoke validate method on a distinct validator providing the model to validate and either a MessageContext or
* ValidationContext. Preference is given to the ValidationContext method.
*/
private boolean validateWithModelAndContext(Object model, Object validator, String validateMethodName) {
boolean validationInvoked = false;
Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), ValidationContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
new DefaultValidationContext(requestContext, eventId) });
validationInvoked = true;
} else {
validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), MessageContext.class });
if (validateMethod != null) {
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model,
requestContext.getMessageContext() });
validationInvoked = true;
}
}
return validationInvoked;
}
/*
* Invoke validate method on a distinct validator providing the model to validate and an Errors object.
*/
private boolean validateWithModelAndErrors(Object model, Object validator, String validateMethodName) {
boolean validationInvoked = false;
Method validateMethod = ReflectionUtils.findMethod(validator.getClass(), validateMethodName, new Class[] {
model.getClass(), Errors.class });
if (validateMethod != null) {
MessageContextErrors errors = new MessageContextErrors(requestContext.getMessageContext(), modelName,
model, expressionParser, mappingResults);
ReflectionUtils.invokeMethod(validateMethod, validator, new Object[] { model, errors });
validationInvoked = true;
}
return validationInvoked;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2008 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.webflow.validation;
import org.springframework.validation.Errors;
/**
* Support class for {@link ValidationHelperTest}
*/
public class StubModelErrors {
public void validateMockState(Object model, Errors errors) {
errors.rejectValue("errors-external", "", "");
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2008 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.webflow.validation;
import org.springframework.validation.Errors;
/**
* Support class for {@link ValidationHelperTest}
*/
public class StubModelErrorsOverridden extends StubModelValidationContext {
public void validateMockState(Object model, Errors errors) {
errors.rejectValue("errors-context", "", "");
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2008 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.webflow.validation;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
/**
* Support class for {@link ValidationHelperTest}
*/
public class StubModelMessageContext {
public void validateMockState(MessageContext context) {
context.addMessage(new MessageBuilder().source("messagecontext").defaultText("").build());
}
public void validateMockState(Object model, MessageContext context) {
context.addMessage(new MessageBuilder().source("messagecontext-external").defaultText("").build());
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2008 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.webflow.validation;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.validation.ValidationContext;
/**
* Support class for {@link ValidationHelperTest}
*/
public class StubModelValidationContext {
public void validateMockState(ValidationContext context) {
context.getMessageContext()
.addMessage(new MessageBuilder().source("validationcontext").defaultText("").build());
}
public void validateMockState(Object model, ValidationContext context) {
context.getMessageContext().addMessage(
new MessageBuilder().source("validationcontext-external").defaultText("").build());
}
}

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2008 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.webflow.validation;
import junit.framework.TestCase;
import org.springframework.binding.message.MessageContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.test.MockRequestContext;
/**
* Unit test for {@link ValidationHelper}
*/
public class ValidationHelperTests extends TestCase {
private MockRequestContext requestContext;
private String eventId;
private String modelName;
protected void setUp() throws Exception {
requestContext = new MockRequestContext();
eventId = "userEvent";
modelName = "model";
}
public void testValidateWithMessageContext() {
Object model = new StubModelMessageContext();
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, null);
helper.validate();
MessageContext messages = requestContext.getMessageContext();
assertEquals(1, messages.getAllMessages().length);
assertEquals(1, messages.getMessagesBySource("messagecontext").length);
assertEquals(0, messages.getMessagesBySource("validationcontext").length);
}
public void testValidateWithValidatioContext() {
Object model = new StubModelValidationContext();
ValidationHelper helper = new ValidationHelper(model, requestContext, eventId, modelName, null, null);
helper.validate();
MessageContext messages = requestContext.getMessageContext();
assertEquals(1, messages.getAllMessages().length);
assertEquals(1, messages.getMessagesBySource("validationcontext").length);
}
public void testValidateWithMessageContextForBeanValidator() {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("modelValidator", StubModelMessageContext.class);
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null, null);
helper.validate();
MessageContext messages = requestContext.getMessageContext();
assertEquals(1, messages.getAllMessages().length);
assertEquals(1, messages.getMessagesBySource("messagecontext-external").length);
}
public void testValidateWithValidationContextForBeanValidator() {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("modelValidator", StubModelValidationContext.class);
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null, null);
helper.validate();
MessageContext messages = requestContext.getMessageContext();
assertEquals(1, messages.getAllMessages().length);
assertEquals(1, messages.getMessagesBySource("validationcontext-external").length);
}
public void testValidateWithErrorsForBeanValidator() {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("modelValidator", StubModelErrors.class);
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null, null);
helper.validate();
MessageContext messages = requestContext.getMessageContext();
assertEquals(1, messages.getAllMessages().length);
assertEquals(1, messages.getMessagesBySource("errors-external").length);
}
public void testValidateWithErrorsForBeanValidatorOverridden() {
StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("modelValidator", StubModelErrorsOverridden.class);
((Flow) requestContext.getActiveFlow()).setApplicationContext(applicationContext);
ValidationHelper helper = new ValidationHelper(new Object(), requestContext, eventId, modelName, null, null);
helper.validate();
MessageContext messages = requestContext.getMessageContext();
assertEquals(1, messages.getAllMessages().length);
assertEquals(1, messages.getMessagesBySource("validationcontext-external").length);
}
}