/* UdgerParser - Java agent string parser based on Udger https://udger.com/products/local_parser author The Udger.com Team ([email protected]) copyright Copyright (c) Udger s.r.o. license GNU Lesser General Public License link https://udger.com/products */ package org.udger.parser; import org.sqlite.SQLiteConfig; import java.io.Closeable; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.*; import java.util.*; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Main parser's class handles parser requests for user agent or IP. */ public class UdgerParser implements Closeable { private static final Logger LOG = Logger.getLogger(UdgerParser.class.getName()); private static final String DB_FILENAME = "udgerdb_v3.dat"; private static final String UDGER_UA_DEV_BRAND_LIST_URL = "https://udger.com/resources/ua-list/devices-brand-detail?brand="; private static final String ID_CRAWLER = "crawler"; private static final Pattern PAT_UNPERLIZE = Pattern.compile("^/?(.*?)/si$"); /** * Holds precalculated data for single DB. Intention is to have single ParserDbData associated with multiple UdgerParser(s) */ public static class ParserDbData { private WordDetector clientWordDetector; private WordDetector deviceWordDetector; private WordDetector osWordDetector; private List<IdRegString> clientRegstringList; private List<IdRegString> osRegstringList; private List<IdRegString> deviceRegstringList; private volatile boolean prepared = false; private final String dbFileName; public ParserDbData(String dbFileName) { this.dbFileName = dbFileName; } protected void prepare(Connection connection) throws SQLException { if (!prepared) { synchronized (this) { if (!prepared) { clientRegstringList = prepareRegexpStruct(connection, "udger_client_regex"); osRegstringList = prepareRegexpStruct(connection, "udger_os_regex"); deviceRegstringList = prepareRegexpStruct(connection, "udger_deviceclass_regex"); clientWordDetector = createWordDetector(connection, "udger_client_regex", "udger_client_regex_words"); deviceWordDetector = createWordDetector(connection, "udger_deviceclass_regex", "udger_deviceclass_regex_words"); osWordDetector = createWordDetector(connection, "udger_os_regex", "udger_os_regex_words"); prepared = true; } } } } } private static class ClientInfo { private Integer clientId; private Integer classId; } private static class IdRegString { int id; int wordId1; int wordId2; Pattern pattern; } private static class MatcherWithIdRegString { private final Matcher matcher; private final IdRegString irs; private MatcherWithIdRegString(Matcher matcher, IdRegString irs) { this.matcher = matcher; this.irs = irs; } } private ParserDbData parserDbData; private Connection connection; private final Map<String, SoftReference<Pattern>> regexCache = new HashMap<>(); private Map<String, PreparedStatement> preparedStmtMap = new HashMap<>(); private LRUCache<String, UdgerUaResult> cache; private boolean osParserEnabled = true; private boolean deviceParserEnabled = true; private boolean deviceBrandParserEnabled = true; private boolean inMemoryEnabled = false; /** * Instantiates a new udger parser with LRU cache with capacity of 10.000 items * * @param parserDbData the parser data associated with single DB */ public UdgerParser(ParserDbData parserDbData) { this(parserDbData, 10000); } /** * Instantiates a new udger parser. * * @param parserDbData the parser data associated with single DB * @param cacheCapacity the LRU cache capacity */ public UdgerParser(ParserDbData parserDbData, int cacheCapacity) { this.parserDbData = parserDbData; if (cacheCapacity > 0) { cache = new LRUCache<>(cacheCapacity); } } /** * Instantiates a new udger parser with LRU cache with capacity of 10.000 items * * @param parserDbData the parser data associated with single DB * @param inMemoryEnabled the true for in memory mode * @param cacheCapacity the LRU cache capacity */ public UdgerParser(ParserDbData parserDbData, boolean inMemoryEnabled, int cacheCapacity) { this(parserDbData, cacheCapacity); this.inMemoryEnabled = inMemoryEnabled; } @Override public void close() throws IOException { try { for (PreparedStatement preparedStmt : preparedStmtMap.values()) { preparedStmt.close(); } preparedStmtMap.clear(); if (connection != null && !connection.isClosed()) { connection.close(); connection = null; } if (cache != null) { cache.clear(); } regexCache.clear(); } catch (SQLException e) { throw new IOException(e.getMessage()); } } /** * Returns true if the sqlite DB connection has not been closed and is still valid. * * @param timeoutMillis the timeout millis * @return true, if is valid * @throws IOException Signals that an I/O exception has occurred. */ public boolean isValid(int timeoutMillis) throws IOException { try { return connection == null || connection.isValid(timeoutMillis); } catch (SQLException e) { throw new IOException("Failed to validate connection within " + timeoutMillis + " millis.", e); } } /** * Parses the user agent string and stores results of parsing in UdgerUaResult. * If the parser was initialized to use an in memory DB, then the DB is not set to read only. * This does not matter since the connection is internal to this client, as such there are * no chance of external modifications. * * @param uaString the user agent string * @return the intance of UdgerUaResult storing results of parsing * @throws SQLException the SQL exception */ public UdgerUaResult parseUa(String uaString) throws SQLException { UdgerUaResult ret; if (cache != null) { ret = cache.get(uaString); if (ret != null) { return ret; } } ret = new UdgerUaResult(uaString); prepare(); ClientInfo clientInfo = clientDetector(uaString, ret); if (!"Crawler".equals(ret.getUaClass())) { if (osParserEnabled) { osDetector(uaString, ret, clientInfo); } if (deviceParserEnabled) { deviceDetector(uaString, ret, clientInfo); } if (deviceBrandParserEnabled) { if (ret.getOsFamilyCode() != null && !ret.getOsFamilyCode().isEmpty()) { fetchDeviceBrand(uaString, ret); } } } if (cache != null) { cache.put(uaString, ret); } return ret; } /** * Parses the IP string and stores results of parsing in UdgerIpResult. * * @param ipString the IP string * @return the instance of UdgerIpResult storing results of parsing * @throws SQLException the SQL exception * @throws UnknownHostException the unknown host exception */ public UdgerIpResult parseIp(String ipString) throws SQLException, UnknownHostException { UdgerIpResult ret = new UdgerIpResult(ipString); InetAddress addr = InetAddress.getByName(ipString); Long ipv4int = null; String normalizedIp = null; if (addr instanceof Inet4Address) { ipv4int = 0L; for (byte b : addr.getAddress()) { ipv4int = ipv4int << 8 | (b & 0xFF); } normalizedIp = addr.getHostAddress(); } else if (addr instanceof Inet6Address) { normalizedIp = addr.getHostAddress().replaceAll("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)", "::$2"); } ret.setIpClassification("Unrecognized"); ret.setIpClassificationCode("unrecognized"); if (normalizedIp != null) { prepare(); try (ResultSet ipRs = getFirstRow(UdgerSqlQuery.SQL_IP, normalizedIp)) { if (ipRs != null && ipRs.next()) { fetchUdgerIp(ipRs, ret); if (!ID_CRAWLER.equals(ret.getIpClassificationCode())) { ret.setCrawlerFamilyInfoUrl(""); } } } if (ipv4int != null) { ret.setIpVer(4); ResultSet dataCenterRs = getFirstRow(UdgerSqlQuery.SQL_DATACENTER, ipv4int, ipv4int); fetchDataCenterAndCloseRs(dataCenterRs, ret); } else { ret.setIpVer(6); int[] ipArray = ip6ToArray((Inet6Address) addr); ResultSet dataCenterRs = getFirstRow(UdgerSqlQuery.SQL_DATACENTER_RANGE6, ipArray[0], ipArray[0], ipArray[1], ipArray[1], ipArray[2], ipArray[2], ipArray[3], ipArray[3], ipArray[4], ipArray[4], ipArray[5], ipArray[5], ipArray[6], ipArray[6], ipArray[7], ipArray[7] ); fetchDataCenterAndCloseRs(dataCenterRs, ret); } } return ret; } private void fetchDataCenterAndCloseRs(ResultSet dataCenterRs, UdgerIpResult ret) throws SQLException { if (dataCenterRs != null) { try { if (dataCenterRs.next()) { fetchDataCenter(dataCenterRs, ret); } } finally { dataCenterRs.close(); } } } /** * Checks if is OS parser enabled. OS parser is enabled by default * * @return true, if is OS parser enabled */ public boolean isOsParserEnabled() { return osParserEnabled; } /** * Enable/disable the OS parser. OS parser is enabled by default. If enabled following fields * of UdgerUaResult are processed by the OS parser: * <ul> * <li>osFamily, osFamilyCode, OS, osCode, osHomePage, osIcon, osIconBig</li> * <li>osFamilyVendor, osFamilyVendorCode, osFamilyVedorHomepage, osInfoUrl</li> * </ul> * <p> * If the OSs fields are not necessary then disabling this feature can increase * the parser's performance. * * @param osParserEnabled the true if os parser is to be enabled */ public void setOsParserEnabled(boolean osParserEnabled) { this.osParserEnabled = osParserEnabled; } /** * Checks if is device parser enabled. Device parser is enabled by default * * @return true, if device parser is enabled */ public boolean isDeviceParserEnabled() { return deviceParserEnabled; } /** * Enable/disable the device parser. Device parser is enabled by default. If enabled following fields * of UdgerUaResult are filled by the device parser: * <ul> * <li>deviceClass, deviceClassCode, deviceClassIcon</li> * <li>deviceClassIconBig, deviceClassInfoUrl</li> * </ul> * <p> * If the DEVICEs fields are not necessary then disabling this feature can increase * the parser's performance. * * @param deviceParserEnabled the true if device parser is to be enabled */ public void setDeviceParserEnabled(boolean deviceParserEnabled) { this.deviceParserEnabled = deviceParserEnabled; } /** * Checks if is device brand parser enabled. Device brand parser is enabled by default. * * @return true, if device brand parser is enabled */ public boolean isDeviceBrandParserEnabled() { return deviceBrandParserEnabled; } /** * Enable/disable the device brand parser. Device brand parser is enabled by default. If enabled following fields * of UdgerUaResult are filled by the device brand parser: * <ul> * <li>deviceMarketname, deviceBrand, deviceBrandCode, deviceBrandHomepage</li> * <li>deviceBrandIcon, deviceBrandIconBig, deviceBrandInfoUrl</li> * </ul> * <p> * If the BRANDs fields are not necessary then disabling this feature can increase * the parser's performance. * * @param deviceBrandParserEnabled the true if device brand parser is to be enabled */ public void setDeviceBrandParserEnabled(boolean deviceBrandParserEnabled) { this.deviceBrandParserEnabled = deviceBrandParserEnabled; } private static WordDetector createWordDetector(Connection connection, String regexTableName, String wordTableName) throws SQLException { Set<Integer> usedWords = new HashSet<>(); addUsedWords(usedWords, connection, regexTableName, "word_id"); addUsedWords(usedWords, connection, regexTableName, "word2_id"); WordDetector result = new WordDetector(); try (final Statement statement = connection.createStatement(); final ResultSet rs = statement.executeQuery("SELECT * FROM " + wordTableName)) { if (rs != null) { while (rs.next()) { int id = rs.getInt("id"); if (usedWords.contains(id)) { String word = rs.getString("word").toLowerCase(); result.addWord(id, word); } } } } return result; } private static void addUsedWords(Set<Integer> usedWords, Connection connection, String regexTableName, String wordIdColumn) throws SQLException { try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("SELECT " + wordIdColumn + " FROM " + regexTableName)) { if (rs != null) { while (rs.next()) { usedWords.add(rs.getInt(wordIdColumn)); } } } } private MatcherWithIdRegString findMatcherIdRegString(String uaString, Set<Integer> foundClientWords, List<IdRegString> list) { for (IdRegString irs : list) { if ((irs.wordId1 == 0 || foundClientWords.contains(irs.wordId1)) && (irs.wordId2 == 0 || foundClientWords.contains(irs.wordId2))) { Matcher matcher = irs.pattern.matcher(uaString); if (matcher.find()) return new MatcherWithIdRegString(matcher, irs); } } return null; } private static List<IdRegString> prepareRegexpStruct(Connection connection, String regexpTableName) throws SQLException { List<IdRegString> ret = new ArrayList<>(); try (Statement statement = connection.createStatement(); ResultSet rs = statement.executeQuery("SELECT rowid, regstring, word_id, word2_id FROM " + regexpTableName + " ORDER BY sequence")) { if (rs != null) { while (rs.next()) { IdRegString irs = new IdRegString(); irs.id = rs.getInt("rowid"); irs.wordId1 = rs.getInt("word_id"); irs.wordId2 = rs.getInt("word2_id"); String regex = rs.getString("regstring"); Matcher m = PAT_UNPERLIZE.matcher(regex); if (m.matches()) { regex = m.group(1); } irs.pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); ret.add(irs); } } } return ret; } private ClientInfo clientDetector(String uaString, UdgerUaResult ret) throws SQLException { ClientInfo clientInfo = new ClientInfo(); try (ResultSet userAgentRs1 = getFirstRow(UdgerSqlQuery.SQL_CRAWLER, uaString)) { if (userAgentRs1 != null && userAgentRs1.next()) { fetchUserAgent(userAgentRs1, ret); clientInfo.classId = 99; clientInfo.clientId = -1; } else { MatcherWithIdRegString mwirs = findMatcherIdRegString(uaString, parserDbData.clientWordDetector.findWords(uaString), parserDbData.clientRegstringList); if (mwirs != null) { try (ResultSet userAgentRs2 = getFirstRow(UdgerSqlQuery.SQL_CLIENT, mwirs.irs.id)) { if (userAgentRs2 != null && userAgentRs2.next()) { fetchUserAgent(userAgentRs2, ret); clientInfo.classId = ret.getClassId(); clientInfo.clientId = ret.getClientId(); patchVersions(mwirs.matcher, ret); } } } else { ret.setUaClass("Unrecognized"); ret.setUaClassCode("unrecognized"); } } } return clientInfo; } private void osDetector(String uaString, UdgerUaResult ret, ClientInfo clientInfo) throws SQLException { MatcherWithIdRegString mwirs = findMatcherIdRegString(uaString, parserDbData.osWordDetector.findWords(uaString), parserDbData.osRegstringList); if (mwirs != null) { try (ResultSet opSysRs = getFirstRow(UdgerSqlQuery.SQL_OS, mwirs.irs.id)) { if (opSysRs != null && opSysRs.next()) { fetchOperatingSystem(opSysRs, ret); } } } else { if (clientInfo.clientId != null && clientInfo.clientId != 0) { try (ResultSet opSysRs = getFirstRow(UdgerSqlQuery.SQL_CLIENT_OS, clientInfo.clientId.toString())) { if (opSysRs != null && opSysRs.next()) { fetchOperatingSystem(opSysRs, ret); } } } } } private void deviceDetector(String uaString, UdgerUaResult ret, ClientInfo clientInfo) throws SQLException { MatcherWithIdRegString mwirs = findMatcherIdRegString(uaString, parserDbData.deviceWordDetector.findWords(uaString), parserDbData.deviceRegstringList); if (mwirs != null) { try (ResultSet devRs = getFirstRow(UdgerSqlQuery.SQL_DEVICE, mwirs.irs.id)) { if (devRs != null && devRs.next()) { fetchDevice(devRs, ret); } } } else { if (clientInfo.classId != null && clientInfo.classId != -1) { try (ResultSet devRs = getFirstRow(UdgerSqlQuery.SQL_CLIENT_CLASS, clientInfo.classId.toString())) { if (devRs != null && devRs.next()) { fetchDevice(devRs, ret); } } } } } private void fetchDeviceBrand(String uaString, UdgerUaResult ret) throws SQLException { PreparedStatement preparedStatement = preparedStmtMap.get(UdgerSqlQuery.SQL_DEVICE_REGEX); if (preparedStatement == null) { preparedStatement = connection.prepareStatement(UdgerSqlQuery.SQL_DEVICE_REGEX); preparedStmtMap.put(UdgerSqlQuery.SQL_DEVICE_REGEX, preparedStatement); } preparedStatement.setObject(1, ret.getOsFamilyCode()); preparedStatement.setObject(2, ret.getOsCode()); try (ResultSet devRegexRs = preparedStatement.executeQuery()) { if (devRegexRs != null) { while (devRegexRs.next()) { String devId = devRegexRs.getString("id"); String regex = devRegexRs.getString("regstring"); if (devId != null && regex != null) { Pattern patRegex = getRegexFromCache(regex); Matcher matcher = patRegex.matcher(uaString); if (matcher.find()) { try (ResultSet devNameListRs = getFirstRow(UdgerSqlQuery.SQL_DEVICE_NAME_LIST, devId, matcher.group(1))) { if (devNameListRs != null && devNameListRs.next()) { ret.setDeviceMarketname(devNameListRs.getString("marketname")); ret.setDeviceBrand(devNameListRs.getString("brand")); ret.setDeviceBrandCode(devNameListRs.getString("brand_code")); ret.setDeviceBrandHomepage(devNameListRs.getString("brand_url")); ret.setDeviceBrandIcon(devNameListRs.getString("icon")); ret.setDeviceBrandIconBig(devNameListRs.getString("icon_big")); ret.setDeviceBrandInfoUrl(UDGER_UA_DEV_BRAND_LIST_URL + devNameListRs.getString("brand_code")); break; } } } } } } } } private int[] ip6ToArray(Inet6Address addr) { int ret[] = new int[8]; byte[] bytes = addr.getAddress(); for (int i = 0; i < 8; i++) { ret[i] = ((bytes[i * 2] << 8) & 0xff00) | (bytes[i * 2 + 1] & 0xff); } return ret; } private void prepare() throws SQLException { connect(); parserDbData.prepare(connection); } private void connect() throws SQLException { if (connection == null) { SQLiteConfig config = new SQLiteConfig(); config.setReadOnly(true); if (inMemoryEnabled) { // we cannot use read only for in memory DB since we need to populate this DB from the file. connection = DriverManager.getConnection("jdbc:sqlite::memory:"); File dbfile = new File(parserDbData.dbFileName); try (Statement statement = connection.createStatement()) { statement.executeUpdate("restore from " + dbfile.getPath()); } catch (Exception e) { LOG.warning("Error re-constructing in memory data base from Db file " + dbfile); } } else { connection = DriverManager.getConnection("jdbc:sqlite:" + parserDbData.dbFileName, config.toProperties()); } } } private Pattern getRegexFromCache(String regex) { SoftReference<Pattern> patRegex = regexCache.get(regex); if (patRegex == null || patRegex.get() == null) { Matcher m = PAT_UNPERLIZE.matcher(regex); if (m.matches()) { regex = m.group(1); } patRegex = new SoftReference<>(Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.DOTALL)); regexCache.put(regex, patRegex); } return patRegex.get(); } private ResultSet getFirstRow(String query, Object... params) throws SQLException { PreparedStatement preparedStatement = preparedStmtMap.get(query); if (preparedStatement == null) { preparedStatement = connection.prepareStatement(query); preparedStmtMap.put(query, preparedStatement); } for (int i = 0; i < params.length; i++) { preparedStatement.setObject(i + 1, params[i]); } preparedStatement.setMaxRows(1); return preparedStatement.executeQuery(); } private void fetchUserAgent(ResultSet rs, UdgerUaResult ret) throws SQLException { ret.setClassId(rs.getInt("class_id")); ret.setClientId(rs.getInt("client_id")); ret.setCrawlerCategory(nvl(rs.getString("crawler_category"))); ret.setCrawlerCategoryCode(nvl(rs.getString("crawler_category_code"))); ret.setCrawlerLastSeen(nvl(rs.getString("crawler_last_seen"))); ret.setCrawlerRespectRobotstxt(nvl(rs.getString("crawler_respect_robotstxt"))); ret.setUa(nvl(rs.getString("ua"))); ret.setUaClass(nvl(rs.getString("ua_class"))); ret.setUaClassCode(nvl(rs.getString("ua_class_code"))); ret.setUaEngine(nvl(rs.getString("ua_engine"))); ret.setUaFamily(nvl(rs.getString("ua_family"))); ret.setUaFamilyCode(nvl(rs.getString("ua_family_code"))); ret.setUaFamilyHomepage(nvl(rs.getString("ua_family_homepage"))); ret.setUaFamilyIcon(nvl(rs.getString("ua_family_icon"))); ret.setUaFamilyIconBig(nvl(rs.getString("ua_family_icon_big"))); ret.setUaFamilyInfoUrl(nvl(rs.getString("ua_family_info_url"))); ret.setUaFamilyVendor(nvl(rs.getString("ua_family_vendor"))); ret.setUaFamilyVendorCode(nvl(rs.getString("ua_family_vendor_code"))); ret.setUaFamilyVendorHomepage(nvl(rs.getString("ua_family_vendor_homepage"))); ret.setUaUptodateCurrentVersion(nvl(rs.getString("ua_uptodate_current_version"))); ret.setUaVersion(nvl(rs.getString("ua_version"))); ret.setUaVersionMajor(nvl(rs.getString("ua_version_major"))); } private void fetchOperatingSystem(ResultSet rs, UdgerUaResult ret) throws SQLException { ret.setOsFamily(nvl(rs.getString("os_family"))); ret.setOs(nvl(rs.getString("os"))); ret.setOsCode(nvl(rs.getString("os_code"))); ret.setOsFamilyCode(nvl(rs.getString("os_family_code"))); ret.setOsFamilyVendorHomepage(nvl(rs.getString("os_family_vendor_homepage"))); ret.setOsFamilyVendor(nvl(rs.getString("os_family_vendor"))); ret.setOsFamilyVendorCode(nvl(rs.getString("os_family_vendor_code"))); ret.setOsHomePage(nvl(rs.getString("os_home_page"))); ret.setOsIcon(nvl(rs.getString("os_icon"))); ret.setOsIconBig(nvl(rs.getString("os_icon_big"))); ret.setOsInfoUrl(nvl(rs.getString("os_info_url"))); } private void fetchDevice(ResultSet rs, UdgerUaResult ret) throws SQLException { ret.setDeviceClass(nvl(rs.getString("device_class"))); ret.setDeviceClassCode(nvl(rs.getString("device_class_code"))); ret.setDeviceClassIcon(nvl(rs.getString("device_class_icon"))); ret.setDeviceClassIconBig(nvl(rs.getString("device_class_icon_big"))); ret.setDeviceClassInfoUrl(nvl(rs.getString("device_class_info_url"))); } private void patchVersions(Matcher lastPatternMatcher, UdgerUaResult ret) { if (lastPatternMatcher != null) { String version = ""; if (lastPatternMatcher.groupCount() >= 1) { version = lastPatternMatcher.group(1); if (version == null) { version = ""; } } ret.setUaVersion(version); String versionSegments[] = version.split("\\."); if (versionSegments.length > 0) { ret.setUaVersionMajor(version.split("\\.")[0]); } else { ret.setUaVersionMajor(""); } ret.setUa((ret.getUa() != null ? ret.getUa() : "") + " " + version); } else { ret.setUaVersion(""); ret.setUaVersionMajor(""); } } private void fetchUdgerIp(ResultSet rs, UdgerIpResult ret) throws SQLException { ret.setCrawlerCategory(nvl(rs.getString("crawler_category"))); ret.setCrawlerCategoryCode(nvl(rs.getString("crawler_category_code"))); ret.setCrawlerFamily(nvl(rs.getString("crawler_family"))); ret.setCrawlerFamilyCode(nvl(rs.getString("crawler_family_code"))); ret.setCrawlerFamilyHomepage(nvl(rs.getString("crawler_family_homepage"))); ret.setCrawlerFamilyIcon(nvl(rs.getString("crawler_family_icon"))); ret.setCrawlerFamilyInfoUrl(nvl(rs.getString("crawler_family_info_url"))); ret.setCrawlerFamilyVendor(nvl(rs.getString("crawler_family_vendor"))); ret.setCrawlerFamilyVendorCode(nvl(rs.getString("crawler_family_vendor_code"))); ret.setCrawlerFamilyVendorHomepage(nvl(rs.getString("crawler_family_vendor_homepage"))); ret.setCrawlerLastSeen(nvl(rs.getString("crawler_last_seen"))); ret.setCrawlerName(nvl(rs.getString("crawler_name"))); ret.setCrawlerRespectRobotstxt(nvl(rs.getString("crawler_respect_robotstxt"))); ret.setCrawlerVer(nvl(rs.getString("crawler_ver"))); ret.setCrawlerVerMajor(nvl(rs.getString("crawler_ver_major"))); ret.setIpCity(nvl(rs.getString("ip_city"))); ret.setIpClassification(nvl(rs.getString("ip_classification"))); ret.setIpClassificationCode(nvl(rs.getString("ip_classification_code"))); ret.setIpCountry(nvl(rs.getString("ip_country"))); ret.setIpCountryCode(nvl(rs.getString("ip_country_code"))); ret.setIpHostname(nvl(rs.getString("ip_hostname"))); ret.setIpLastSeen(nvl(rs.getString("ip_last_seen"))); } private String nvl(String v) { return v != null ? v : ""; } private void fetchDataCenter(ResultSet rs, UdgerIpResult ret) throws SQLException { ret.setDataCenterHomePage(nvl(rs.getString("datacenter_homepage"))); ret.setDataCenterName(nvl(rs.getString("datacenter_name"))); ret.setDataCenterNameCode(nvl(rs.getString("datacenter_name_code"))); } }