Validators
available hoping that they will successfully pass the manual tests. Well, some of them did :-) except from one: The 'Email' Validator.If you look at the sources of Tapestry (5.1.0.5) email validator, you may spot the problem.
//...
private static final String ATOM = "[^\\x00-\\x1F^\\(^\\)^\\<^\\>^\\@^\\,^\\;^\\:^\\\\^\\\"^\\.^\\[^\\]^\\s]";
private static final String DOMAIN = "(" + ATOM + "+(\\." + ATOM + "+)*";
private static final String IP_DOMAIN = "\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\]";
private static final Pattern PATTERN = Pattern
.compile("^" + ATOM + "+(\\." + ATOM + "+)*@" + DOMAIN + "|" + IP_DOMAIN + ")$", Pattern.CASE_INSENSITIVE);
//...
public void validate(Field field, Void constraintValue, MessageFormatter formatter, String value)
throws ValidationException
{
if (!PATTERN.matcher(value).matches()) throw new ValidationException(buildMessage(formatter, field));
}
//...
Can you see it?
What about the user "dajdajda" having account at "dhadadhahjad.edhadja.dads"? What about a Polish guy trying "adaś@ćma.pl" or German one checking out "thomas.müller@öäüß.de"? Are these really valid email addresses? Do you want to accept them? I would rather reject them right at the beginning.
To me full SMTP validation is a little overkill (because of performance, complexity and multiple additional problems that make your life complicated). However a hybrid of regex checks and DNS validation seems to be the acceptable solution.
I have created my custom Email Validator based on the simple DNS check code samples from http://www.rgagnon.com/javadetails/java-0452.html.
Also, for regex validation I used the
org.apache.commons.validator.EmailValidator
which in my opinion offers a better regex pattern than one defined in Tapestry 5.1.0.5 (see code above). If you agree with me, then please feel free to use my code shown below (free of charge ;-)). You can create your DNSEmailValidator in any package you want, then contribute it in you AppModule.java and simply use it within your @Validate or <t:.. validate="dnsEmail"/> statements.
//...
import java.util.Hashtable;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.apache.commons.validator.EmailValidator;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.ValidationException;
import org.apache.tapestry5.ioc.MessageFormatter;
import org.apache.tapestry5.services.FormSupport;
import org.apache.tapestry5.validator.AbstractValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DNSEmailValidator extends AbstractValidator<Void, String> {
private final static Logger _logger = LoggerFactory
.getLogger(DNSEmailValidator.class);
public DNSEmailValidator() {
super(null, String.class, "invalid-email");
}
public void validate(Field field, Void constraintValue,
MessageFormatter formatter, String value)
throws ValidationException {
// validate the syntax
final EmailValidator validator = EmailValidator.getInstance();
if (!validator.isValid(value))
throw new ValidationException(buildMessage(formatter, field));
// validate the DNS
final String[] tokens = value.split("@");
try {
int servers = doLookup(tokens[1]);
_logger.info(tokens[1] + " has " + servers + " mail servers.");
} catch (NamingException e) {
throw new ValidationException(buildMessage(formatter, field));
}
}
private String buildMessage(MessageFormatter formatter, Field field) {
return formatter.format(field.getLabel());
}
public void render(Field field, Void constraintValue,
MessageFormatter formatter, MarkupWriter writer,
FormSupport formSupport) {
formSupport.addValidation(field, "dnsEmail", buildMessage(formatter,
field), null);
}
static int doLookup(String hostName) throws NamingException {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial",
"com.sun.jndi.dns.DnsContextFactory");
DirContext ictx = new InitialDirContext(env);
Attributes attrs = ictx.getAttributes(hostName, new String[] { "MX" });
Attribute attr = attrs.get("MX");
if (attr == null)
return (0);
return (attr.size());
}
}
Contribution code:
//...
public static void contributeFieldValidatorSource(
MappedConfiguration<String, Validator<Void, String>>> configuration) {
configuration.add("dnsEmail", new DNSEmailValidator());
}
//...
Of course, this DNSMailValidator does not make sure that the mail account really exists. It proves that there are mail servers available at the given domain name. Checking the existence of mail account may take a little longer and sometimes fail (e.g. when greylisting is enabled). The cool thing is that Tapestry allows you to create any Validator you want, so you may implement both and use the one that satisfies current requirements of your project.