JndiMxRecordLookup.java
package com.albertsilva.dev.asjcatalog.validation.user.dns;
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.springframework.stereotype.Component;
/**
* Implementação de {@link MxRecordLookup} que consulta o DNS real pela API
* JNDI, com o provedor {@code com.sun.jndi.dns.DnsContextFactory}.
*
* <p>
* Não há servidor, timeout nem tentativas configurados no código: valem os
* padrões do provedor e o DNS do sistema operacional. Só o tipo MX é
* considerado; não há alternativa para domínios que aceitam e-mail apenas por
* registro A/AAAA. <b>Qualquer {@link Exception}</b> (domínio inexistente, DNS
* indisponível, tempo esgotado etc.) resulta em {@code false}, o que faz uma
* falha de rede tornar inválido um e-mail que talvez fosse válido.
* </p>
*
* <p>
* Cada chamada cria um contexto JNDI novo e o fecha ao final. A consulta é
* <b>síncrona</b>, na thread de quem chama.
* </p>
*/
@Component
public class JndiMxRecordLookup implements MxRecordLookup {
/**
* Cria o contexto JNDI usado em cada consulta. Existe para que os testes de
* unidade possam fornecer um contexto falso, sem acessar a rede.
*/
@FunctionalInterface
interface DirContextFactory {
/**
* @return novo contexto JNDI
* @throws NamingException se o contexto não puder ser criado
*/
DirContext create() throws NamingException;
}
private final DirContextFactory contextFactory;
/**
* Construtor usado pelo Spring: cada consulta cria um
* {@link InitialDirContext} com o provedor de DNS do JNDI.
*/
public JndiMxRecordLookup() {
this(JndiMxRecordLookup::createDnsContext);
}
/**
* Construtor usado pelos testes de unidade, que fornecem a fábrica do
* contexto.
*
* @param contextFactory fábrica do contexto JNDI
*/
JndiMxRecordLookup(DirContextFactory contextFactory) {
this.contextFactory = contextFactory;
}
/**
* Consulta o DNS e verifica se o domínio possui ao menos um registro do tipo
* MX. O contexto JNDI é fechado no bloco {@code finally}.
*
* @param domain domínio a consultar
* @return {@code true} se houver ao menos um registro MX; {@code false} se não
* houver ou se ocorrer qualquer erro
*/
@Override
public boolean hasMxRecord(String domain) {
DirContext context = null;
try {
context = contextFactory.create();
Attributes attributes = context.getAttributes(domain, new String[] { "MX" });
Attribute attribute = attributes.get("MX");
return attribute != null && attribute.size() > 0;
} catch (Exception e) {
return false;
} finally {
closeContext(context);
}
}
/**
* Cria o contexto JNDI de DNS.
*
* @return novo contexto
* @throws NamingException se o contexto não puder ser criado
*/
private static DirContext createDnsContext() throws NamingException {
Hashtable<String, String> env = new Hashtable<>();
env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
return new InitialDirContext(env);
}
/**
* Fecha o contexto JNDI de forma segura, capturando
* qualquer exceção que possa ocorrer durante o encerramento.
*
* @param context contexto JNDI a ser fechado
*/
private void closeContext(DirContext context) {
if (context == null) {
return;
}
try {
context.close();
} catch (Exception e) {
// ignora erro ao fechar
}
}
}