/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 net.formio; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import net.formio.binding.DefaultBeanExtractor; import net.formio.binding.PropertyMethodRegex; import net.formio.internal.FormUtils; import net.formio.render.RenderUtils; import net.formio.validation.ConstraintViolationMessage; import net.formio.validation.Severity; import net.formio.validation.Validator; import net.formio.validation.constraints.NotEmpty; import net.formio.validation.validators.RequiredValidator; /** * Common implementations of {@link FormElement}'s methods. * @author Radek Beran */ public abstract class AbstractFormElement<T> implements FormElement<T> { // public because of introspection required by some template frameworks, constructors are not public final FormMapping<?> parent; final String propertyName; final List<Validator<T>> validators; AbstractFormElement(FormMapping<?> parent, String propertyName, List<Validator<T>> validators) { this.parent = parent; this.propertyName = propertyName; if (validators == null) { throw new IllegalArgumentException("validators cannot be null"); } this.validators = validators; } /** * Returns validation messages of form element. * @return */ @Override public List<ConstraintViolationMessage> getValidationMessages() { List<ConstraintViolationMessage> msgs = null; if (getValidationResult() != null) { msgs = getValidationResult().getFieldMessages().get(getName()); } if (msgs == null) { msgs = new ArrayList<ConstraintViolationMessage>(); } return msgs; } /** * Returns true if given form element is visible. * @return */ @Override public boolean isVisible() { boolean visible = getProperties().isVisible(); if (getParent() != null && !getParent().isVisible()) { visible = false; } return visible; } /** * Returns true if given form element is enabled. * @return */ @Override public boolean isEnabled() { boolean enabled = getProperties().isEnabled(); if (getParent() != null && !getParent().isEnabled()) { enabled = false; } return enabled; } /** * Returns true if given form element is readonly. * @return */ @Override public boolean isReadonly() { boolean readonly = getProperties().isReadonly(); if (getParent() != null && getParent().isReadonly()) { readonly = true; } return readonly; } @Override public List<Validator<T>> getValidators() { return validators; } @Override public boolean isRequired() { Class<?> parentDataClass = null; if (parent != null) { parentDataClass = parent.getDataClass(); } return isRequired(parentDataClass); } protected boolean isRequired(Class<?> parentDataClass) { if (getPropertyName().equals(Forms.AUTH_TOKEN_FIELD_NAME)) { return false; // handled specially } boolean required = false; if (parentDataClass != null) { final String propName = getPropertyName(); boolean accessorFound = false; try { final Field fld = parentDataClass.getDeclaredField(propName); if (fld != null) { if (isRequiredByAnnotations(fld.getAnnotations(), 0)) { required = true; } accessorFound = true; } } catch (NoSuchFieldException ex) { // Accessor not found yet } if (!required) { // Try to inspect annotations on getter final Method[] objMethods = parentDataClass.getMethods(); Config conf = getConfig(); final PropertyMethodRegex accessorRegex = conf != null ? conf.getAccessorRegex() : DefaultBeanExtractor.DEFAULT_ACCESSOR_REGEX; for (Method objMethod : objMethods) { if (objMethod.getName().equals("getClass")) continue; if (accessorRegex.matchesPropertyMethod(objMethod.getName(), propName)) { if (isRequiredByAnnotations(objMethod.getAnnotations(), 0)) { required = true; } accessorFound = true; break; } } } if (!accessorFound) { // This also checks if the accessor for property exists and throws exception in time of form definition // building if not. throw new ReflectionException("Error while checking if property " + getPropertyName() + " of class " + parentDataClass.getName() + " is required, the corresponding field or getter does not exist"); } } if (validators != null && validators.contains(RequiredValidator.getInstance())) { required = true; } return required; } private boolean isRequiredByAnnotations(Annotation[] annots, int level) { boolean required = false; if (level < 2) { if (annots != null) { for (Annotation ann : annots) { if (ann instanceof Size) { Size s = (Size) ann; if (s.min() > 0) { required = true; break; } } else if (ann instanceof NotNull) { required = true; break; } else if (ann instanceof NotEmpty) { required = true; break; } else { if (isRequiredByAnnotations(ann.annotationType().getAnnotations(), level + 1)) { required = true; break; } } } } } return required; } @Override public <U> FormElement<U> findElement(Class<U> cls, String name) { FormElement<U> foundEl = null; if (name != null && !name.isEmpty()) { if (this.getName().equals(name)) { foundEl = (FormElement<U>)this; } if (foundEl == null) { FormMapping<?> root = getRoot(); if (root != null) { foundEl = FormUtils.findElementRecursive(cls, name, root); } } } return foundEl; } @Override public <U> FormElement<U> findElement(String name) { return (FormElement<U>)findElement(Object.class, name); } @Override public <U> FormElement<U> requireElement(String name) { return (FormElement<U>)requireElements(name).get(0); } @Override public List<FormElement<?>> requireElements(String ... names) { List<FormElement<?>> foundElems = new ArrayList<FormElement<?>>(); if (names != null) { for (String name: names) { FormElement<?> el = findElement(name); if (el == null) { throw new IllegalStateException("Form element with name " + name + " was not found"); } foundElems.add(el); } } return Collections.unmodifiableList(foundElems); } @Override public FormMapping<?> getRoot() { FormMapping<?> root = null; if (this instanceof FormMapping<?>) { root = (FormMapping<?>)this; } else { root = this.getParent(); } if (root != null) { while (root.getParent() != null) { root = root.getParent(); } } return root; } @Override public String getElementId() { return RenderUtils.getElementIdForName(getName()); } @Override public String getElementPlaceholderId() { Config c = getConfig(); String sep = c != null ? c.getPathSeparator() : Config.DEFAULT_PATH_SEP; return getElementPlaceholderId(getName(), sep); } public static String getElementPlaceholderId(String elementName, String pathSep) { return "placeholder" + pathSep + elementName; } @Override public String getElementIdWithIndex(int index) { Config c = getConfig(); String sep = c != null ? c.getPathSeparator() : Config.DEFAULT_PATH_SEP; return getElementId() + sep + index; } @Override public String getMaxSeverityClass() { Severity maxSeverity = Severity.max(getValidationMessages()); return maxSeverity != null ? maxSeverity.getStyleClass() : ""; } }