The solution should cover a fair number of regular use cases for input components in any UIData based component. It provides a usability benefit by using the standard JSF validation mechanism, so that inputs containing duplicate values are individually highlighted as errors to users, and can have <h:message/> attached per input to provide contextual help for the error.
But it only validates values passed during a single request, so it won't work against duplicate values in pages of a datatable that are not being processed in a given request/response cycle.
Here's what it looks like used in a facelet:
Code: Select all
<h:dataTable var="emailEntry" value="#{bean.emailList}">
<h:column headerText="#{i18n['EMAIL_ADDRESSES_HEAD']}">
<p:inputText id="email" value="#{emailEntry}">
<f:validator validatorId="noDuplicates"/>
<f:attribute name="duplicatesMessage" value="#{i18n['ERR_DUPLICATE_EMAIL']}"/>
</p:inputText>
<p:message for="email"/>
</h:column>
</h:dataTable>
Code: Select all
public abstract class MessageAttributeAwareValidator implements Validator {
private final String messageAttributeName;
private final String defaultMessage;
protected MessageAttributeAwareValidator(final String messageAttributeName, final String defaultMessage) {
this.messageAttributeName = messageAttributeName;
this.defaultMessage = defaultMessage;
}
protected void failValidation(final UIComponent component) {
String message = (String) component.getAttributes().get(messageAttributeName);
message = message == null ? defaultMessage : message;
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message));
}
}
Code: Select all
@FacesValidator(value = "noDuplicates")
public class NoDuplicatesValidator extends MessageAttributeAwareValidator {
public static final String REQUEST_SCOPE_KEY = "_NoDuplicatesValidatorValuesMap";
public static final String DUPLICATES_MESSAGE = "duplicatesMessage";
public static final String DEFAULT_MESSAGE = "Duplicate Value";
public NoDuplicatesValidator() {
super(DUPLICATES_MESSAGE, DEFAULT_MESSAGE);
}
@Override
public void validate(final FacesContext context, final UIComponent component, final Object value)
throws ValidatorException {
final UIInput input = (UIInput) component;
final Set<Object> componentValues = getComponentValuesSet(context, input);
if (componentValues.contains(value)) {
failValidation(component);
}
componentValues.add(value);
}
private static Set<Object> getComponentValuesSet(final FacesContext context, final UIInput input) {
final Map<String, Object> requestScope = getInitialisedRequestScope(context);
final Map<UIInput, Set<Object>> valuesMap =
(Map<UIInput, Set<Object>>) requestScope.get(REQUEST_SCOPE_KEY);
if (null==valuesMap.get(input)) {
valuesMap.put(input, new HashSet<Object>());
}
return valuesMap.get(input);
}
private static Map<String, Object> getInitialisedRequestScope(final FacesContext context) {
final Map<String, Object> requestScope = context.getExternalContext().getRequestMap();
if (!requestScope.containsKey(REQUEST_SCOPE_KEY)) {
requestScope.put(REQUEST_SCOPE_KEY, new HashMap<UIInput, Set<Object>>());
}
return requestScope;
}
}