Skip to content

Commit eeb2681

Browse files
authored
Lookup DnsClientProvider via ServiceLoader (#893)
Default to existing JNDI-based implementation JAVA-4512
1 parent 3928344 commit eeb2681

File tree

8 files changed

+329
-79
lines changed

8 files changed

+329
-79
lines changed

driver-core/src/main/com/mongodb/internal/dns/DefaultDnsResolver.java

Lines changed: 48 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,14 @@
1616

1717
package com.mongodb.internal.dns;
1818

19-
import com.mongodb.MongoClientException;
2019
import com.mongodb.MongoConfigurationException;
20+
import com.mongodb.spi.dns.DnsClient;
21+
import com.mongodb.spi.dns.DnsClientProvider;
22+
import com.mongodb.spi.dns.DnsWithResponseCodeException;
2123

22-
import javax.naming.Context;
23-
import javax.naming.NameNotFoundException;
24-
import javax.naming.NamingEnumeration;
25-
import javax.naming.NamingException;
26-
import javax.naming.directory.Attribute;
27-
import javax.naming.directory.Attributes;
28-
import javax.naming.directory.InitialDirContext;
2924
import java.util.ArrayList;
30-
import java.util.Hashtable;
3125
import java.util.List;
26+
import java.util.ServiceLoader;
3227

3328
import static java.lang.String.format;
3429
import static java.util.Arrays.asList;
@@ -40,6 +35,23 @@
4035
*/
4136
public final class DefaultDnsResolver implements DnsResolver {
4237

38+
private final DnsClient dnsClient;
39+
40+
public DefaultDnsResolver() {
41+
ServiceLoader<DnsClientProvider> loader = ServiceLoader.load(DnsClientProvider.class);
42+
DnsClient dnsClientFromServiceLoader = null;
43+
for (DnsClientProvider dnsClientProvider : loader) {
44+
dnsClientFromServiceLoader = dnsClientProvider.create();
45+
break;
46+
}
47+
48+
if (dnsClientFromServiceLoader == null) {
49+
dnsClient = new JndiDnsClient();
50+
} else {
51+
dnsClient = dnsClientFromServiceLoader;
52+
}
53+
}
54+
4355
/*
4456
The format of SRV record is
4557
priority weight port target.
@@ -56,40 +68,28 @@ The priority and weight are ignored, and we just concatenate the host (after rem
5668
public List<String> resolveHostFromSrvRecords(final String srvHost, final String srvServiceName) {
5769
String srvHostDomain = srvHost.substring(srvHost.indexOf('.') + 1);
5870
List<String> srvHostDomainParts = asList(srvHostDomain.split("\\."));
59-
List<String> hosts = new ArrayList<String>();
60-
InitialDirContext dirContext = createDnsDirContext();
71+
List<String> hosts = new ArrayList<>();
72+
String resourceName = "_" + srvServiceName + "._tcp." + srvHost;
6173
try {
62-
String resourceRecordName = "_" + srvServiceName + "._tcp." + srvHost;
63-
Attributes attributes = dirContext.getAttributes(resourceRecordName, new String[]{"SRV"});
64-
Attribute attribute = attributes.get("SRV");
65-
if (attribute == null) {
66-
throw new MongoConfigurationException(format("No SRV records available for %s", resourceRecordName));
74+
List<String> srvAttributeValues = dnsClient.getResourceRecordData(resourceName, "SRV");
75+
if (srvAttributeValues == null || srvAttributeValues.isEmpty()) {
76+
throw new MongoConfigurationException(format("No SRV records available for '%s'.", resourceName));
6777
}
68-
NamingEnumeration<?> srvRecordEnumeration = attribute.getAll();
69-
while (srvRecordEnumeration.hasMore()) {
70-
String srvRecord = (String) srvRecordEnumeration.next();
78+
79+
for (String srvRecord : srvAttributeValues) {
7180
String[] split = srvRecord.split(" ");
7281
String resolvedHost = split[3].endsWith(".") ? split[3].substring(0, split[3].length() - 1) : split[3];
7382
String resolvedHostDomain = resolvedHost.substring(resolvedHost.indexOf('.') + 1);
7483
if (!sameParentDomain(srvHostDomainParts, resolvedHostDomain)) {
7584
throw new MongoConfigurationException(
76-
format("The SRV host name '%s'resolved to a host '%s 'that is not in a sub-domain of the SRV host.",
85+
format("The SRV host name '%s' resolved to a host '%s 'that is not in a sub-domain of the SRV host.",
7786
srvHost, resolvedHost));
7887
}
7988
hosts.add(resolvedHost + ":" + split[2]);
8089
}
8190

82-
if (hosts.isEmpty()) {
83-
throw new MongoConfigurationException("Unable to find any SRV records for host " + srvHost);
84-
}
85-
} catch (NamingException e) {
86-
throw new MongoConfigurationException("Unable to look up SRV record for host " + srvHost, e);
87-
} finally {
88-
try {
89-
dirContext.close();
90-
} catch (NamingException e) {
91-
// ignore
92-
}
91+
} catch (Exception e) {
92+
throw new MongoConfigurationException(format("Failed looking up SRV record for '%s'.", resourceName), e);
9393
}
9494
return hosts;
9595
}
@@ -110,58 +110,27 @@ private static boolean sameParentDomain(final List<String> srvHostDomainParts, f
110110
*/
111111
@Override
112112
public String resolveAdditionalQueryParametersFromTxtRecords(final String host) {
113-
String additionalQueryParameters = "";
114-
InitialDirContext dirContext = createDnsDirContext();
115113
try {
116-
Attributes attributes = dirContext.getAttributes(host, new String[]{"TXT"});
117-
Attribute attribute = attributes.get("TXT");
118-
if (attribute != null) {
119-
NamingEnumeration<?> txtRecordEnumeration = attribute.getAll();
120-
if (txtRecordEnumeration.hasMore()) {
121-
// Remove all space characters, as the DNS resolver for TXT records inserts a space character
122-
// between each character-string in a single TXT record. That whitespace is spurious in
123-
// this context and must be removed
124-
additionalQueryParameters = ((String) txtRecordEnumeration.next()).replaceAll("\\s", "");
125-
126-
if (txtRecordEnumeration.hasMore()) {
127-
throw new MongoConfigurationException(format("Multiple TXT records found for host '%s'. Only one is permitted",
128-
host));
129-
}
130-
}
114+
List<String> attributeValues = dnsClient.getResourceRecordData(host, "TXT");
115+
if (attributeValues == null || attributeValues.isEmpty()) {
116+
return "";
131117
}
132-
} catch (NameNotFoundException e) {
133-
// ignore NXDomain error (error code 3, "Non-Existent Domain)
134-
} catch (NamingException e) {
135-
throw new MongoConfigurationException("Unable to look up TXT record for host " + host, e);
136-
} finally {
137-
try {
138-
dirContext.close();
139-
} catch (NamingException e) {
140-
// ignore
118+
if (attributeValues.size() > 1) {
119+
throw new MongoConfigurationException(format("Multiple TXT records found for host '%s'. Only one is permitted",
120+
host));
141121
}
142-
}
143-
return additionalQueryParameters;
144-
}
145-
146-
/*
147-
It's unfortunate that we take a runtime dependency on com.sun.jndi.dns.DnsContextFactory.
148-
This is not guaranteed to work on all JVMs but in practice is expected to work on most.
149-
*/
150-
private static InitialDirContext createDnsDirContext() {
151-
Hashtable<String, String> envProps = new Hashtable<String, String>();
152-
envProps.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
153-
154-
try {
155-
return new InitialDirContext(envProps);
156-
} catch (NamingException e) {
157-
// Just in case the provider url default has been changed to a non-dns pseudo url, fallback to the JDK default
158-
envProps.put(Context.PROVIDER_URL, "dns:");
159-
try {
160-
return new InitialDirContext(envProps);
161-
} catch (NamingException ex) {
162-
throw new MongoClientException("Unable to support mongodb+srv// style connections as the 'com.sun.jndi.dns.DnsContextFactory' "
163-
+ "class is not available in this JRE. A JNDI context is required for resolving SRV records.", e);
122+
// Remove all space characters, as the DNS resolver for TXT records inserts a space character
123+
// between each character-string in a single TXT record. That whitespace is spurious in
124+
// this context and must be removed
125+
return attributeValues.get(0).replaceAll("\\s", "");
126+
} catch (DnsWithResponseCodeException e) {
127+
// ignore NXDomain error (error code 3, "Non-Existent Domain)
128+
if (e.getResponseCode() != 3) {
129+
throw new MongoConfigurationException("Failed looking up TXT record for host " + host, e);
164130
}
131+
return "";
132+
} catch (Exception e) {
133+
throw new MongoConfigurationException("Failed looking up TXT record for host " + host, e);
165134
}
166135
}
167136
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.internal.dns;
18+
19+
import com.mongodb.MongoClientException;
20+
import com.mongodb.spi.dns.DnsClient;
21+
import com.mongodb.spi.dns.DnsException;
22+
import com.mongodb.spi.dns.DnsWithResponseCodeException;
23+
24+
import javax.naming.Context;
25+
import javax.naming.NameNotFoundException;
26+
import javax.naming.NamingEnumeration;
27+
import javax.naming.NamingException;
28+
import javax.naming.directory.Attribute;
29+
import javax.naming.directory.InitialDirContext;
30+
import java.util.ArrayList;
31+
import java.util.Collections;
32+
import java.util.Hashtable;
33+
import java.util.List;
34+
35+
final class JndiDnsClient implements DnsClient {
36+
37+
@Override
38+
public List<String> getResourceRecordData(final String name, final String type) throws DnsException {
39+
InitialDirContext dirContext = createDnsDirContext();
40+
try {
41+
Attribute attribute = dirContext.getAttributes(name, new String[]{type}).get(type);
42+
if (attribute == null) {
43+
return Collections.emptyList();
44+
}
45+
List<String> attributeValues = new ArrayList<>();
46+
NamingEnumeration<?> namingEnumeration = attribute.getAll();
47+
while (namingEnumeration.hasMore()) {
48+
attributeValues.add((String) namingEnumeration.next());
49+
}
50+
return attributeValues;
51+
} catch (NameNotFoundException e) {
52+
throw new DnsWithResponseCodeException(e.getMessage(), 3, e);
53+
} catch (NamingException e) {
54+
throw new DnsException(e.getMessage(), e);
55+
} finally {
56+
try {
57+
dirContext.close();
58+
} catch (NamingException e) {
59+
// ignore
60+
}
61+
}
62+
}
63+
64+
/*
65+
It's unfortunate that we take a runtime dependency on com.sun.jndi.dns.DnsContextFactory.
66+
This is not guaranteed to work on all JVMs but in practice is expected to work on most.
67+
*/
68+
private static InitialDirContext createDnsDirContext() {
69+
Hashtable<String, String> envProps = new Hashtable<>();
70+
envProps.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
71+
72+
try {
73+
return new InitialDirContext(envProps);
74+
} catch (NamingException e) {
75+
// Just in case the provider url default has been changed to a non-dns pseudo url, fallback to the JDK default
76+
envProps.put(Context.PROVIDER_URL, "dns:");
77+
try {
78+
return new InitialDirContext(envProps);
79+
} catch (NamingException ex) {
80+
throw new MongoClientException("Unable to support mongodb+srv// style connections as the 'com.sun.jndi.dns.DnsContextFactory' "
81+
+ "class is not available in this JRE. A JNDI context is required for resolving SRV records.", e);
82+
}
83+
}
84+
}
85+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.spi.dns;
18+
19+
import java.util.List;
20+
21+
22+
/**
23+
* An interface describing a DNS client.
24+
*
25+
* @since 4.6
26+
* @see DnsClientProvider
27+
*/
28+
public interface DnsClient {
29+
/**
30+
* Gets the resource record values for the given name and type.
31+
*
32+
* <p>
33+
* Implementations should throw {@link DnsWithResponseCodeException} if the DNS response code is known. Otherwise, the more generic
34+
* {@link DnsException} should be thrown.
35+
* </p>
36+
*
37+
* @param name the name of the resource to look up
38+
* @param type the resource record type, typically either {@code "SRV"} or {@code "TXT"}.
39+
* @return the list of values for the requested resource, or the empty list if none exist
40+
* @throws DnsException the exception
41+
*/
42+
List<String> getResourceRecordData(String name, String type) throws DnsException;
43+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.spi.dns;
18+
19+
/**
20+
* A provider interface for {@link DnsClient}.
21+
*
22+
* <p>
23+
* The driver discovers implementations of this interface via {@link java.util.ServiceLoader}.
24+
* </p>
25+
*
26+
* @since 4.6
27+
* @see java.util.ServiceLoader
28+
*/
29+
public interface DnsClientProvider {
30+
/**
31+
* Construct a new instance of a {@link DnsClient}.
32+
*
33+
* @return a {@link DnsClient}
34+
*/
35+
DnsClient create();
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.spi.dns;
18+
19+
/**
20+
* An exception indicating a DNS error;
21+
*
22+
* @since 4.6
23+
* @see DnsClient#getResourceRecordData(String, String)
24+
*/
25+
public class DnsException extends RuntimeException {
26+
private static final long serialVersionUID = 1;
27+
28+
/**
29+
* Construct an instance
30+
*
31+
* @param message the message
32+
* @param cause the cause
33+
*/
34+
public DnsException(final String message, final Throwable cause) {
35+
super(message, cause);
36+
}
37+
}

0 commit comments

Comments
 (0)