You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2019/05/15 17:35:07 UTC

[unomi] branch master updated: [UNOMI-228] Replace user agent detector library

This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/master by this push:
     new 9901d95  [UNOMI-228] Replace user agent detector library
9901d95 is described below

commit 9901d95898c515942f754255ba2bd3d6fcbdbbf3
Author: Francois Papon <fp...@apache.org>
AuthorDate: Sat Apr 20 19:53:20 2019 +0400

    [UNOMI-228] Replace user agent detector library
---
 itests/pom.xml                                     |  33 +------
 .../test/java/org/apache/unomi/itests/BaseIT.java  |  40 +-------
 package/pom.xml                                    |  13 +--
 performance-tests/pom.xml                          |  36 +------
 .../apache/unomi/performancetests/BasicTest.java   |  52 ++---------
 plugins/request/pom.xml                            |  86 ++++++++++++++---
 .../request/actions/SetRemoteHostInfoAction.java   |  52 ++++++-----
 .../unomi/plugins/request/useragent/UserAgent.java |  86 +++++++++++++++++
 .../useragent/UserAgentDetectorServiceImpl.java    |  76 +++++++++++++++
 .../resources/OSGI-INF/blueprint/blueprint.xml     |   6 ++
 .../request/actions/UserAgentDetectorTest.java     | 104 +++++++++++++++++++++
 pom.xml                                            |  34 ++++++-
 services/pom.xml                                   |  14 +++
 .../resources/OSGI-INF/blueprint/blueprint.xml     |   1 -
 14 files changed, 439 insertions(+), 194 deletions(-)

diff --git a/itests/pom.xml b/itests/pom.xml
index fad2656..5f04b9a 100644
--- a/itests/pom.xml
+++ b/itests/pom.xml
@@ -30,10 +30,9 @@
     <dependencies>
         <dependency>
             <groupId>org.apache.unomi</groupId>
-            <artifactId>unomi-kar</artifactId>
-            <classifier>features</classifier>
+            <artifactId>unomi</artifactId>
             <version>${project.version}</version>
