/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.core.location.geo;
import java.io.Serializable;
import java.net.InetAddress;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.location.AddressableLocation;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.core.location.AbstractLocation;
import org.apache.brooklyn.core.location.LocationConfigKeys;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.internal.BrooklynSystemProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
/**
* Encapsulates geo-IP information for a given host.
*/
public class HostGeoInfo implements Serializable {
private static final long serialVersionUID = -5866759901535266181L;
public static final Logger log = LoggerFactory.getLogger(HostGeoInfo.class);
/** the IP address */
public final String address;
public final String displayName;
public final double latitude;
public final double longitude;
private static Maybe<HostGeoLookup> cachedLookup = null;
public static HostGeoInfo create(String address, String displayName, double latitude, double longitude) {
return new HostGeoInfo(address, displayName, latitude, longitude);
}
public static HostGeoInfo fromIpAddress(InetAddress address) {
try {
HostGeoLookup lookup = getDefaultLookup();
if (lookup!=null)
return lookup.getHostGeoInfo(address);
} catch (Exception e) {
if (log.isDebugEnabled())
log.debug("unable to look up geo DNS info for "+address, e);
}
return null;
}
@Nullable
public static HostGeoLookup getDefaultLookup() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
if (cachedLookup==null) {
cachedLookup = Maybe.of(findHostGeoLookupImpl());
}
return cachedLookup.get();
}
public static void clearCachedLookup() {
cachedLookup = null;
}
/** returns null if cannot be set */
public static HostGeoInfo fromLocation(Location l) {
if (l==null) return null;
Location la = l;
HostGeoInfo resultFromLocation = null;
while (la!=null) {
if (la instanceof HasHostGeoInfo) {
resultFromLocation = ((HasHostGeoInfo)l).getHostGeoInfo();
if (resultFromLocation!=null) break;
}
la = la.getParent();
}
if (resultFromLocation!=null && l==la) {
// from the location
return resultFromLocation;
}
// resultFromLocation may be inherited, in which case we will copy it later
InetAddress address = findIpAddress(l);
Object latitude = l.getConfig(LocationConfigKeys.LATITUDE);
Object longitude = l.getConfig(LocationConfigKeys.LONGITUDE);
if (resultFromLocation!=null && (latitude == null || longitude == null)) {
latitude = resultFromLocation.latitude;
longitude = resultFromLocation.longitude;
}
if (address!=null && (latitude == null || longitude == null)) {
HostGeoInfo geo = fromIpAddress(address);
if (geo==null) return null;
latitude = geo.latitude;
longitude = geo.longitude;
}
if (latitude==null || longitude==null)
return null;
Exception error=null;
try {
latitude = TypeCoercions.castPrimitive(latitude, Double.class);
longitude = TypeCoercions.castPrimitive(longitude, Double.class);
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
error = e;
}
if (error!=null || !(latitude instanceof Double) || !(longitude instanceof Double))
throw new IllegalArgumentException("Location "+l+" specifies invalid type of lat/long: " +
"lat="+latitude+" (type "+(latitude==null ? null : latitude.getClass())+"); " +
"lon="+longitude+" (type "+(longitude==null ? null : longitude.getClass())+")", error);
HostGeoInfo result = new HostGeoInfo(address!=null ? address.getHostAddress() : null, l.getDisplayName(), (Double) latitude, (Double) longitude);
if (l instanceof AbstractLocation) {
((AbstractLocation)l).setHostGeoInfo(result);
}
return result;
}
@Deprecated
private static boolean warnedLegacy = false;
private static HostGeoLookup findHostGeoLookupImpl() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String type = BrooklynSystemProperties.HOST_GEO_LOOKUP_IMPL.getValue();
if (type==null) {
type = BrooklynSystemProperties.HOST_GEO_LOOKUP_IMPL_LEGACY.getValue();
if (type!=null && !warnedLegacy) {
warnedLegacy = true;
log.warn("Using deprecated host-geo-lookup property "+BrooklynSystemProperties.HOST_GEO_LOOKUP_IMPL_LEGACY+"; "
+ "set "+BrooklynSystemProperties.HOST_GEO_LOOKUP_IMPL+" instead");
}
}
/* utrace seems more accurate than geobytes, and it gives a report of how many tokens are left;
* but maxmind if it's installed locally is even better (does not require remote lookup),
* so use it if available */
if (type==null) {
if (MaxMind2HostGeoLookup.getDatabaseReader()!=null)
return new MaxMind2HostGeoLookup();
log.debug("Using Utrace remote for geo lookup because MaxMind2 is not available");
return new UtraceHostGeoLookup();
}
if (type.isEmpty()) return null;
return (HostGeoLookup) Class.forName(type).newInstance();
}
public static HostGeoInfo fromEntity(Entity e) {
for (Location l : e.getLocations()) {
HostGeoInfo hgi = fromLocation(l);
if (hgi != null)
return hgi;
}
return null;
}
public static InetAddress findIpAddress(Location l) {
if (l == null)
return null;
if (l instanceof AddressableLocation)
return ((AddressableLocation) l).getAddress();
return findIpAddress(l.getParent());
}
public HostGeoInfo(String address, String displayName, double latitude, double longitude) {
this.address = address;
this.displayName = displayName==null ? "" : displayName;
this.latitude = latitude;
this.longitude = longitude;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "HostGeoInfo["+displayName+": "+(address!=null ? address : "(no-address)")+" at ("+latitude+","+longitude+")]";
}
@Override
public boolean equals(Object o) {
// Slight cheat: only includes the address + displayName field (displayName to allow overloading localhost etc)
return (o instanceof HostGeoInfo) && Objects.equal(address, ((HostGeoInfo) o).address)
&& Objects.equal(displayName, ((HostGeoInfo) o).displayName);
}
@Override
public int hashCode() {
// Slight cheat: only includes the address + displayName field (displayName to allow overloading localhost etc)
return Objects.hashCode(address, displayName);
}
}