Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added multithreaded version of caching parser #24

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
<artifactId>snakeyaml</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's newer versions of this. Lets update to 31.1-jre

</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
127 changes: 127 additions & 0 deletions src/main/java/ua_parser/ConcurrentCachingParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package ua_parser;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import javax.annotation.ParametersAreNonnullByDefault;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;

/**
* This class is concurrent version of CachingParser.
*
* @author kyryloholodnov
*/
public class ConcurrentCachingParser extends Parser {

private static long cacheKeyExpiresAfterAccessMs = 24 * 60L * 60 * 1000; // 24 hours
private static long cacheMaximumSize = 1000;

private final LoadingCache<String, Client> cacheClient = CacheBuilder.newBuilder()
.expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS)
.maximumSize(cacheMaximumSize)
.build(new CacheLoader<String, Client>() {
@Override
@ParametersAreNonnullByDefault
public Client load(String agentString) throws Exception {
return ConcurrentCachingParser.super.parse(agentString);
}
});

private final LoadingCache<String, UserAgent> cacheUserAgent = CacheBuilder.newBuilder()
.expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS)
.maximumSize(cacheMaximumSize)
.build(new CacheLoader<String, UserAgent>() {
@Override
@ParametersAreNonnullByDefault
public UserAgent load(String agentString) throws Exception {
return ConcurrentCachingParser.super.parseUserAgent(agentString);
}
});

private final LoadingCache<String, Device> cacheDevice = CacheBuilder.newBuilder()
.expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS)
.maximumSize(cacheMaximumSize)
.build(new CacheLoader<String, Device>() {
@Override
@ParametersAreNonnullByDefault
public Device load(String agentString) throws Exception {
return ConcurrentCachingParser.super.parseDevice(agentString);
}
});

private final LoadingCache<String, OS> cacheOS = CacheBuilder.newBuilder()
.expireAfterAccess(cacheKeyExpiresAfterAccessMs, TimeUnit.MILLISECONDS)
.maximumSize(cacheMaximumSize)
.build(new CacheLoader<String, OS>() {
@Override
@ParametersAreNonnullByDefault
public OS load(String agentString) throws Exception {
return ConcurrentCachingParser.super.parseOS(agentString);
}
});

public ConcurrentCachingParser() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove throws IOException

super();
}

public ConcurrentCachingParser(InputStream regexYaml) {
super(regexYaml);
}

@Override
public Client parse(String agentString) {
if (agentString == null) {
return null;
}
return cacheClient.getUnchecked(agentString);
}

@Override
public UserAgent parseUserAgent(String agentString) {
if (agentString == null) {
return null;
}
return cacheUserAgent.getUnchecked(agentString);
}

@Override
public Device parseDevice(String agentString) {
if (agentString == null) {
return null;
}
return cacheDevice.getUnchecked(agentString);
}

@Override
public OS parseOS(String agentString) {
if (agentString == null) {
return null;
}
return cacheOS.getUnchecked(agentString);
}

public static long getCacheKeyExpiresAfterAccessMs() {
return cacheKeyExpiresAfterAccessMs;
}

public static long getCacheMaximumSize() {
return cacheMaximumSize;
}

public static void setCacheMaximumSize(long cacheMaximumSize) {
if (cacheMaximumSize < 1) {
throw new IllegalArgumentException("Cache size should be positive value");
}
ConcurrentCachingParser.cacheMaximumSize = cacheMaximumSize;
}

public static void setCacheKeyExpiresAfterAccessMs(long cacheKeyExpiresAfterAccessMs) {
if (cacheKeyExpiresAfterAccessMs < 1) {
throw new IllegalArgumentException("Cache key expiration should be positive value");
}
ConcurrentCachingParser.cacheKeyExpiresAfterAccessMs = cacheKeyExpiresAfterAccessMs;
}
}
86 changes: 86 additions & 0 deletions src/test/java/ua_parser/ConcurrentCachingParserTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package ua_parser;

import org.junit.Before;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
* These tests really only redo the same tests as in ParserTest but with a
* different Parser subclass Also the same tests will be run several times on
* the same user agents to validate the caching works correctly.
*
* @author kyryloholodnov
*/
public class ConcurrentCachingParserTest extends ParserTest {

@Before
public void initParser() throws Exception {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove throws Exception

parser = new ConcurrentCachingParser();
}

@Override
Parser parserFromStringConfig(String configYamlAsString) throws Exception {
InputStream yamlInput = new ByteArrayInputStream(
configYamlAsString.getBytes("UTF8"));
return new CachingParser(yamlInput);
}

@Test
public void testCachedParseUserAgent() {
super.testParseUserAgent();
super.testParseUserAgent();
super.testParseUserAgent();
}

@Test
public void testCachedParseOS() {
super.testParseOS();
super.testParseOS();
super.testParseOS();
}

@Test
public void testCachedParseAdditionalOS() {
super.testParseAdditionalOS();
super.testParseAdditionalOS();
super.testParseAdditionalOS();
}

@Test
public void testCachedParseDevice() {
super.testParseDevice();
super.testParseDevice();
super.testParseDevice();
}

@Test
public void testCachedParseFirefox() {
super.testParseFirefox();
super.testParseFirefox();
super.testParseFirefox();
}

@Test
public void testCachedParsePGTS() {
super.testParsePGTS();
super.testParsePGTS();
super.testParsePGTS();
}

@Test
public void testCachedParseAll() {
super.testParseAll();
super.testParseAll();
super.testParseAll();
}

@Test
public void testCachedReplacementQuoting() throws Exception {
super.testReplacementQuoting();
super.testReplacementQuoting();
super.testReplacementQuoting();
}

}