/*******************************************************************************
* Copyright 2011 André Rouél
*
* 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 net.sf.jacclog.uasparser.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;
import net.sf.jacclog.uasparser.internal.data.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This parser checks by every <code>read</code> call once a day, if newer data are available remotely. When newer data
* are available, they are downloaded, read and replaced by the current one.
*
* @author André Rouél
*/
public final class OnlineUserAgentStringParser extends UserAgentStringParser {
private static final Logger LOG = LoggerFactory.getLogger(OnlineUserAgentStringParser.class);
private static final String DATA_URL_KEY = "ini.data.url";
private static final String VERSION_URL_KEY = "ini.version.url";
private static final long DEFAULT_UPDATE_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
/**
* Reads all configuration properties from <code>config.properties</code> file and returns a <code>Properties</code>
* object.
*
* @return Configuration properties
* @throws IOException
*/
private static Properties readConfigProperties() throws IOException {
final Properties properties = new Properties();
final InputStream stream = OnlineUserAgentStringParser.class.getClassLoader().getResourceAsStream(
"config.properties");
properties.load(stream);
stream.close();
return properties;
}
/**
* Retrieves the current data version from <a
* href="http://user-agent-string.info">http://user-agent-string.info</a>.
*
* @return A version string
* @throws IOException
*/
public static String retrieveRemoteVersion(final URL url) throws IOException {
final InputStream stream = url.openStream();
try {
final byte[] buffer = new byte[4048];
final int len = stream.read(buffer);
return new String(buffer, 0, len);
} finally {
stream.close();
}
}
private long updateInterval = DEFAULT_UPDATE_INTERVAL;
private long lastUpdateCheck;
private final transient Properties configuration;
public OnlineUserAgentStringParser() {
this(OnlineUserAgentStringParser.class.getClassLoader().getResourceAsStream(DEFAULT_DATA));
}
/**
* This constructor is originally for the unit testing against an older <code>uas.ini</code> file.
*
* @param stream
* <code>InputStream</code> of default <code>Data</code>
*/
public OnlineUserAgentStringParser(final InputStream stream) {
super(stream);
// reading configuration
Properties configuration = new Properties();
try {
configuration = readConfigProperties();
} catch (final IOException e) {
LOG.info(e.getLocalizedMessage());
}
this.configuration = configuration;
// reading remote data
try {
final URL url = new URL(configuration.getProperty(DATA_URL_KEY));
retrieveRemoteData(url);
} catch (final MalformedURLException e) {
LOG.info(e.getLocalizedMessage());
} catch (final IOException e) {
LOG.info(e.getLocalizedMessage());
}
}
protected Properties getConfiguration() {
return configuration;
}
@Override
protected Data getData() {
try {
final URL url = new URL(configuration.getProperty(DATA_URL_KEY));
retrieveRemoteData(url);
} catch (final IOException e) {
LOG.info(e.getLocalizedMessage());
}
return super.getData();
}
public long getLastUpdateCheck() {
return lastUpdateCheck;
}
public long getUpdateInterval() {
return updateInterval;
}
/**
* Checks the version address <code>VERSION_URL</code> for updates.
*
* @return <code>true</code> if an update exists, otherwise <code>false</code>
*/
private boolean isUpdateAvailable() {
boolean result = false;
if (lastUpdateCheck == 0 || lastUpdateCheck < System.currentTimeMillis() - getUpdateInterval()) {
try {
final URL url = new URL(configuration.getProperty(VERSION_URL_KEY));
final String version = retrieveRemoteVersion(url);
if (getCurrentVersion() == null || version.compareTo(getCurrentVersion()) > 0) {
LOG.debug("An update is available. Current version is '" + getCurrentVersion()
+ "' and remote version is '" + version + "'.");
result = true;
} else {
LOG.debug("No update available. Current version is '" + getCurrentVersion() + "'.");
}
} catch (final IOException e) {
LOG.info(e.getLocalizedMessage());
}
lastUpdateCheck = System.currentTimeMillis();
} else {
LOG.debug("There is no check necessary because the update interval has not expired.");
}
return result;
}
/**
* Loads the data file from user-agent-string.info
*
* @throws IOException
*/
private void retrieveRemoteData(final URL url) throws IOException {
if (url == null) {
throw new IllegalArgumentException("Argument 'url' can not be null.");
}
if (isUpdateAvailable()) {
LOG.debug("Reading remote data...");
setData(getDataReader().read(url));
}
}
public void setLastUpdateCheck(final long lastUpdateCheck) {
this.lastUpdateCheck = lastUpdateCheck;
}
public void setUpdateInterval(final long updateInterval) {
this.updateInterval = updateInterval;
}
}