View Javadoc
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 }