/** * Copyright 2015 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.palantir.giraffe.host; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.Set; import com.google.common.collect.Sets; import com.palantir.giraffe.command.CommandException; import com.palantir.giraffe.command.Commands; import com.palantir.giraffe.internal.OsDetector; /** * Represents a host with a unique hostname in a network. * * <h3><a name="localhost-handling">Localhost Handling</a></h3> * <p> * This class attempts to resolve all representations of the local host to the * same object. The canonical local host object is the one returned by * {@link #localhost()}. Factory methods that perform resolution return this * object when given any loopback IP address or the string "localhost". * * @author pchen * @author bkeyes */ public final class Host { private static final String LOCALHOST = "localhost"; private static final Set<String> localUriSchemes = Sets.newHashSet("file"); /** * Adds a URI scheme to the set used by {@link #fromUri(URI)} to identify * the localhost URIs. * <p> * By default, the {@code file} scheme is included. * * @param scheme the scheme to add */ public static void addLocalUriScheme(String scheme) { checkNotNull(scheme, "scheme must be non-null"); checkArgument(!scheme.isEmpty(), "scheme must not be empty"); localUriSchemes.add(scheme); } private static final class LocalHostHolder { private static final Host localhost = getLocalhostInstance(); private static Host getLocalhostInstance() { try { InetAddress ia = InetAddress.getLocalHost(); return new Host(ia.getCanonicalHostName()); } catch (UnknownHostException e) { // work around JDK-7180557 (Java 7 only) // http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7180557 if (OsDetector.isOsX()) { return tryOsXHostnameCommand(); } throw new InetAddressUnresolvableException(LOCALHOST, e); } } private static Host tryOsXHostnameCommand() { try { String name = Commands.execute(Commands.get("/bin/hostname")).getStdOut().trim(); return fromHostnameUnresolved(name); } catch (IOException | CommandException e) { throw new InetAddressUnresolvableException(LOCALHOST, e); } } } /** * Creates a {@code Host} from a valid hostname. The hostname is resolved to * an {@code InetAddress}, which is used to * {@linkplain #fromInetAddress(InetAddress) create the host}. * * @param name the hostname to resolve * * @throws InetAddressUnresolvableException if the hostname cannot be * resolved */ public static Host fromHostname(String name) { if (name.equals(LOCALHOST)) { return localhost(); } else { try { return fromInetAddress(InetAddress.getByName(name)); } catch (UnknownHostException e) { throw new InetAddressUnresolvableException(name, e); } } } /** * Gets a {@link Host} from a {@link URI}. * <p> * If the URI's scheme {@linkplain #addLocalUriScheme(String) identifies} * resources on this host or if the host component is "localhost", returns * the local host object. Otherwise, use the host component of the URI. * * @param uri the URI from which to extract the host * * @return a host for the given URI * * @throws IllegalArgumentException if the URI has no host component and * does not have a known scheme */ public static Host fromUri(URI uri) { checkNotNull(uri, "uri must be non-null"); String scheme = uri.getScheme(); if (scheme != null && localUriSchemes.contains(scheme.toLowerCase())) { return localhost(); } String host = uri.getHost(); if (host == null) { throw new IllegalArgumentException("URI '" + uri + "' must have a host component"); } else if (host.equals(LOCALHOST)) { return localhost(); } else { return fromHostnameUnresolved(host); } } /** * Gets the {@code Host} representing the local host. * * @throws InetAddressUnresolvableException if "localhost" cannot be * resolved */ public static Host localhost() { return LocalHostHolder.localhost; } /** * Creates a {@code Host} from an {@link InetAddress} using the saved or * resolved {@link InetAddress#getHostName() hostname}. * * @param ia the {@code InetAddress} */ public static Host fromInetAddress(InetAddress ia) { if (ia.isLoopbackAddress()) { return localhost(); } else { String hostname = ia.getHostName(); if (hostname.equals(LOCALHOST)) { return localhost(); } else { return new Host(hostname); } } } /** * Creates a {@code Host} with the given literal hostname. This method does * not perform <a href="#localhost-handling">localhost resolution</a> and * {@link #getHostname()} always returns the given hostname. * * @param name the hostname */ public static Host fromHostnameUnresolved(String name) { return new Host(name); } private final String hostname; private Host(String hostname) { this.hostname = checkNotNull(hostname, "hostname must be non-null"); checkArgument(!hostname.isEmpty(), "hostname must not be empty"); } /** * Returns the hostname of this host. The hostname is set at construction * time and may not be in canonical form. * * @see #getCanonicalHostname() */ public String getHostname() { return hostname; } /** * Resolves this host's {@linkplain #getHostname() hostname} to an IP * address. * * @return an {@link InetAddress} for this host * * @throws InetAddressUnresolvableException if the hostname cannot be * resolved */ public InetAddress getInetAddress() { try { return InetAddress.getByName(hostname); } catch (UnknownHostException e) { throw new InetAddressUnresolvableException(hostname, e); } } /** * Gets this host's canonical hostname by performing a reverse DNS lookup * on this host's {@linkplain #getInetAddress() IP address}. * * @see InetAddress#getCanonicalHostName() */ public String getCanonicalHostname() { return getInetAddress().getCanonicalHostName(); } /** * Determines if this host has the same canonical name as another host. * * @param other the other host * * @return {@code true} if the hosts have the same canonical hostname * * @throws InetAddressUnresolvableException if either hostname cannot be * resolved */ public boolean resolvesToHost(Host other) { return getCanonicalHostname().equals(other.getCanonicalHostname()); } @Override public int hashCode() { return hostname.hashCode(); } /** * Determines if this {@code Host} is equal to another {@code Host}. Two * {@code Host}s are equal if they have the same {@linkplain #getHostname() * hostname}. */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof Host)) { return false; } else { return hostname.equals(((Host) obj).hostname); } } /** * Returns the {@linkplain #getHostname() hostname} of this host. */ @Override public String toString() { return hostname; } }