/* * #%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; } }