MaxMind's GeoLiteCity database and Java API

Last night I downloaded MaxMind's GeoLite City database and Java API. I wanted some way to figure out where the visitors to my web site were located around the world, and potentially use the Google Maps API to plot locations on my site. MaxMind produces a database that contains location information keyed by IP address and makes available APIs in Java, C, Ruby, Perl, Python and other languages. I was aware of MaxMind's product through some web searches for GPS location by latitude/longitude I performed a few months ago. I chose to download the free, lite, version of the database as a start. The paid version, GeoIP City, is a bit pricey for the small scope of my at-home projects at $370. If I decide to upgrade, the database is just a drop-in replacement and the Java API remains the same. The Java API is distributed under the GPL.

For a number of reason I am not 100% satisfied with Webalizer as a server log analyzer. One of the reasons, which is due to user error I'm sure, is that I can not get reverse-DNS lookups to work. I also don't want to run webalizer daily over my log files and end up with disjointed information in the files and directory structure needed to support it. Additionally I'd like to be able to display this information in real-time on the web.

The GeoLite City database is a 25 MB file that is placed somewhere on the filesystem where the application can access it.

Using the MaxMind Java API is straight forward. There are three main data structures classes in the com.maxmind.geoip package: Country, Region and Location. Location contains the most information, containing a superset of both Country and Region. Also in the package is LookupService, the largest class in the package, that contains methods for selecting the database to use, whether to read it from disk or RAM and looking up IP address info. I imported the classes into Eclipse 3.2 and started an EJB project. For this project I'm going to use Hibernate to handle database persistence.

I created a Session Bean called GeoIP that has a LookupService member. In the ejbCreate() method I have:

try {
    lookupService = new LookupService("c:\\GeoLiteCity.dat");
}
catch (Exception e) {
    e.printStackTrace();
}

This LookupService constructor throws an IOException and the catch block is completely inadequate for a professional application so make sure you handle it in whatever way you deem fit. The good news is that this is all the setup code needed before making queries against the database. In the method getAndSaveIPInfo(String ipAddr) the first line is:

Location loc = lookupService.getLocation(ipAddr);

The one complaint I have about the Location class is that it does not conform to the JavaBean specification and as such there are no getter and setter methods for the public member fields. Hibernate populates and persists objects using the getter and setter methods so I extended Location and gave it getters/setters for all public fields and added id and ipAddr fields with their own getters/setters. Since it is only a bean, rather than waste space listing the code for the LocationBean class I will only list the definition.

public class LocationBean extends Location implements Serializable

After creating a LocationBean and calling the LookupService the LocationBean has to be populated. The method to get the Location object is the LookupService#getLocation(String ipAddr). This method is overloaded to get an InetAddress and long as well.

Location loc = lookupService.getLocation(ipAddr);
LocationBean bean = new LocationBean();
bean.setArea_code(loc.area_code);
bean.setCity(loc.city);
bean.setCountryCode(loc.countryCode);
bean.setCountryName(loc.countryName);
bean.setDma_code(loc.dma_code);
bean.setLatitude(loc.latitude);
bean.setLongitude(loc.longitude);
bean.setPostalCode(loc.postalCode);
bean.setRegion(loc.region);

I'm using MySQL 5.0 to store the data in LocationBeans. The DDL to create the table and the Hibernate mapping file look like this:

mysql> create table geoip_locations (
    -> id integer primary key,
    -> ipAddr varchar(15),
    -> area_code tinyint,
    -> city varchar(100),
    -> countryCode char(2),
    -> countryName varchar(100),
    -> dma_name varchar(100),
    -> latitude float,
    -> longitude float,
    -> postalCode varchar(25),
    -> region varchar(100)
    -> );


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
	<class name="net.anthonychaves.geoip.LocationBean" table="geoip_locations">
		<id name="id">
			<generator class="native"/>
		</id>
		<property name="area_code"/>
		<property name="city"/>
		<property name="countryCode"/>
		<property name="countryName"/>
		<property name="dma_code"/>
		<property name="latitude"/>
		<property name="longitude"/>
		<property name="postalCode"/>
		<property name="region"/>
	</class>
</hibernate-mapping>

After putting the table and mapping file in place the code to save the LocationBean is just standard Hibernate stuff. This code must be refactored out of the getAndSaveIPInfo(…) method into a new method or into a helper class. I'll take care of the this weekend though.

SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
session.save(bean);
session.getTransaction().commit();
sessionFactory.close();

That is about all I have for right now. I'll continue working on this project and web client and post any updates I make. Thanks for reading and comments are always welcome.