-            <type>xml</type>
+            <type>tar.gz</type>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -64,34 +63,6 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>standard</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf.cellar</groupId>
-            <artifactId>apache-karaf-cellar</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.ops4j.pax.web</groupId>
-            <artifactId>pax-web-features</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.cxf.karaf</groupId>
-            <artifactId>apache-cxf</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <scope>provided</scope>
diff --git a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
index eede503..039fb51 100644
--- a/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
+++ b/itests/src/test/java/org/apache/unomi/itests/BaseIT.java
@@ -47,42 +47,13 @@ public abstract class BaseIT {
 
     @Configuration
     public Option[] config() throws InterruptedException {
-        MavenArtifactUrlReference karafUrl = maven()
-                .groupId("org.apache.karaf")
-                .artifactId("apache-karaf")
-                .version("4.1.5")
-                .type("tar.gz");
 
-        MavenUrlReference karafStandardRepo = maven()
-                .groupId("org.apache.karaf.features")
-                .artifactId("standard")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafCellarRepo = maven()
-                .groupId("org.apache.karaf.cellar")
-                .artifactId("apache-karaf-cellar")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafPaxWebRepo = maven()
-                .groupId("org.ops4j.pax.web")
-                .artifactId("pax-web-features")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafCxfRepo = maven()
-                .groupId("org.apache.cxf.karaf")
-                .artifactId("apache-cxf")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference contextServerRepo = maven()
+        MavenArtifactUrlReference karafUrl = maven()
                 .groupId("org.apache.unomi")
-                .artifactId("unomi-kar")
-                .classifier("features")
-                .type("xml")
+                .artifactId("unomi")
+                .type("tar.gz")
                 .versionAsInProject();
+
         MavenUrlReference routerRepo = maven()
                 .groupId("org.apache.unomi")
                 .artifactId("unomi-router-karaf-feature")
@@ -132,9 +103,6 @@ public abstract class BaseIT {
                 systemProperty("org.apache.unomi.hazelcast.tcp-ip.members").value("127.0.0.1"),
                 systemProperty("org.apache.unomi.hazelcast.tcp-ip.interface").value("127.0.0.1"),
                 systemProperty("unomi.autoStart").value("true"),
-                features(karafCxfRepo, "cxf"),
-                features(karafCellarRepo, "cellar"),
-                features(contextServerRepo, "unomi-kar"),
                 features(routerRepo, "unomi-router-karaf-feature"),
                 CoreOptions.bundleStartLevel(100),
                 CoreOptions.frameworkStartLevel(100)
diff --git a/package/pom.xml b/package/pom.xml
index 9dad3f6..1cb567a 100644
--- a/package/pom.xml
+++ b/package/pom.xml
@@ -345,6 +345,7 @@
                         <library>mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.stax-api-1.2/${servicemix.specs.version};type:=endorsed;export:=true</library>
                         <library>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xalan/${xalan.bundle.version};type:=endorsed;export:=true</library>
                         <library>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xalan-serializer/${xalan-serializer.bundle.version};type:=endorsed;export:=true</library>
+                        <library>mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.jsr305/${jsr305.bundle.version};type:=endorsed;export:=true</library>
                         <library>mvn:javax.annotation/javax.annotation-api/1.2;type:=endorsed;export:=true</library>
 
                         <library>mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.activator/${servicemix.specs.version};type:=default;export:=true</library>
@@ -361,18 +362,6 @@
 
     <profiles>
         <profile>
-            <id>integration-tests</id>
-            <dependencies>
-                <dependency>
-                    <groupId>org.apache.unomi</groupId>
-                    <artifactId>unomi-itests</artifactId>
-                    <version>${project.version}</version>
-                    <classifier>features</classifier>
-                    <scope>test</scope>
-                </dependency>
-            </dependencies>
-        </profile>
-        <profile>
             <id>src</id>
             <build>
                 <plugins>
diff --git a/performance-tests/pom.xml b/performance-tests/pom.xml
index 9371753..b844b39 100644
--- a/performance-tests/pom.xml
+++ b/performance-tests/pom.xml
@@ -34,41 +34,11 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>standard</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-        </dependency>
-        <dependency>
-            <groupId>org.ops4j.pax.web</groupId>
-            <artifactId>pax-web-features</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>enterprise</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.karaf.features</groupId>
-            <artifactId>spring</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.cxf.karaf</groupId>
-            <artifactId>apache-cxf</artifactId>
-            <classifier>features</classifier>
-            <type>xml</type>
-        </dependency>
-        <dependency>
             <groupId>org.apache.unomi</groupId>
-            <artifactId>unomi-kar</artifactId>
-            <classifier>features</classifier>
+            <artifactId>unomi</artifactId>
             <version>${project.version}</version>
-            <type>xml</type>
+            <type>tar.gz</type>
+            <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/performance-tests/src/test/java/org/apache/unomi/performancetests/BasicTest.java b/performance-tests/src/test/java/org/apache/unomi/performancetests/BasicTest.java
index f260109..be3d7fe 100644
--- a/performance-tests/src/test/java/org/apache/unomi/performancetests/BasicTest.java
+++ b/performance-tests/src/test/java/org/apache/unomi/performancetests/BasicTest.java
@@ -80,59 +80,25 @@ public class BasicTest {
     @Configuration
     public Option[] config() {
         MavenArtifactUrlReference karafUrl = maven()
-                .groupId("org.apache.karaf")
-                .artifactId("apache-karaf")
-                .version("4.1.5")
-                .type("tar.gz");
-
-        MavenUrlReference karafStandardRepo = maven()
-                .groupId("org.apache.karaf.features")
-                .artifactId("standard")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafPaxWebRepo = maven()
-                .groupId("org.ops4j.pax.web")
-                .artifactId("pax-web-features")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafSpringRepo = maven()
-                .groupId("org.apache.karaf.features")
-                .artifactId("spring")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafCxfRepo = maven()
-                .groupId("org.apache.cxf.karaf")
-                .artifactId("apache-cxf")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference karafEnterpriseRepo = maven()
-                .groupId("org.apache.karaf.features")
-                .artifactId("enterprise")
-                .classifier("features")
-                .type("xml")
-                .versionAsInProject();
-        MavenUrlReference contextServerRepo = maven()
                 .groupId("org.apache.unomi")
-                .artifactId("unomi-kar")
-                .classifier("features")
-                .type("xml")
+                .artifactId("unomi")
+                .type("tar.gz")
                 .versionAsInProject();
 
+        String localRepository = System.getProperty("org.ops4j.pax.url.mvn.localRepository");
+        if (localRepository == null) {
+            localRepository = "";
+        }
+
         return new Option[]{
                 KarafDistributionOption.debugConfiguration("5005", false),
+                KarafDistributionOption.logLevel(LogLevel.INFO),
+                KarafDistributionOption.editConfigurationFilePut("etc/org.ops4j.pax.url.mvn.cfg", "org.ops4j.pax.url.mvn.localRepository", localRepository),
                 karafDistributionConfiguration()
                         .frameworkUrl(karafUrl)
                         .unpackDirectory(new File("target/exam"))
                         .useDeployFolder(false),
                 keepRuntimeFolder(),
-                KarafDistributionOption.features(karafStandardRepo, "wrap")
-                KarafDistributionOption.features(karafPaxWebRepo, "war"),
-                KarafDistributionOption.features(karafCxfRepo, "cxf"),
-                KarafDistributionOption.features(contextServerRepo, "unomi-kar"),
                 // we need to wrap the HttpComponents libraries ourselves since the OSGi bundles provided by the project are incorrect
                 wrappedBundle(mavenBundle("org.apache.httpcomponents",
                         "httpcore").versionAsInProject()),
diff --git a/plugins/request/pom.xml b/plugins/request/pom.xml
index 6c91d13..9fde026 100644
--- a/plugins/request/pom.xml
+++ b/plugins/request/pom.xml
@@ -30,6 +30,14 @@
     <description>Request reading actions plugin for the Apache Unomi Context Server</description>
     <packaging>bundle</packaging>
 
+    <properties>
+        <yauaa.version>5.9</yauaa.version>
+        <kryo.version>2.24.0</kryo.version>
+        <minlog.version>1.3.1</minlog.version>
+        <prefixmap>1.1</prefixmap>
+        <objenesis.version>2.1</objenesis.version>
+    </properties>
+
     <dependencies>
         <dependency>
             <groupId>javax.servlet</groupId>
@@ -43,18 +51,6 @@
         </dependency>
 
         <dependency>
-            <groupId>net.sf.uadetector</groupId>
-            <artifactId>uadetector-resources</artifactId>
-            <version>2014.11-jahia1</version>
-        </dependency>
-
-        <dependency>
-            <groupId>net.sf.uadetector</groupId>
-            <artifactId>uadetector-core</artifactId>
-            <version>0.9.23-jahia1</version>
-        </dependency>
-
-        <dependency>
             <groupId>net.sf.qualitycheck</groupId>
             <artifactId>quality-check</artifactId>
             <version>1.3</version>
@@ -104,6 +100,66 @@
             <version>1.3</version>
         </dependency>
 
+        <dependency>
+            <groupId>nl.basjes.parse.useragent</groupId>
+            <artifactId>yauaa</artifactId>
+            <version>${yauaa.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.esotericsoftware.kryo</groupId>
+            <artifactId>kryo</artifactId>
+            <version>${kryo.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.esotericsoftware</groupId>
+            <artifactId>minlog</artifactId>
+            <version>${minlog.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>nl.basjes.collections</groupId>
+            <artifactId>prefixmap</artifactId>
+            <version>1.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-text</artifactId>
+            <version>1.6</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-collections4</artifactId>
+            <version>4.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.objenesis</groupId>
+            <artifactId>objenesis</artifactId>
+            <version>${objenesis.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.6.6</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -120,6 +176,12 @@
                             org.apache.log;resolution:=optional,
                             org.apache.log4j;resolution:=optional,
                             sun.misc;resolution:=optional,
+                            sun.nio.ch;resolution:=optional,
+                            kotlin.reflect;resolution:=optional,
+                            kotlin.reflect.jvm;resolution:=optional,
+                            nl.basjes.shaded.com.google.errorprone.annotations;resolution:=optional,
+                            nl.basjes.shaded.com.google.errorprone.annotations.concurrent;resolution:=optional,
+                            org.checkerframework.checker.nullness.qual;resolution:=optional,
                             *
                         </Import-Package>
                     </instructions>
diff --git a/plugins/request/src/main/java/org/apache/unomi/plugins/request/actions/SetRemoteHostInfoAction.java b/plugins/request/src/main/java/org/apache/unomi/plugins/request/actions/SetRemoteHostInfoAction.java
index 05687e8..cc1c5ae 100644
--- a/plugins/request/src/main/java/org/apache/unomi/plugins/request/actions/SetRemoteHostInfoAction.java
+++ b/plugins/request/src/main/java/org/apache/unomi/plugins/request/actions/SetRemoteHostInfoAction.java
@@ -20,20 +20,6 @@ package org.apache.unomi.plugins.request.actions;
 import com.maxmind.geoip2.DatabaseReader;
 import com.maxmind.geoip2.exception.GeoIp2Exception;
 import com.maxmind.geoip2.model.CityResponse;
-import net.sf.uadetector.ReadableUserAgent;
-import net.sf.uadetector.UserAgentStringParser;
-import net.sf.uadetector.service.UADetectorServiceFactory;
-import org.apache.http.conn.util.InetAddressUtils;
-import org.apache.unomi.api.Event;
-import org.apache.unomi.api.Session;
-import org.apache.unomi.api.actions.Action;
-import org.apache.unomi.api.actions.ActionExecutor;
-import org.apache.unomi.api.services.EventService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.PostConstruct;
-import javax.servlet.http.HttpServletRequest;
 import java.io.File;
 import java.io.IOException;
 import java.net.InetAddress;
@@ -42,10 +28,24 @@ import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.HashMap;
 import java.util.Map;
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.http.conn.util.InetAddressUtils;
+import org.apache.unomi.api.Event;
+import org.apache.unomi.api.Session;
+import org.apache.unomi.api.actions.Action;
+import org.apache.unomi.api.actions.ActionExecutor;
+import org.apache.unomi.api.services.EventService;
+import org.apache.unomi.plugins.request.useragent.UserAgent;
+import org.apache.unomi.plugins.request.useragent.UserAgentDetectorServiceImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class SetRemoteHostInfoAction implements ActionExecutor {
     private static final Logger logger = LoggerFactory.getLogger(SetRemoteHostInfoAction.class.getName());
 
+    private UserAgentDetectorServiceImpl userAgentDetectorService;
+
     private DatabaseReader databaseReader;
     private String pathToGeoLocationDatabase;
 
@@ -58,6 +58,14 @@ public class SetRemoteHostInfoAction implements ActionExecutor {
     private double defaultLatitude = 46.1884341;
     private double defaultLongitude = 6.1282508;
 
+    public UserAgentDetectorServiceImpl getUserAgentDetectorService() {
+        return userAgentDetectorService;
+    }
+
+    public void setUserAgentDetectorService(UserAgentDetectorServiceImpl userAgentDetectorService) {
+        this.userAgentDetectorService = userAgentDetectorService;
+    }
+
     public void setPathToGeoLocationDatabase(String pathToGeoLocationDatabase) {
         this.pathToGeoLocationDatabase = pathToGeoLocationDatabase;
     }
@@ -150,14 +158,14 @@ public class SetRemoteHostInfoAction implements ActionExecutor {
             logger.error("Cannot lookup IP", e);
         }
 
-        UserAgentStringParser parser = UADetectorServiceFactory.getResourceModuleParser();
-        ReadableUserAgent agent = parser.parse(httpServletRequest.getHeader("User-Agent"));
-        session.setProperty("operatingSystemFamily", agent.getOperatingSystem().getFamilyName());
-        session.setProperty("operatingSystemName", agent.getOperatingSystem().getName());
-        session.setProperty("userAgentName", agent.getName());
-        session.setProperty("userAgentVersion", agent.getVersionNumber().toVersionString());
-        session.setProperty("userAgentNameAndVersion", session.getProperty("userAgentName") + "@@" + session.getProperty("userAgentVersion"));
-        session.setProperty("deviceCategory", agent.getDeviceCategory().getName());
+
+        UserAgent agent =userAgentDetectorService.parseUserAgent(httpServletRequest.getHeader("User-Agent"));
+        session.setProperty("operatingSystemFamily", agent.getOperatingSystemFamily());
+        session.setProperty("operatingSystemName", agent.getOperatingSystemName());
+        session.setProperty("userAgentName", agent.getUserAgentName());
+        session.setProperty("userAgentVersion", agent.getUserAgentVersion());
+        session.setProperty("userAgentNameAndVersion", agent.getUserAgentNameAndVersion());
+        session.setProperty("deviceCategory", agent.getDeviceCategory());
 
         return EventService.SESSION_UPDATED;
     }
diff --git a/plugins/request/src/main/java/org/apache/unomi/plugins/request/useragent/UserAgent.java b/plugins/request/src/main/java/org/apache/unomi/plugins/request/useragent/UserAgent.java
new file mode 100644
index 0000000..69f83b8
--- /dev/null
+++ b/plugins/request/src/main/java/org/apache/unomi/plugins/request/useragent/UserAgent.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.unomi.plugins.request.useragent;
+
+/**
+ * Basic information about a User Agent
+ */
+public class UserAgent {
+
+    private String operatingSystemFamily;
+    private String operatingSystemName;
+    private String userAgentName;
+    private String userAgentVersion;
+    private String deviceCategory;
+
+    public String getOperatingSystemFamily() {
+        return operatingSystemFamily;
+    }
+
+    public void setOperatingSystemFamily(String operatingSystemFamily) {
+        this.operatingSystemFamily = operatingSystemFamily;
+    }
+
+    public String getOperatingSystemName() {
+        return operatingSystemName;
+    }
+
+    public void setOperatingSystemName(String operatingSystemName) {
+        this.operatingSystemName = operatingSystemName;
+    }
+
+    public String getUserAgentName() {
+        return userAgentName;
+    }
+
+    public void setUserAgentName(String userAgentName) {
+        this.userAgentName = userAgentName;
+    }
+
+    public String getUserAgentVersion() {
+        return userAgentVersion;
+    }
+
+    public void setUserAgentVersion(String userAgentVersion) {
+        this.userAgentVersion = userAgentVersion;
+    }
+
+    public String getDeviceCategory() {
+        return deviceCategory;
+    }
+
+    public void setDeviceCategory(String deviceCategory) {
+        this.deviceCategory = deviceCategory;
+    }
+
+    public String getUserAgentNameAndVersion() {
+        return this.userAgentName + "@@" + this.userAgentVersion;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("User-Agent { \n");
+        sb.append("agent.name: " + this.getUserAgentName() + ",\n");
+        sb.append("agent.version: " + this.getUserAgentVersion() + ",\n");
+        sb.append("operatingsystem.family: " + this.getOperatingSystemFamily() + ",\n");
+        sb.append("operatingsystem.name: " + this.getOperatingSystemName() + ",\n");
+        sb.append("device.category: " + this.getDeviceCategory() + " \n}");
+        return super.toString();
+    }
+}
diff --git a/plugins/request/src/main/java/org/apache/unomi/plugins/request/useragent/UserAgentDetectorServiceImpl.java b/plugins/request/src/main/java/org/apache/unomi/plugins/request/useragent/UserAgentDetectorServiceImpl.java
new file mode 100644
index 0000000..cba6a3f
--- /dev/null
+++ b/plugins/request/src/main/java/org/apache/unomi/plugins/request/useragent/UserAgentDetectorServiceImpl.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.unomi.plugins.request.useragent;
+
+import nl.basjes.parse.useragent.UserAgentAnalyzer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author fpapon@apache.org
+ */
+public class UserAgentDetectorServiceImpl {
+
+    private static final Logger logger = LoggerFactory.getLogger(UserAgentDetectorServiceImpl.class.getName());
+
+    private UserAgentAnalyzer userAgentAnalyzer;
+
+    public void postConstruct() {
+        ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+            this.userAgentAnalyzer = UserAgentAnalyzer
+                    .newBuilder()
+                    .hideMatcherLoadStats()
+                    .withCache(10000)
+                    .withField(nl.basjes.parse.useragent.UserAgent.OPERATING_SYSTEM_CLASS)
+                    .withField(nl.basjes.parse.useragent.UserAgent.OPERATING_SYSTEM_NAME)
+                    .withField(nl.basjes.parse.useragent.UserAgent.AGENT_NAME)
+                    .withField(nl.basjes.parse.useragent.UserAgent.AGENT_VERSION)
+                    .withField(nl.basjes.parse.useragent.UserAgent.DEVICE_CLASS)
+                    .build();
+            this.userAgentAnalyzer.immediateInitialization();
+            this.userAgentAnalyzer.initializeMatchers();
+        } finally {
+            Thread.currentThread().setContextClassLoader(tccl);
+        }
+        logger.info("UserAgentDetector service initialized.");
+    }
+
+    public void preDestroy() {
+        userAgentAnalyzer = null;
+        logger.info("UserAgentDetector service shutdown.");
+    }
+
+    public UserAgent parseUserAgent(String header) {
+        nl.basjes.parse.useragent.UserAgent yauaaAgent = userAgentAnalyzer.parse(header);
+
+        UserAgent userAgent = new UserAgent();
+        userAgent.setDeviceCategory(yauaaAgent.getValue(nl.basjes.parse.useragent.UserAgent.DEVICE_CLASS));
+        userAgent.setOperatingSystemFamily(yauaaAgent.getValue(nl.basjes.parse.useragent.UserAgent.OPERATING_SYSTEM_CLASS));
+        userAgent.setOperatingSystemName(yauaaAgent.getValue(nl.basjes.parse.useragent.UserAgent.OPERATING_SYSTEM_NAME));
+        userAgent.setUserAgentName(yauaaAgent.getValue(nl.basjes.parse.useragent.UserAgent.AGENT_NAME));
+        userAgent.setUserAgentVersion(yauaaAgent.getValue(nl.basjes.parse.useragent.UserAgent.AGENT_VERSION));
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(userAgent.toString());
+        }
+
+        return userAgent;
+    }
+}
diff --git a/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 7411399..9b48229 100644
--- a/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/plugins/request/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -67,6 +67,8 @@
         </service-properties>
         <bean class="org.apache.unomi.plugins.request.actions.SetRemoteHostInfoAction"
               init-method="postConstruct">
+            <property name="userAgentDetectorService" ref="userAgentDetectorServiceImpl"/>
+
             <property name="pathToGeoLocationDatabase" value="${request.ipDatabase.location}"/>
 
             <property name="defaultSessionCountryCode" value="${defaultSessionCountryCode}"/>
@@ -81,4 +83,8 @@
         </bean>
     </service>
 
+    <bean id="userAgentDetectorServiceImpl" class="org.apache.unomi.plugins.request.useragent.UserAgentDetectorServiceImpl"
+          init-method="postConstruct" destroy-method="preDestroy">
+    </bean>
+
 </blueprint>
diff --git a/plugins/request/src/test/java/org/apache/unomi/plugins/request/actions/UserAgentDetectorTest.java b/plugins/request/src/test/java/org/apache/unomi/plugins/request/actions/UserAgentDetectorTest.java
new file mode 100644
index 0000000..4733c4e
--- /dev/null
+++ b/plugins/request/src/test/java/org/apache/unomi/plugins/request/actions/UserAgentDetectorTest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.unomi.plugins.request.actions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.apache.unomi.plugins.request.useragent.UserAgent;
+import org.apache.unomi.plugins.request.useragent.UserAgentDetectorServiceImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class UserAgentDetectorTest {
+    
+    private static final Logger logger = LoggerFactory.getLogger(UserAgentDetectorTest.class);
+
+    private UserAgentDetectorServiceImpl userAgentDetectorService;
+
+    @Before
+    public void init() {
+        long start = System.currentTimeMillis();
+        this.userAgentDetectorService = new UserAgentDetectorServiceImpl();
+        this.userAgentDetectorService.postConstruct();
+        long end = System.currentTimeMillis();
+        logger.info("Duration starting user agent (in msec) > {}", (end - start));
+    }
+
+    @After
+    public void end() {
+        this.userAgentDetectorService.preDestroy();
+    }
+
+    @Test
+    public void testFirstUserAgentDetection() {
+        String header = "Mozilla/5.0 (Linux; Android 7.0; Nexus 6 Build/NBD90Z) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36";
+
+        long start = System.currentTimeMillis();
+        UserAgent agent = this.userAgentDetectorService.parseUserAgent(header);
+        long end = System.currentTimeMillis();
+        logger.info("Duration user agent parsing (in msec) > {}", (end - start));
+        logger.info(agent.toString());
+    }
+
+    @Test
+    public void testUserAgentDetectionPerformance() throws InterruptedException {
+        int workerCount = 5000000;
+        ExecutorService executorService = Executors.newFixedThreadPool(3000);
+
+        for (int cpt = 1; cpt < 6; cpt++) {
+            logger.info("Execution " + cpt + "/5");
+            executeWorker(executorService, workerCount);
+        }
+    }
+
+    private void executeWorker(ExecutorService executorService, int workerCount) throws InterruptedException {
+        List<Callable<Object>> callables = new ArrayList<>(workerCount);
+        long startTime = System.currentTimeMillis();
+        for (int i = 0; i < workerCount; i++) {
+            callables.add(new AgentWorker(this.userAgentDetectorService));
+        }
+        executorService.invokeAll(callables);
+        long totalTime = System.currentTimeMillis() - startTime;
+        logger.info("AgentWorker workers completed execution in " + totalTime + "ms");
+    }
+
+    private class AgentWorker implements Callable<Object> {
+
+        String header = "Mozilla/5.0 (Linux; Android 7.0; Nexus 6 Build/NBD90Z) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.124 Mobile Safari/537.36";
+        UserAgentDetectorServiceImpl service;
+
+        public AgentWorker(UserAgentDetectorServiceImpl userAgentDetectorService) {
+            this.service = userAgentDetectorService;
+        }
+
+        @Override
+        public Object call() {
+            this.service.parseUserAgent(header);
+            return null;
+        }
+    }
+
+}
diff --git a/pom.xml b/pom.xml
index 8ba671b..67f51f5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,7 @@
         <!-- Librairies for Karaf packaging -->
         <xerces.version>2.11.0_1</xerces.version>
         <servicemix.specs.version>2.9.0</servicemix.specs.version>
+        <jsr305.bundle.version>3.0.2_1</jsr305.bundle.version>
         <xalan.bundle.version>2.7.2_3</xalan.bundle.version>
         <xalan-serializer.bundle.version>2.7.2_1</xalan-serializer.bundle.version>
         <jna.version>4.5.0</jna.version>
@@ -281,10 +282,6 @@
             <name>Apache ServiceMix M2</name>
             <url>http://svn.apache.org/repos/asf/servicemix/m2-repo/</url>
         </repository>
-        <repository>
-            <id>jahia.3rdparty</id>
-            <url>https://devtools.jahia.com/nexus/content/repositories/thirdparty-releases/</url>
-        </repository>
         <!-- Apache snapshots -->
         <repository>
             <id>apache-snapshots</id>
@@ -323,6 +320,35 @@
         </profile>
 
         <profile>
+            <id>ci-build-itests</id>
+            <activation>
+                <property>
+                    <name>maven.repo.local</name>
+                </property>
+            </activation>
+            <modules>
+                <module>itests</module>
+            </modules>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <excludes>
+                                <exclude>MavenTest</exclude>
+                            </excludes>
+                            <systemPropertyVariables>
+                                <org.ops4j.pax.url.mvn.localRepository>${maven.repo.local}</org.ops4j.pax.url.mvn.localRepository>
+                                <org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
             <id>performance-tests</id>
             <activation>
                 <activeByDefault>false</activeByDefault>
diff --git a/services/pom.xml b/services/pom.xml
index 7c494ef..fa6e67d 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -139,6 +139,20 @@
             <version>4.3.0</version>
         </dependency>
 
+        <!-- Unit tests -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>1.6.6</version>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 9569e67..e09a38b 100644
--- a/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
+++ b/services/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@ -382,5 +382,4 @@
         </service-properties>
     </service>
 
-
 </blueprint>