/* * JBoss, a division of Red Hat * Copyright 2012, Red Hat Middleware, LLC, and individual * contributors as indicated by the @authors tag. See the * copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.webui.form.validator; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Pattern; import org.exoplatform.commons.serialization.api.annotations.Serialized; import org.exoplatform.commons.utils.PrivilegedSystemHelper; import org.exoplatform.portal.pom.config.Utils; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.web.application.CompoundApplicationMessage; import org.exoplatform.webui.exception.MessageException; import org.exoplatform.webui.form.UIFormInput; /** * A user-configurable validator. Several aspects of this validator can be configured via properties in the * configuration.properties file found in the GateIn configuration directory (${gatein.conf.dir}). The validator supports * several configurations that can be activated when a validator instance is created by passing it the name of the configuration * to be activated. A configuration is created by adding an entry in configuration.properties using the {@link #KEY_PREFIX} * prefix followed by the name of the configuration, a period '.' and the name of the validation aspect to modify. * <p/> * Currently supported validation aspects, where {configuration} is a configuration's name: * <ul> * <li>{@link #KEY_PREFIX}{configuration}.length.min: the minimal length of the validated field</li> * <li>{@link #KEY_PREFIX}{configuration}.length.max: the maximal length of the validated field</li> * <li>{@link #KEY_PREFIX}{configuration}.regexp: the regular expression to which the validated field must conform</li> * <li>{@link #KEY_PREFIX}{configuration}.format.message: a message to display providing details about the format of values the * regular expression allows in case the validated field doesn't conform to it</li> * </ul> * * Currently used configurations in the code are defined by the {@link #USERNAME} and {@link #GROUPMEMBERSHIP} names. * * @author <a href="mailto:chris.laprun@jboss.com">Chris Laprun</a> */ @Serialized public class UserConfigurableValidator extends MultipleConditionsValidator { protected static Log log = ExoLogger.getLogger(UserConfigurableValidator.class); public static final String USERNAME = "username"; public static final String GROUPMEMBERSHIP = "groupmembership"; public static final String PAGE_NAME = "pagename"; public static final String EMAIL = "email"; public static final String DEFAULT_LOCALIZATION_KEY = "ExpressionValidator.msg.value-invalid"; /** * Note that this regular expression should actually validate comma-separated usernames. This is not the case as some * constraints (consecutive symbols for examples) are not taken into account. */ public static final String GROUP_MEMBERSHIP_VALIDATION_REGEX = "^(\\p{Lower}[\\p{Lower}\\d\\._]+)(\\s*,\\s*(\\p{Lower}[\\p{Lower}\\d\\._]+))*$"; public static final String GROUP_MEMBERSHIP_LOCALIZATION_KEY = "UIGroupMembershipForm.msg.Invalid-char"; public static final String EMAIL_VALIDATION_REGEX = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; private static Map<String, ValidatorConfiguration> configurations; public static final String KEY_PREFIX = "gatein.validators."; private static Map<String, ValidatorConfiguration> getConfigurations() { if (configurations == null) { synchronized (UserConfigurableValidator.class) { if (configurations == null) { configurations = new HashMap<String, ValidatorConfiguration>(3); String gateinConfDir = System.getProperty("gatein.conf.dir"); File conf = new File(gateinConfDir, "configuration.properties"); Properties properties = null; if (conf.exists()) { try { properties = new Properties(); properties.load(new FileInputStream(conf)); } catch (IOException e) { log.info(e.getLocalizedMessage()); log.debug(e); } } else { properties = PrivilegedSystemHelper.getProperties(); } if (properties != null) { int length = KEY_PREFIX.length(); for (Object objectKey : properties.keySet()) { String key = (String) objectKey; if (key.startsWith(KEY_PREFIX)) { // extract property key String propertyKey = key.substring(length, key.indexOf('.', length)); if (!configurations.containsKey(propertyKey)) { configurations.put(propertyKey, new ValidatorConfiguration(propertyKey, properties)); } } } } } } } return configurations; } private final String validatorName; private final String localizationKey; // needed by @Serialized public UserConfigurableValidator() { this(USERNAME, DEFAULT_LOCALIZATION_KEY); } public UserConfigurableValidator(String configurationName, String messageLocalizationKey) { this(configurationName, messageLocalizationKey, true); } public UserConfigurableValidator(String configurationName, String messageLocalizationKey, Boolean exceptionOnMissingMandatory) { this.exceptionOnMissingMandatory = exceptionOnMissingMandatory; this.trimValue = true; localizationKey = messageLocalizationKey != null ? messageLocalizationKey : DEFAULT_LOCALIZATION_KEY; this.validatorName = configurationName != null ? configurationName : USERNAME; } public UserConfigurableValidator(String configurationName) { this(configurationName, DEFAULT_LOCALIZATION_KEY); } @Override public void validate(UIFormInput uiInput) throws Exception { if (exceptionOnMissingMandatory) { super.validate(uiInput); } else { String label = getLabelFor(uiInput); CompoundApplicationMessage messages = new CompoundApplicationMessage(); validate((String) uiInput.getValue(), label, messages, uiInput); if (!messages.isEmpty()) { throw new MessageException(messages); } } } @Override protected void validate(String value, String label, CompoundApplicationMessage messages, UIFormInput uiInput) { ValidatorConfiguration configuration = getConfigurations().get(validatorName); if (value == null) { value = ""; } if (configuration == null) { // we don't have a user-configured validator for this validator name if (USERNAME.equals(validatorName)) { // if the validator name is USERNAME constant, we have a username to validate with the original, non-configured // behavior UsernameValidator.validate(value, label, messages, UsernameValidator.DEFAULT_MIN_LENGTH, UsernameValidator.DEFAULT_MAX_LENGTH); } else if (EMAIL.equals(validatorName)) { // if the validator name is the EMAIL constant, we use the default e-mail validator EmailAddressValidator.validate(value, label, messages); } else if (GROUPMEMBERSHIP.equals(validatorName)) { // else, we assume that we need to validate a group membership, replicating original behavior if (!Pattern.matches(GROUP_MEMBERSHIP_VALIDATION_REGEX, value)) { messages.addMessage(localizationKey, new Object[] { label }); } } else if (PAGE_NAME.equals(validatorName)) { ConfigurableIdentifierValidator.validate(value, label, messages, uiInput, ConfigurableIdentifierValidator.DEFAULT_MIN_LENGTH, ConfigurableIdentifierValidator.DEFAULT_MAX_LENGTH); } } else { // otherwise, use the user-provided configuration if (value.length() < configuration.minLength || value.length() > configuration.maxLength) { messages.addMessage("StringLengthValidator.msg.length-invalid", new Object[] { label, configuration.minLength.toString(), configuration.maxLength.toString() }); } if (!Pattern.matches(configuration.pattern, value)) { messages.addMessage(localizationKey, new Object[] { label, configuration.formatMessage }); } } } /** * Provides a way to query what are the currently known policies from the configuration files. For instance, * if an entry exists as gatein.validators.mycompanypasspolicy.regexp , then there's a configuration called * "mycompanypasspolicy", and this will be returned by this method. * * Note: the built-in configurations are <b>not</b> returned by this method, only the custom-provided ones. * * @return a set containing all known configuration key names. */ public static final Set<String> getConfigurationNames() { return Collections.unmodifiableSet(getConfigurations().keySet()); } private static class ValidatorConfiguration { private Integer minLength; private Integer maxLength; private String pattern; private String formatMessage; private ValidatorConfiguration(String propertyKey, Properties properties) { // used to assign backward compatible default values String prefixedKey = KEY_PREFIX + propertyKey; String minProperty = properties.getProperty(prefixedKey + ".length.min"); String maxProperty = properties.getProperty(prefixedKey + ".length.max"); if (USERNAME.equals(propertyKey)) { minLength = minProperty != null ? Integer.valueOf(minProperty) : UsernameValidator.DEFAULT_MIN_LENGTH; maxLength = maxProperty != null ? Integer.valueOf(maxProperty) : UsernameValidator.DEFAULT_MAX_LENGTH; pattern = properties.getProperty(prefixedKey + ".regexp", Utils.USER_NAME_VALIDATOR_REGEX); } else if (PAGE_NAME.equals(propertyKey)) { minLength = minProperty != null ? Integer.valueOf(minProperty) : ConfigurableIdentifierValidator.DEFAULT_MIN_LENGTH; maxLength = maxProperty != null ? Integer.valueOf(maxProperty) : ConfigurableIdentifierValidator.DEFAULT_MAX_LENGTH; pattern = properties.getProperty(prefixedKey + ".regexp", ConfigurableIdentifierValidator.IDENTIFER_VALIDATOR_REGEX); } else { minLength = minProperty != null ? Integer.valueOf(minProperty) : 0; maxLength = maxProperty != null ? Integer.valueOf(maxProperty) : Integer.MAX_VALUE; pattern = properties.getProperty(prefixedKey + ".regexp", Utils.USER_NAME_VALIDATOR_REGEX); } formatMessage = properties.getProperty(prefixedKey + ".format.message", pattern); } } }