/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* 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.
* #L%
*/
package org.broadleafcommerce.openadmin.web.form.entity;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.broadleafcommerce.openadmin.dto.Entity;
import org.broadleafcommerce.openadmin.server.service.AdminEntityService;
import org.broadleafcommerce.openadmin.server.service.JSCompatibilityHelper;
import org.springframework.stereotype.Component;
import org.springframework.validation.AbstractBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import java.util.List;
import java.util.Map;
/**
* Validator used at the controller level to ensure that an Entity has passed validation from the PersistenceModule or
* CustomPersistenceHandler. This should be used as a final validation step after attempting the save
*
* @author Phillip Verheyden
*/
@Component("blEntityFormValidator")
public class EntityFormValidator {
/**
* Validates the DTO against the map of property errors. Note that this method does not support global validation errors
* from {@link Entity#getGlobalValidationErrors()} as they might not make sense.
*
* @param form
* @param propertyErrors
* @param errors
* @return
*/
public boolean validate(EntityForm form, Map<String, List<String>> propertyErrors, Errors errors) {
return validate(form, propertyErrors, null, errors);
}
/**
* Validates the form DTO against the passed in map of propertyErrors along with global validation errors.
*
* @see #validate(EntityForm, Entity, Errors)
* @param form
* @param propertyErrors
* @param globalErrors
* @param errors
* @return
*/
public boolean validate(EntityForm form, Map<String, List<String>> propertyErrors, List<String> globalErrors, Errors errors) {
if (MapUtils.isEmpty(propertyErrors) && CollectionUtils.isEmpty(globalErrors)) {
return true;
}
if (MapUtils.isNotEmpty(propertyErrors)) {
for (Map.Entry<String, List<String>> pe : propertyErrors.entrySet()) {
for (String errorMessage : pe.getValue()) {
String unserializedFieldName = pe.getKey();
String serializedFieldName = JSCompatibilityHelper.encode(unserializedFieldName);
/**
* Rather than just use errors.rejectValue directly, we need to instantiate the FieldError object ourself
* and add it to the binding result. This is so that we can resolve the actual rejected value ourselves
* rather than rely on Spring to do it for us. If we rely on Spring, Spring will attempt to resolve something
* like fields['defaultSku__name'] immediately (at the time of invoking errors.rejectValue). At that point,
* the field names within the EntityForm are still in their unserialized state, and thus Spring would only
* find fields['defaultSku.name'] and there would be an empty string for the rejected value. Then on the
* frontend, Thymeleaf's th:field processor relies on Spring's BindingResult to provide the value that
* was actually rejected so you can get blank form fields.
*
* With this implementation, we avoid all of those additional problems because we are referencing the
* field that is being rejected along with providing our own method for getting the rejected value
*/
Field field = null;
if (StringUtils.contains(unserializedFieldName, DynamicEntityFormInfo.FIELD_SEPARATOR)) {
String[] fieldInfo = unserializedFieldName.split("\\" + DynamicEntityFormInfo.FIELD_SEPARATOR);
field = form.getDynamicForm(fieldInfo[0]).getFields().get(fieldInfo[1]);
} else if (form.getFields().get(unserializedFieldName) != null) {
field = form.getFields().get(unserializedFieldName);
}
//If the submitted field was a radio button but has validation associated with it, that radio field
//will have never been submitted in the POST and thus will not have ever been attached to the EntityForm.
//We still want to notate the fact that there was a validation failure on that field
String value = (field != null) ? field.getValue() : null;
String[] errorCodes = ((AbstractBindingResult) errors).resolveMessageCodes(errorMessage, serializedFieldName);
FieldError fieldError = new FieldError("entityForm", String.format("fields[%s].value", serializedFieldName),
value, false, errorCodes, null, errorMessage);
((AbstractBindingResult) errors).addError(fieldError);
}
}
}
if (CollectionUtils.isNotEmpty(globalErrors)) {
for (String errorMessage : globalErrors) {
errors.reject(errorMessage, errorMessage);
}
}
return false;
}
/**
* Validates the form DTO against the passed in entity
* @param form the form DTO
* @param entity value obtained after attempting to save via {@link AdminEntityService#updateEntity(EntityForm, String)}
* @return <b>true</b> if <b>entity</b> does not have any validation errors, <b>false</b> otherwise.
*/
public boolean validate(EntityForm form, Entity entity, Errors errors) {
if (entity.isValidationFailure()) {
return validate(form, entity.getPropertyValidationErrors(), entity.getGlobalValidationErrors(), errors);
}
return true;
}
}