/* * Copyright 2014 GoDataDriven B.V. * * 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 io.divolte.server.ip2geo; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; import java.util.zip.GZIPInputStream; import javax.annotation.ParametersAreNonnullByDefault; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.maxmind.db.ClosedDatabaseException; import com.maxmind.geoip2.DatabaseReader; import com.maxmind.geoip2.exception.GeoIp2Exception; import com.maxmind.geoip2.model.CityResponse; @ParametersAreNonnullByDefault public class DatabaseLookupService implements LookupService { private static final Logger logger = LoggerFactory.getLogger(DatabaseLookupService.class); private final DatabaseReader databaseReader; public DatabaseLookupService(final Path externalLocation) throws IOException { databaseReader = loadExternalDatabase(externalLocation); } private static DatabaseReader loadExternalDatabase(final Path location) throws IOException { /* * We have 2 strategies here. * 1) If compressed, we load via a stream (which means into memory). * 2) If it's not compressed, we pass a File in directly which allows DatabaseReader * to mmap(2) the file. */ final DatabaseReader reader; if ("gz".equals(com.google.common.io.Files.getFileExtension(location.toString()))) { try (final InputStream rawStream = Files.newInputStream(location); final InputStream bufferedStream = new BufferedInputStream(rawStream); final InputStream dbStream = new GZIPInputStream(bufferedStream)) { logger.debug("Loading compressed GeoIP database: {}", location); reader = new DatabaseReader.Builder(dbStream).build(); } } else { logger.debug("Loading GeoIP database: {}", location); reader = new DatabaseReader.Builder(location.toFile()).build(); } logger.info("Loaded GeoIP database: {}", location); return reader; } @Override public void close() throws IOException { logger.debug("Closed GeoIP database."); databaseReader.close(); } @Override public Optional<CityResponse> lookup(final InetAddress address) throws ClosedServiceException { Optional<CityResponse> result; try { result = Optional.of(databaseReader.city(address)); logger.debug("Lookup result for {}: {}", address, result); } catch (final GeoIp2Exception e) { // This can happen during normal operation. if (logger.isDebugEnabled()) { logger.debug("Ignoring failure to lookup address: " + address, e); } result = Optional.empty(); } catch (final ClosedDatabaseException e) { throw new ClosedServiceException(this, e); } catch (final IOException e) { logger.warn("Lookup failed for address: " + address, e); result = Optional.empty(); } return result; } }