/*
* Copyright 2014-2015 JKOOL, LLC.
*
* 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.jkoolcloud.tnt4j.repository;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.configuration.AbstractConfiguration;
import org.apache.commons.configuration.AbstractFileConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.event.ConfigurationErrorEvent;
import org.apache.commons.configuration.event.ConfigurationErrorListener;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
import com.jkoolcloud.tnt4j.config.ConfigException;
import com.jkoolcloud.tnt4j.config.Configurable;
import com.jkoolcloud.tnt4j.core.OpLevel;
import com.jkoolcloud.tnt4j.sink.DefaultEventSinkFactory;
import com.jkoolcloud.tnt4j.sink.EventSink;
import com.jkoolcloud.tnt4j.utils.Utils;
/**
* <p>This class implements a file based token repository based on a property file following
* the key=value pairs defined per line. File is auto-reloaded by default based on 20sec refresh
* time. The reload time can be changed by setting <code>tnt4j.file.respository.refresh</code> property</p>
*
* @see TokenRepository
*
* @version $Revision: 6 $
*
*/
public class FileTokenRepository implements TokenRepository, Configurable {
private static EventSink logger = DefaultEventSinkFactory.defaultEventSink(FileTokenRepository.class);
private static ConcurrentHashMap<TokenRepositoryListener, TokenConfigurationListener> LISTEN_MAP = new ConcurrentHashMap<TokenRepositoryListener, TokenConfigurationListener>(49);
private String configName = null;
private PropertiesConfiguration config = null;
protected Map<String, Object> settings = null;
private long refDelay = 20000;
/**
* Create file/property based token repository instance based on default
* file name or url specified by <code>tnt4j.token.repository</code> java
* property which should be found in specified properties.
*
*/
public FileTokenRepository() {
this(System.getProperty("tnt4j.token.repository"),
Long.getLong("tnt4j.file.respository.refresh", 20000));
}
/**
* Create file/property based token repository instance given
* a specific filename or url. File name is auto-loaded based on
* <code>tnt4j.file.respository.refresh</code> property which is set to 20000 (ms)
* by default.
*
* @param url file name or URL of the property file containing tokens
* @param refreshDelay delay in milliseconds between refresh
*/
public FileTokenRepository(String url, long refreshDelay) {
configName = url;
refDelay = refreshDelay;
}
@Override
public void addRepositoryListener(TokenRepositoryListener listener) {
if (configName == null) return;
TokenConfigurationListener pListener = new TokenConfigurationListener(listener, logger);
LISTEN_MAP.put(listener, pListener);
config.addConfigurationListener(pListener);
config.addErrorListener(pListener);
}
@Override
public void removeRepositoryListener(TokenRepositoryListener listener) {
if (configName == null) return;
TokenConfigurationListener pListener = LISTEN_MAP.get(listener);
if (pListener != null) {
LISTEN_MAP.remove(listener);
config.removeConfigurationListener(pListener);
config.removeErrorListener(pListener);
}
}
@Override
public Object get(String key) {
return config != null? config.getProperty(key): null;
}
@Override
public Iterator<? extends Object> getKeys() {
return config != null? config.getKeys(): null;
}
@Override
public void remove(String key) {
if (config != null) {
config.clearProperty(key);
}
}
@Override
public void set(String key, Object value) {
if (config != null) {
config.setProperty(key, value);
}
}
@Override
public String getName() {
return configName;
}
@Override
public String toString() {
return super.toString() + "{url: " + getName() + ", delay: " + refDelay + ", config: " + config + "}";
}
@Override
public boolean isOpen() {
return config != null;
}
@Override
public void open() throws IOException {
if (isOpen() || (configName == null)) return;
try {
initConfig();
if (refDelay > 0) {
FileChangedReloadingStrategy reloadConfig = new FileChangedReloadingStrategy();
reloadConfig.setRefreshDelay(refDelay);
config.setReloadingStrategy(reloadConfig);
}
} catch (Throwable e) {
IOException ioe = new IOException(e.toString());
ioe.initCause(e);
throw ioe;
}
}
/**
* Initialize property configuration based on a configured configuration
* file name. The method attempts to load it from URL if given config is URL,
* then load it from class path and then from file system.
*
* @throws ConfigurationException if error loading configuration file
* @throws MalformedURLException if malformed configuration file name
*/
protected void initConfig() throws ConfigurationException, MalformedURLException {
int urlIndex = configName.indexOf("://");
if (urlIndex > 0) {
config = new PropertiesConfiguration(new URL(configName)); new PropertiesConfiguration(configName);
} else {
URL configResource = getClass().getResource("/" + configName);
if (configResource != null) {
config = new PropertiesConfiguration(configResource);
} else {
config = new PropertiesConfiguration(configName);
}
}
}
@Override
public void close() throws IOException {
}
@Override
public Map<String, Object> getConfiguration() {
return settings;
}
@Override
public void setConfiguration(Map<String, Object> props) throws ConfigException {
settings = props;
configName = Utils.getString("Url", props, configName);
refDelay = Utils.getLong("RefreshTime", props, refDelay);
}
@Override
public boolean isDefined() {
return configName != null;
}
}
class TokenConfigurationListener implements ConfigurationListener, ConfigurationErrorListener{
TokenRepositoryListener repListener = null;
EventSink logger = null;
public TokenConfigurationListener(TokenRepositoryListener listener, EventSink log) {
repListener = listener;
logger = log;
}
@Override
public void configurationChanged(ConfigurationEvent event) {
if (event.isBeforeUpdate()) return;
logger.log(OpLevel.DEBUG, "configurationChanged: type={0}, {1}:{2}",
event.getType(), event.getPropertyName(), event.getPropertyValue());
switch (event.getType()) {
case AbstractConfiguration.EVENT_ADD_PROPERTY:
repListener.repositoryChanged(new TokenRepositoryEvent(event.getSource(),
TokenRepository.EVENT_ADD_KEY, event.getPropertyName(), event.getPropertyValue(), null));
break;
case AbstractConfiguration.EVENT_SET_PROPERTY:
repListener.repositoryChanged(new TokenRepositoryEvent(event.getSource(),
TokenRepository.EVENT_SET_KEY, event.getPropertyName(), event.getPropertyValue(), null));
break;
case AbstractConfiguration.EVENT_CLEAR_PROPERTY:
repListener.repositoryChanged(new TokenRepositoryEvent(event.getSource(),
TokenRepository.EVENT_CLEAR_KEY, event.getPropertyName(), event.getPropertyValue(), null));
break;
case AbstractConfiguration.EVENT_CLEAR:
repListener.repositoryChanged(new TokenRepositoryEvent(event.getSource(),
TokenRepository.EVENT_CLEAR, event.getPropertyName(), event.getPropertyValue(), null));
break;
case AbstractFileConfiguration.EVENT_RELOAD:
repListener.repositoryChanged(new TokenRepositoryEvent(event.getSource(),
TokenRepository.EVENT_RELOAD, event.getPropertyName(), event.getPropertyValue(), null));
break;
}
}
@Override
public void configurationError(ConfigurationErrorEvent event) {
logger.log(OpLevel.ERROR, "Configuration error detected, event={0}", event, event.getCause());
repListener.repositoryError(new TokenRepositoryEvent(event.getSource(),
TokenRepository.EVENT_EXCEPTION, event.getPropertyName(), event.getPropertyValue(), event.getCause()));
}
}