/*
* Hibernate OGM, Domain model persistence for NoSQL datastores
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.ogm.cfg.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.hibernate.ogm.cfg.spi.Hosts;
import org.hibernate.ogm.util.impl.Log;
import org.hibernate.ogm.util.impl.LoggerFactory;
/**
* Parser for the host property.
* Could use parboiled if things become more complicated but so far the code is more compact.
*
* @author Emmanuel Bernard emmanuel@hibernate.org
*/
public class HostParser {
/* Allows: host, host:port, ipv4, ipv4:port, ipv6, [ipv6]:port
* regexp: ^([^\[\]:\s]+|\[[\da-fA-F:\.]+\]|[\da-fA-F:\.]+)(:(\d+))*$
* ^ // start from the beginning
* ( // first group
* [^\[\]:\s]+ // allow everything but square brackets, colon and whitespace. At least one element: IPv4 and host
* | // or
* \[[\da-fA-F:\.]+\] // allow numbers, a->f and colon within a bracket, at least one element: IPv6 [12:31:211:2f::]
* | // or
* [\da-fA-F:\.]+ // allow numbers, a->f and colon, at least one element: IPv6 12:31:211:2f::
* ) // end of first group
* ( // second group
* :(\d+) // colon and digits, also defines the third group
* )* //end of second group, optional
* $ // end marker
*/
private static final Pattern HOST_AND_PORT_PATTERN = Pattern.compile( "^([^\\[\\]:\\s]+|\\[[\\da-fA-F:\\.]+\\]|[\\da-fA-F:\\.]+)(:(\\d+))*$" );
// remove surrounding square brackets
// regex: ^\[(.+)\]$
private static final Pattern NAKED_IPV6_PATTERN = Pattern.compile( "^\\[(.+)\\]$" );
private static final Log LOG = LoggerFactory.make();
/**
* Accepts a comma separated list of host / ports.
*
* For example
*
* www.example.com, www2.example.com:123, 192.0.2.1, 192.0.2.2:123, 2001:db8::ff00:42:8329, [2001:db8::ff00:42:8329]:123
*/
public static Hosts parse(String hostString, Integer explicitGlobalPort, Integer defaultPort) {
List<String> hosts = new ArrayList<>();
List<Integer> ports = new ArrayList<>();
if ( hostString == null || hostString.trim().isEmpty() ) {
return Hosts.NO_HOST;
}
// for each element between commas
String[] splits = hostString.split( "," );
for ( String rawSplit : splits ) {
// remove whitespaces
String split = rawSplit.trim();
//
Matcher matcher = HOST_AND_PORT_PATTERN.matcher( split );
if ( matcher.matches() ) {
setCleanHost( matcher, hosts );
setPort( ports, matcher, explicitGlobalPort, splits, defaultPort );
}
else {
throw LOG.unableToParseHost( hostString );
}
}
return new Hosts( hosts, ports );
}
private static void setPort(List<Integer> ports, Matcher matcher, Integer globalPort, String[] splits, Integer defaultPort) {
String portAsString = matcher.group( 3 );
// get the port or if null add the null port
if ( portAsString != null ) {
ports.add( Integer.valueOf( portAsString ) );
}
else if ( splits.length == 1 && globalPort != null ) {
// single host and the property OgmProperties.PORT is set
// this is the legacy setting
ports.add( globalPort );
}
else {
ports.add( defaultPort );
}
}
/**
* If host is of the form [ipv6], return ipv6
*/
private static void setCleanHost(Matcher matcher, List<String> hosts) {
// get the host but might be [ipv6]
String maybeIPv6Host = matcher.group( 1 );
Matcher ipv6Matcher = NAKED_IPV6_PATTERN.matcher( maybeIPv6Host );
String cleanHost = maybeIPv6Host;
if ( ipv6Matcher.matches() ) {
cleanHost = ipv6Matcher.group( 1 );
if ( cleanHost == null ) {
cleanHost = maybeIPv6Host;
}
}
hosts.add( cleanHost );
}
}