org.echocat.jomon.net.http.browsers.BrowserDefinitionFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.echocat.jomon.net.http.browsers.BrowserDefinitionFactory.java

Source

/*****************************************************************************************
 * *** BEGIN LICENSE BLOCK *****
 *
 * Version: MPL 2.0
 *
 * echocat Jomon, Copyright (c) 2012-2013 echocat
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * *** END LICENSE BLOCK *****
 ****************************************************************************************/

package org.echocat.jomon.net.http.browsers;

import org.echocat.jomon.cache.Cache;
import org.echocat.jomon.cache.ClearableCache;
import org.echocat.jomon.cache.LimitedCache;
import org.echocat.jomon.cache.LruCache;
import org.echocat.jomon.cache.management.CacheProvider;
import org.echocat.jomon.net.http.browsers.BrowserDefinitionParser.Row;
import org.echocat.jomon.net.http.browsers.BrowserDefinitionParser.Rows;
import org.echocat.jomon.runtime.util.Duration;
import org.echocat.jomon.runtime.util.Entry;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.net.URL;
import java.util.*;

import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.echocat.jomon.cache.management.DefaultCacheDefinition.lruCache;

/**
 * <p>Return {@link BrowserDefinition} which contains information on browser version and properties.
 * It loads its definitions by invoking {@link #init()} which retrieves its values from {@link BrowserDefinitionParser}.</p>
 *
 * <p>Many thanks to Gary Keith, who is maintaining this list of browser.
 * See <a href="http://browsers.garykeith.com/downloads.asp">Gary Keith's browser capabilities project</a></p>
 */
public class BrowserDefinitionFactory {

    private URL _browserDefinitionFile = BrowserDefinitionParser.DEFAULT_BROWSER_DEFINITION_FILE;

    private final Cache<String, BrowserDefinition> _cache;

    public BrowserDefinitionFactory(@Nonnull CacheProvider cacheProvider) {
        _cache = cacheProvider.provide(getClass(), "definitions",
                lruCache(String.class, BrowserDefinition.class).withCapacity(10000).withMaximumLifetime("1d"));
    }

    public BrowserDefinitionFactory() {
        _cache = new LruCache<>(String.class, BrowserDefinition.class);
        ((LimitedCache<String, BrowserDefinition>) _cache).setMaximumLifetime(new Duration("1d"));
        ((LimitedCache<String, BrowserDefinition>) _cache).setCapacity(10000L);
    }

    private List<BrowserDefinition> _browserDefinitions;

    public void setBrowserDefinitionFile(@Nonnull URL browserDefinitionFile) {
        _browserDefinitionFile = browserDefinitionFile;
    }

    @PostConstruct
    public void init() throws Exception {
        if (_cache instanceof ClearableCache) {
            ((ClearableCache) _cache).clear();
        }
        try (final Rows rows = new BrowserDefinitionParser(_browserDefinitionFile).iterator()) {
            _browserDefinitions = parseRows(rows);
        }
    }

    @Nonnull
    public BrowserDefinition getByUserAgent(@Nonnull String userAgent) {
        BrowserDefinition browserDefinition = _cache.get(userAgent);
        if (browserDefinition == null) {
            browserDefinition = findFirstMatchInBrowserDefinitions(userAgent);
            _cache.put(userAgent, browserDefinition);
        }
        return browserDefinition;
    }

    @Nonnull
    private List<BrowserDefinition> parseRows(@Nonnull Rows rows) throws Exception {
        final List<BrowserDefinition> browserDefinitions = new ArrayList<>();
        final Map<String, Map<String, String>> parentUserAgentToProperties = new HashMap<>();
        while (rows.hasNext()) {
            final Row row = rows.next();
            final BrowserDefinition browserDefinition = createBrowserDefinitionFrom(row,
                    parentUserAgentToProperties);
            if (browserDefinition.isParent()) {
                parentUserAgentToProperties.put(browserDefinition.getUserAgent(),
                        browserDefinition.getProperties());
            }
            browserDefinitions.add(browserDefinition);
        }
        return browserDefinitions;
    }

    @Nonnull
    private BrowserDefinition createBrowserDefinitionFrom(@Nonnull Row row,
            @Nonnull Map<String, Map<String, String>> parentUserAgentToProperties) throws Exception {
        final String plainUserAgent = row.getValue("PropertyName");
        if (plainUserAgent == null) {
            throw new IllegalAccessException("The provided " + row + " contains a null UserAgent.");
        }
        final String userAgent = normalizeUserAgent(plainUserAgent);
        final String parentUserAgent = row.getValue("Parent");
        if (parentUserAgent == null) {
            throw new IllegalAccessException("The provided " + row + " contains a null Parent.");
        }
        final Map<String, String> properties = new HashMap<>();
        enrichWithPropertiesOfParentIfPossible(properties, userAgent, parentUserAgent, parentUserAgentToProperties);
        enrichWithPropertiesFromRow(properties, row);
        return new BrowserDefinition(userAgent, properties, parentUserAgent);
    }

    private void enrichWithPropertiesFromRow(@Nonnull Map<String, String> properties, @Nonnull Row row) {
        for (final Entry<String, String> entry : row) {
            final String value = entry.getValue();
            if (!"default".equals(value) && !isEmpty(value)) {
                properties.put(entry.getKey(), value);
            }
        }
    }

    private void enrichWithPropertiesOfParentIfPossible(@Nonnull Map<String, String> properties,
            @Nonnull String actualUserAgent, @Nullable String parentUserAgent,
            @Nonnull Map<String, Map<String, String>> parentUserAgentToProperties) {
        if (parentUserAgent != null && !parentUserAgent.equals(actualUserAgent)) {
            final Map<String, String> parentUserAgentProperties = parentUserAgentToProperties.get(parentUserAgent);
            if (parentUserAgentProperties != null) {
                properties.putAll(parentUserAgentProperties);
            }
        }
    }

    @Nonnull
    private String normalizeUserAgent(@Nonnull String plainUserAgent) {
        final String userAgent;
        if (plainUserAgent.startsWith("[") && plainUserAgent.endsWith("]")) {
            userAgent = plainUserAgent.substring(1, plainUserAgent.length() - 1);
        } else {
            userAgent = plainUserAgent;
        }
        return userAgent;
    }

    @Nonnull
    private BrowserDefinition findFirstMatchInBrowserDefinitions(@Nonnull String userAgent) {
        BrowserDefinition result = null;
        final Iterator<BrowserDefinition> i = _browserDefinitions.iterator();
        // In case we find a mobile agent this browser definition is the result we look for even if it has no major version.
        // Otherwise we would not support
        while (i.hasNext()
                && (result == null || (isNotMobileOrTabletDevice(result) && result.getMajorVersion() == null))) {
            final BrowserDefinition browserDefinition = i.next();
            if (browserDefinition.getGlob().matches(userAgent)) {
                if (result == null || !"Default Browser".equals(browserDefinition.getBrowserName())) {
                    result = browserDefinition;
                }
            }
        }
        if (result == null) {
            throw new IllegalStateException(
                    "Could not find any browserDefinition for userAgent '" + userAgent + "'?");
        }
        return result;
    }

    private boolean isNotMobileOrTabletDevice(BrowserDefinition result) {
        return !result.isMobileDevice() && !result.isTablet();
    }
}