UserAgentDetector.java
/*
* Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
*
* 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 com.irurueta.server.commons.useragent;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.io.Closeable;
import java.lang.ref.SoftReference;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.uadetector.ReadableDeviceCategory;
import net.sf.uadetector.ReadableUserAgent;
import net.sf.uadetector.UserAgentStringParser;
import net.sf.uadetector.service.UADetectorServiceFactory;
/**
* Class to parse and process a user agent from a web browser, web crawler, bot, email client, RESt library, etc. This class will detect
* which kind of user agent made a request, operating system information of the requester, kind of device, etc.
*/
public class UserAgentDetector implements Closeable {
/**
* Logger for this class.
*/
private static final Logger LOG = Logger.getLogger(UserAgentDetector.class.
getName());
/**
* Singleton instance of UserAgentDetector stored in a soft reference (to keep it cached in memory unless memory is claimed).
*/
private static SoftReference<UserAgentDetector> mReference;
/**
* Indicates whether user agent detection is enabled or not.
*/
private boolean mEnabled;
/**
* Amount of user agents that are cached.
*/
private int mCacheSize;
/**
* Amount of time to keep user agents cached expressed in hours.
*/
private int mCacheExpirationTime;
/**
* Internal user agent string parser.
*/
private UserAgentStringParser mParser;
/**
* Cache to hold instantiated instances of radable user agents.
*/
private Cache<String, ReadableUserAgent> mCache;
/**
* Constructor. Creates and configures a UserAgentDetector instance.
*/
private UserAgentDetector() {
mEnabled = false;
try {
final UserAgentConfiguration cfg = UserAgentConfigurationFactory.
getInstance().configure();
mEnabled = cfg.isUserAgentDetectionEnabled();
if (mEnabled) {
mCacheSize = cfg.getUserAgentCacheSize();
mCacheExpirationTime =
cfg.getUserAgentCacheExpirationTimeHours();
mParser = UADetectorServiceFactory.getCachingAndUpdatingParser();
mCache = CacheBuilder.newBuilder().maximumSize(mCacheSize).
expireAfterWrite(mCacheExpirationTime, TimeUnit.HOURS).
build();
LOG.log(Level.INFO, "User agent detection is enabled");
} else {
LOG.log(Level.INFO, "User agent detection is disabled");
}
} catch (final Exception e) {
mEnabled = false;
LOG.log(Level.INFO, "User agent detection is disabled because " +
"configuration failed", e);
}
}
/**
* Factory method to return the singleton instance of UserAgentDetector based on current configuration.
*
* @return singleton instance.
*/
public static synchronized UserAgentDetector getInstance() {
UserAgentDetector detector;
if (mReference == null || (detector = mReference.get()) == null) {
detector = new UserAgentDetector();
mReference = new SoftReference<>(detector);
}
return detector;
}
/**
* Indicates whether user agent detection is enabled or not. If not enabled no user agent detection will be done when requested.
*
* @return true if user agent detection is enabled, false otherwise.
*/
public boolean isEnabled() {
return mEnabled;
}
/**
* Amount of user agents that are cached. A cache of user agents is used to speed up the parsing process when user agents get repeated,
* which can happen if a user makes several requests to the server, or multiple users have the same user agent
*
* @return amount of user agents that are cached.
*/
public int getCacheSize() {
return mCacheSize;
}
/**
* Amount of time to keep user agents cached expressed in hours.
*
* @return amount of time to keep user agents cached expressed in hours.
*/
public int getCacheExpirationTime() {
return mCacheExpirationTime;
}
/**
* Detects data on provided user agent string. Detected data can be operating system, user agent type (browser, mail client, etc), user
* agent family, type of device, etc.
*
* @param userAgentString original user agent string being parsed
* @return detected user agent data.
* @throws UserAgentDetectionDisabledException if user agent detection is disabled.
* @throws UserAgentException if anything else fails.
*/
public UserAgentData detect(final String userAgentString) throws
UserAgentDetectionDisabledException, UserAgentException {
if (!mEnabled) {
throw new UserAgentDetectionDisabledException();
}
try {
ReadableUserAgent result = mCache.getIfPresent(userAgentString);
if (result == null) {
result = mParser.parse(userAgentString);
mCache.put(userAgentString, result);
}
final DeviceCategory deviceCategory = toDeviceCategory(
result.getDeviceCategory().getCategory());
final String deviceCategoryName = result.getDeviceCategory().getName();
final String family = result.getFamily().getName();
final OperatingSystemFamily osFamily = toOsFamily(result.getOperatingSystem().getFamily());
final String osFamilyName = result.getOperatingSystem().getFamilyName();
final String osName = result.getOperatingSystem().getName();
final String osProducer = result.getOperatingSystem().getProducer();
final String osVersion = result.getOperatingSystem().getVersionNumber().toVersionString();
final UserAgentType userAgentType = toUserAgentType(result.getType());
final String userAgentVersion = result.getVersionNumber().toVersionString();
return new UserAgentData(userAgentString, deviceCategory,
deviceCategoryName, family, osFamily, osFamilyName, osName,
osProducer, osVersion, userAgentType, userAgentVersion);
} catch (final Exception e) {
throw new UserAgentException(e);
}
}
/**
* Stops internal user agent parser. Once closed, user agent detection will no longer be available.
*/
@Override
public void close() {
if (mParser != null) {
mParser.shutdown();
}
if (mCache != null) {
mCache.invalidateAll();
}
mEnabled = false;
LOG.log(Level.INFO, "User agent detection has been shutdown");
}
/**
* Resets UserAgentDetector so a new instance having new configuration can be created.
*/
protected static synchronized void reset() {
final UserAgentDetector detector = mReference != null ?
mReference.get() : null;
if (detector != null) {
detector.close();
}
mReference = null;
}
/**
* This method is called on garbage collection. When this method is called, internal user agent parser will be stopped.
*
* @throws Throwable if anything fails.
*/
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
/**
* Converts an internal device category enumerator into a DeviceCategory enumerator used by this package.
*
* @param category internal category to be converted.
* @return a device category.
*/
protected DeviceCategory toDeviceCategory(
ReadableDeviceCategory.Category category) {
if (category != null) {
switch (category) {
case GAME_CONSOLE:
return DeviceCategory.GAME_CONSOLE;
case OTHER:
return DeviceCategory.OTHER;
case PDA:
return DeviceCategory.PDA;
case PERSONAL_COMPUTER:
return DeviceCategory.PERSONAL_COMPUTER;
case SMARTPHONE:
return DeviceCategory.SMARTPHONE;
case SMART_TV:
return DeviceCategory.SMART_TV;
case TABLET:
return DeviceCategory.TABLET;
case UNKNOWN:
return DeviceCategory.UNKNOWN;
case WEARABLE_COMPUTER:
return DeviceCategory.WEARABLE_COMPUTER;
default:
break;
}
}
return null;
}
/**
* Converts an internal OS family enumerator into an OperatingSystemFamily. enumerator used by this package.
*
* @param family internal Os family to be converted.
* @return an OS family.
*/
protected OperatingSystemFamily toOsFamily(
net.sf.uadetector.OperatingSystemFamily family) {
if (family != null) {
switch (family) {
case AIX:
return OperatingSystemFamily.AIX;
case AMIGA_OS:
return OperatingSystemFamily.AMIGA_OS;
case ANDROID:
return OperatingSystemFamily.ANDROID;
case AROS:
return OperatingSystemFamily.AROS;
case BADA:
return OperatingSystemFamily.BADA;
case BEOS:
return OperatingSystemFamily.BEOS;
case BLACKBERRY_OS:
return OperatingSystemFamily.BLACKBERRY_OS;
case BREW:
return OperatingSystemFamily.BREW;
case BSD:
return OperatingSystemFamily.BSD;
case DANGEROS:
return OperatingSystemFamily.DANGEROS;
case FIREFOX_OS:
return OperatingSystemFamily.FIREFOX_OS;
case HAIKU:
return OperatingSystemFamily.HAIKU;
case HPUX:
return OperatingSystemFamily.HPUX;
case INFERNO_OS:
return OperatingSystemFamily.INFERNO_OS;
case IOS:
return OperatingSystemFamily.IOS;
case IRIX:
return OperatingSystemFamily.IRIX;
case JVM:
return OperatingSystemFamily.JVM;
case LINUX:
return OperatingSystemFamily.LINUX;
case MAC_OS:
return OperatingSystemFamily.MAC_OS;
case MEEGO:
return OperatingSystemFamily.MEEGO;
case MINIX:
return OperatingSystemFamily.MINIX;
case MORPHOS:
return OperatingSystemFamily.MORPHOS;
case NINTENDO:
return OperatingSystemFamily.NINTENDO;
case OPENVMS:
return OperatingSystemFamily.OPENVMS;
case OS_2:
return OperatingSystemFamily.OS_2;
case OS_X:
return OperatingSystemFamily.OS_X;
case PALM_OS:
return OperatingSystemFamily.PALM_OS;
case PLAYSTATION_VITA:
return OperatingSystemFamily.PLAYSTATION_VITA;
case QNX:
return OperatingSystemFamily.QNX;
case RISC_OS:
return OperatingSystemFamily.RISC_OS;
case SAILFISH_OS:
return OperatingSystemFamily.SAILFISH_OS;
case SOLARIS:
return OperatingSystemFamily.SOLARIS;
case SYLLABLE:
return OperatingSystemFamily.SYLLABLE;
case SYMBIAN:
return OperatingSystemFamily.SYMBIAN;
case TIZEN:
return OperatingSystemFamily.TIZEN;
case UNKNOWN:
return OperatingSystemFamily.UNKNOWN;
case WEBOS:
return OperatingSystemFamily.WEBOS;
case WII_OS:
return OperatingSystemFamily.WII_OS;
case WINDOWS:
return OperatingSystemFamily.WINDOWS;
case XROSSMEDIABAR:
return OperatingSystemFamily.XROSSMEDIABAR;
default:
break;
}
}
return null;
}
/**
* Converts an internal user agent type enumerator into a UserAgentType enumerator used by this package.
*
* @param type internal user agent type to be converted.
* @return a user agent type.
*/
protected UserAgentType toUserAgentType(
final net.sf.uadetector.UserAgentType type) {
if (type != null) {
switch (type) {
case BROWSER:
return UserAgentType.BROWSER;
case EMAIL_CLIENT:
return UserAgentType.EMAIL_CLIENT;
case FEED_READER:
return UserAgentType.FEED_READER;
case LIBRARY:
return UserAgentType.LIBRARY;
case MEDIAPLAYER:
return UserAgentType.MEDIAPLAYER;
case MOBILE_BROWSER:
return UserAgentType.MOBILE_BROWSER;
case OFFLINE_BROWSER:
return UserAgentType.OFFLINE_BROWSER;
case OTHER:
return UserAgentType.OTHER;
case ROBOT:
return UserAgentType.ROBOT;
case UNKNOWN:
return UserAgentType.UNKNOWN;
case USERAGENT_ANONYMIZER:
return UserAgentType.USERAGENT_ANONYMIZER;
case VALIDATOR:
return UserAgentType.VALIDATOR;
case WAP_BROWSER:
return UserAgentType.WAP_BROWSER;
default:
break;
}
}
return null;
}
}