/*
* Copyright 2010 NCHOVY
*
* 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 org.krakenapps.geoip.impl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Provides;
import org.krakenapps.geoip.GeoIpLocation;
import org.krakenapps.geoip.GeoIpService;
@Component(name = "geoip-service")
@Provides
public class GeoIpServiceImpl implements GeoIpService {
private static final int BLOCK_SIZE = 12;
private static final int INDEX_ITEM_SIZE = 8;
private static final String LOCATION_CSV = "geoip_locs.csv";
private static final String GEOIP_LOCS_IDX = "geoip_locs.idx";
private static final String GEOIP_BLOCKS_BIN = "geoip_blocks.bin";
private static final File base = new File(System.getProperty("kraken.data.dir"), "kraken-geoip/");
@Override
public GeoIpLocation locate(InetAddress address) {
RandomAccessFile raf = null;
RandomAccessFile idx = null;
RandomAccessFile csv = null;
try {
byte[] b = address.getAddress();
long ip = (((long) (b[0] & 0xFF)) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | (b[3] & 0xFF);
raf = new RandomAccessFile(new File(base, GEOIP_BLOCKS_BIN), "r");
idx = new RandomAccessFile(new File(base, GEOIP_LOCS_IDX), "r");
csv = new RandomAccessFile(new File(base, LOCATION_CSV), "r");
int id = searchBlocks(raf, ip);
if (id < 0)
return null;
long location = searchLocations(idx, id);
if (location < 0)
return null;
csv.seek(location);
return new GeoIpLocationImpl(csv.readLine());
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
closeFile(raf);
closeFile(csv);
closeFile(idx);
}
}
private void closeFile(RandomAccessFile raf) {
if (raf == null)
return;
try {
raf.close();
} catch (IOException e) {
}
}
private long searchLocations(RandomAccessFile raf, int locId) throws IOException {
long low = 0;
long high = raf.length() / INDEX_ITEM_SIZE - 1;
while (low <= high) {
long mid = low + (high - low) / 2;
long pos = mid * INDEX_ITEM_SIZE;
raf.seek(pos);
int id = raf.readInt();
if (locId > id)
low = mid + 1;
else if (locId < id)
high = mid - 1;
else {
return raf.readInt();
}
}
return -1;
}
private int searchBlocks(RandomAccessFile raf, long ip) throws IOException {
long lowerBound = 0;
long upperBound = raf.length() / BLOCK_SIZE - 1;
while (lowerBound <= upperBound) {
// bound value is item index
long mid = lowerBound + (upperBound - lowerBound) / 2;
long pos = mid * BLOCK_SIZE;
raf.seek(pos);
long begin = raf.readInt() & 0xFFFFFFFFL;
long end = raf.readInt() & 0xFFFFFFFFL;
long id = raf.readInt() & 0xFFFFFFFFL;
if (begin <= ip && ip <= end) {
return (int) id;
} else if (ip < begin)
upperBound = mid - 1;
else if (end < ip)
lowerBound = mid + 1;
}
return -1;
}
public void indexLocation(File f) throws IOException {
int pos = 0;
FileOutputStream bw = new FileOutputStream(new File(GEOIP_LOCS_IDX));
RandomAccessFile raf = new RandomAccessFile(f, "r");
byte[] all = new byte[(int) f.length()];
raf.read(all);
int mark = 0;
ByteBuffer bb = ByteBuffer.allocate(INDEX_ITEM_SIZE);
try {
while (true) {
int begin = mark;
if (mark >= all.length)
break;
while (true) {
if (mark >= all.length)
break;
if (all[mark++] == '\n')
break;
}
int end = mark;
int length = (int) (end - begin);
String line = new String(all, begin, length);
char c = line.charAt(0);
if (c < '0' || c > '9') {
pos += length;
continue;
}
int id = Integer.parseInt(line.split(",")[0]);
bb.clear();
bb.putInt(id);
bb.putInt(pos);
bb.flip();
bw.write(bb.array());
pos += length;
}
} finally {
raf.close();
bw.close();
}
}
@Override
public void compileIpBlocks(File f) throws IOException {
File output = new File(GEOIP_BLOCKS_BIN);
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
FileOutputStream bw = new FileOutputStream(output);
ByteBuffer bb = ByteBuffer.allocate(BLOCK_SIZE); // 4 + 4 + 4
try {
while (true) {
String line = br.readLine();
if (line == null)
break;
if (line.charAt(0) != '"')
continue;
String[] tokens = line.replaceAll("\"", "").split(",");
long begin = Long.parseLong(tokens[0]);
long end = Long.parseLong(tokens[1]);
long location = Long.parseLong(tokens[2]);
bb.clear();
bb.putInt((int) begin);
bb.putInt((int) end);
bb.putInt((int) location);
bb.flip();
bw.write(bb.array());
}
} finally {
if (br != null)
br.close();
if (bw != null)
bw.close();
}
}
}