1 /*
2 * #%L
3 * ServerNameUtils.java - mongodb-async-driver - Allanbank Consulting, Inc.
4 * %%
5 * Copyright (C) 2011 - 2014 Allanbank Consulting, Inc.
6 * %%
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * #L%
19 */
20 package com.allanbank.mongodb.util;
21
22 import java.net.InetAddress;
23 import java.net.InetSocketAddress;
24 import java.net.UnknownHostException;
25 import java.util.regex.Pattern;
26
27 /**
28 * ServerNameUtils provides the ability to generate a normalized name for a
29 * server.
30 *
31 * @api.no This class is <b>NOT</b> part of the drivers API. This class may be
32 * mutated in incompatible ways between any two releases of the driver.
33 * @copyright 2012-2013, Allanbank Consulting, Inc., All Rights Reserved
34 */
35 public class ServerNameUtils {
36 /** The default MongoDB port. */
37 public static final int DEFAULT_PORT = 27017;
38
39 /** The length of an IPv6 address in bytes. */
40 public static final int IPV6_LENGTH = 16;
41
42 /**
43 * Pattern to match a literal IPv6 address with an elipse; e.g.,
44 * fe80::250:56ff:fec0:1
45 */
46 private static final Pattern IPV6_ELLIPSED_PATTERN;
47
48 /** Pattern to match a fully specified IPv6. */
49 private static final Pattern IPV6_PATTERN;
50
51 static {
52 final String hexWord = "[0-9A-Fa-f]{1,4}";
53 IPV6_ELLIPSED_PATTERN = Pattern.compile("^((?:" + hexWord + "(?::"
54 + hexWord + ")*)?)::((?:" + hexWord + "(?::" + hexWord
55 + ")*)?)$");
56 IPV6_PATTERN = Pattern.compile("^(?:" + hexWord + ":){7}" + hexWord
57 + "$");
58 }
59
60 /**
61 * Creates a normalized form of the {@link InetSocketAddress} in the form
62 * '[server]:[port]'.
63 *
64 * @param address
65 * The address to generate a normalized form for.
66 * @return The normalized address.
67 */
68 public static String normalize(final InetSocketAddress address) {
69 final StringBuilder b = new StringBuilder();
70
71 String name;
72 if (address.isUnresolved()) {
73 name = address.getHostName();
74 }
75 else {
76 name = address.getAddress().getHostName();
77 }
78
79 // Check for a raw IPv6 address and wrap in RFC 2732 format.
80 if (IPV6_ELLIPSED_PATTERN.matcher(name).matches()
81 || IPV6_PATTERN.matcher(name).matches()) {
82 b.append('[');
83 b.append(name);
84 b.append(']');
85 }
86 else {
87 b.append(name);
88 }
89
90 b.append(':');
91 b.append(address.getPort());
92
93 return b.toString();
94 }
95
96 /**
97 * Normalizes the name into a '[server]:[port]' string. If a port component
98 * is not provided then port 27017 is assumed.
99 *
100 * @param server
101 * The server[:port] string.
102 * @return The normailzed server string.
103 */
104 public static String normalize(final String server) {
105 final String name = server;
106 int port = DEFAULT_PORT;
107
108 final int firstBracket = server.indexOf('[');
109 final int lastBracket = server.lastIndexOf(']');
110 final int colonIndex = server.lastIndexOf(':');
111
112 // Check for RFC 2732.
113 if ((firstBracket == 0) && (lastBracket > 0)) {
114 if (colonIndex == (lastBracket + 1)) {
115 final String portString = server.substring(colonIndex + 1);
116 try {
117 Integer.parseInt(portString);
118
119 // Its a good name no need to create another string.
120 return server;
121 }
122 catch (final NumberFormatException nfe) {
123 // Not a port after the colon. Move on.
124 port = DEFAULT_PORT;
125 }
126 }
127
128 // No port. Add it.
129 final String namePart = server.substring(0, lastBracket + 1);
130 return namePart + ":" + port;
131 }
132
133 if (0 <= colonIndex) {
134 final int previousColon = server.lastIndexOf(':', colonIndex - 1);
135 if (0 <= previousColon) {
136 // Colon in the host name. Might be an IPv6 address. Try to
137 // parse the whole thing as an address and if it works then
138 // assume no port.
139 try {
140 final InetAddress addr = InetAddress.getByName(server);
141 final byte[] bytes = addr.getAddress();
142
143 // Is it an IPv6 address?
144 if (bytes.length == IPV6_LENGTH) {
145 // Yep - add the default port and wrap in RFC 2732
146 return "[" + server + "]:" + DEFAULT_PORT;
147 }
148 }
149 catch (final UnknownHostException uhe) {
150 // OK - fall through to being a port.
151 final String addrString = server.substring(0, colonIndex);
152 final String portString = server.substring(colonIndex + 1);
153 try {
154 final InetAddress addr = InetAddress
155 .getByName(addrString);
156 final byte[] bytes = addr.getAddress();
157
158 // Is it an IPv6 address?
159 if (bytes.length == IPV6_LENGTH) {
160 port = Integer.parseInt(portString);
161
162 return "[" + addrString + "]:" + port;
163 }
164 }
165 catch (final NumberFormatException nfe) {
166 // Not a port after the colon. Move on.
167 port = DEFAULT_PORT;
168 }
169 catch (final UnknownHostException uhe2) {
170 // OK - fall through to being a port.
171 uhe2.hashCode(); // Shhh - PMD.
172 }
173 }
174 }
175
176 final String portString = server.substring(colonIndex + 1);
177 try {
178 Integer.parseInt(portString);
179
180 // Its a good name no need to create another string.
181 return server;
182 }
183 catch (final NumberFormatException nfe) {
184 // Not a port after the colon. Move on.
185 port = DEFAULT_PORT;
186 }
187 }
188
189 return name + ':' + port;
190 }
191
192 /**
193 * Parse the name into a {@link InetSocketAddress}. If a port component is
194 * not provided then port 27017 is assumed.
195 *
196 * @param server
197 * The server[:port] string.
198 * @return The {@link InetSocketAddress} parsed from the server string.
199 */
200 public static InetSocketAddress parse(final String server) {
201 String name = server;
202 int port = DEFAULT_PORT;
203
204 final int firstBracket = server.indexOf('[');
205 final int lastBracket = server.lastIndexOf(']');
206 final int colonIndex = server.lastIndexOf(':');
207 // Check for RFC 2732.
208 if ((firstBracket == 0) && (lastBracket > 0)) {
209 if ((lastBracket + 1) == colonIndex) {
210 final String portString = server.substring(colonIndex + 1);
211 try {
212 port = Integer.parseInt(portString);
213 name = server.substring(0, colonIndex);
214 }
215 catch (final NumberFormatException nfe) {
216 // Not a port after the colon. Move on.
217 port = DEFAULT_PORT;
218 }
219 }
220 }
221 else if (colonIndex > 0) {
222 final String portString = server.substring(colonIndex + 1);
223 try {
224 port = Integer.parseInt(portString);
225 name = server.substring(0, colonIndex);
226 }
227 catch (final NumberFormatException nfe) {
228 // Not a port after the colon. Move on.
229 port = DEFAULT_PORT;
230
231 }
232 }
233
234 return new InetSocketAddress(name, port);
235 }
236
237 /**
238 * Creates a new ServerNameUtils.
239 */
240 private ServerNameUtils() {
241 // nothing
242 }
243
244 }