/*
* Copyright 2012 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.eventservice;
import java.util.Dictionary;
import java.util.List;
import java.util.StringTokenizer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solmix.eventservice.deliver.AsyncDeliver;
import org.solmix.eventservice.deliver.SyncDeliver;
import org.solmix.eventservice.filter.BlackListImpl;
import org.solmix.eventservice.filter.CachedEventFilter;
import org.solmix.eventservice.filter.CachedTopicFilter;
import org.solmix.eventservice.filter.DistributionFilter;
import org.solmix.eventservice.tasks.EventTaskManagerImpl;
import org.solmix.eventservice.util.EventThreadPool;
import org.solmix.eventservice.util.LeastRecentlyUsedCacheMap;
/**
* OSGI Enterprise 4,chapter 113 Event Admin service specification.
*
* @author solmix.f@gmail.com
* @version 110035 2011-9-27
*/
public class EventAdminImpl implements EventAdmin, ManagedService
{
static final String PROP_CACHE_SIZE = "CacheSize";
static final String PROP_THREAD_POOL_SIZE = "ThreadPoolSize";
static final String PROP_TIMEOUT = "Timeout";
static final String PROP_REQUIRE_TOPIC = "RequireTopic";
static final String PROP_IGNORE_TIMEOUT = "IgnoreTimeout";
/**
* Distributed white-list if the topic in this list.the event will be distributed post.
*/
static final String PROP_DIS_EVENT_WHITELIST = "dis.WhiteList";
static final String PROP_DIS_EVENT_TARGET_TYPE = "dis.TargetType";
static final String PROP_DIS_EVENT_TARGET_ID = "dis.TargetID";
private static final Logger logger = LoggerFactory.getLogger(EventAdminImpl.class);
private volatile EventThreadPool syn_pool;
private volatile EventThreadPool asy_pool;
private volatile SyncDeliver syn_deliver;
private volatile EventDeliver asy_deliver;
private volatile EventTaskManager taskManager;
private BundleContext bundleContext;
private boolean requireTopic;
private int cacheSize;
private int threadPoolSize;
private int timeout;
private String[] ignoreTimeout;
private String[] disWhiteList;
public EventAdminImpl()
{
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.event.EventAdmin#postEvent(org.osgi.service.event.Event)
*/
@Override
public void postEvent(Event event) {
DistributionFilter.filte(event);
List<EventTask> tasks = taskManager.createEventTasks(event);
handleEvent(tasks, asy_deliver);
}
/**
* @return the bundleContext
*/
public BundleContext getBundleContext() {
return bundleContext;
}
/**
* @param bundleContext the bundleContext to set
*/
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
/**
* @return the cacheSize
*/
public int getCacheSize() {
return cacheSize;
}
/**
* @param cacheSize the cacheSize to set
*/
public void setCacheSize(int cacheSize) {
this.cacheSize = cacheSize;
}
/**
* @return the threadPoolSize
*/
public int getThreadPoolSize() {
return threadPoolSize;
}
/**
* @param threadPoolSize the threadPoolSize to set
*/
public void setThreadPoolSize(int threadPoolSize) {
this.threadPoolSize = threadPoolSize;
}
/**
* @return the timeout
*/
public int getTimeout() {
return timeout;
}
/**
* @param timeout the timeout to set
*/
public void setTimeout(int timeout) {
this.timeout = timeout;
}
/**
* @return the ignoreTimeout
*/
public String[] getIgnoreTimeout() {
return ignoreTimeout;
}
/**
* @param ignoreTimeout the ignoreTimeout to set
*/
public void setIgnoreTimeout(String[] ignoreTimeout) {
this.ignoreTimeout = ignoreTimeout;
}
/**
* @return the disWhiteList
*/
public String[] getDisWhiteList() {
return disWhiteList;
}
/**
* @param disWhiteList the disWhiteList to set
*/
public void setDisWhiteList(String[] disWhiteList) {
this.disWhiteList = disWhiteList;
}
/**
* @return the requireTopic
*/
public boolean isRequireTopic() {
return requireTopic;
}
/**
* @param requireTopic the requireTopic to set
*/
public void setRequireTopic(boolean requireTopic) {
this.requireTopic = requireTopic;
}
/**
* @param event
*/
protected void handleEvent(List<EventTask> tasks, final EventDeliver deliver) {
if (tasks != null && tasks.size() > 0)
deliver.execute(tasks);
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.event.EventAdmin#sendEvent(org.osgi.service.event.Event)
*/
@Override
public void sendEvent(Event event) {
List<EventTask> tasks = taskManager.createEventTasks(event);
handleEvent(tasks, syn_deliver);
}
/**
*
*/
public void shutdown() {
taskManager = new EventTaskManager() {
/**
* This is a null object and this method will throw an IllegalStateException due to the bundle being
* stopped.
*
* @param event An event that is not used.
*
* @return This method does not return normally
*
* @throws IllegalStateException - This is a null object and this method will always throw an
* IllegalStateException
*/
@Override
public List<EventTask> createEventTasks(final Event event) {
throw new IllegalStateException("The EventAdmin is stopped");
}
};
}
/**
* {@inheritDoc}
*
* @see org.osgi.service.cm.ManagedService#updated(java.util.Dictionary)
*/
@Override
public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
updateServer(properties);
}
/**
* @param properties
*/
protected void updateServer(final Dictionary<String, ?> properties) {
new Thread() {
@Override
public void run() {
synchronized (EventAdminImpl.this) {
EventAdminImpl.this.configureServer(properties);
EventAdminImpl.this.startOrUpdateServer();
}
}
}.start();
}
/**
*
*/
protected void startOrUpdateServer() {
if (syn_pool == null) {
syn_pool = new EventThreadPool(threadPoolSize, true);
} else {
syn_pool.updatePoolSize(threadPoolSize);
}
final int asyncThreadPoolSize = threadPoolSize > 5 ? threadPoolSize / 2 : 2;
if (asy_pool == null) {
asy_pool = new EventThreadPool(asyncThreadPoolSize, false);
} else {
asy_pool.updatePoolSize(asyncThreadPoolSize);
}
taskManager = createEventTaskManager();
if (syn_deliver == null)
syn_deliver = new SyncDeliver(syn_pool, timeout, ignoreTimeout);
else
syn_deliver.update(timeout, ignoreTimeout);
asy_deliver = new AsyncDeliver(asy_pool, syn_deliver);
}
/**
* Create Task manager.
* @return
*/
protected EventTaskManager createEventTaskManager(){
/**
* initial Topic filter.
*/
Cache<String, String> topicCache = new LeastRecentlyUsedCacheMap<String, String>(cacheSize);
final TopicFilter topicFilter = new CachedTopicFilter(topicCache, requireTopic);
/**
* Initial Event filter.
*/
Cache<String, Filter> event_filter_cache = new LeastRecentlyUsedCacheMap<String, Filter>(cacheSize);
final EventFilter filter = new CachedEventFilter(event_filter_cache);
return new EventTaskManagerImpl(bundleContext, new BlackListImpl(), topicFilter, filter);
}
private void configureServer() {
configureServer(null);
}
/**
* @param config config properties.
*/
void configureServer(Dictionary<String, ?> config) {
if (config == null) {
// The size of various internal caches. At the moment there are 4
// internal caches affected. Each will cache the determined amount of
// small but frequently used objects (i.e., in case of the default value
// we end-up with a total of 120 small objects being cached). A value of less
// then 10 triggers the default value.
cacheSize = getIntProperty(PROP_CACHE_SIZE, bundleContext.getProperty(PROP_CACHE_SIZE), 30, 10);
// The size of the internal thread pool. Note that we must execute
// each synchronous event dispatch that happens in the synchronous event
// dispatching thread in a new thread, hence a small thread pool is o.k.
// A value of less then 2 triggers the default value. A value of 2
// effectively disables thread pooling. Furthermore, this will be used by
// a lazy thread pool (i.e., new threads are created when needed). Ones the
// the size is reached and no cached thread is available new threads will
// be created.
threadPoolSize = getIntProperty(PROP_THREAD_POOL_SIZE, bundleContext.getProperty(PROP_THREAD_POOL_SIZE), 20, 2);
// The timeout in milliseconds - A value of less then 100 turns timeouts off.
// Any other value is the time in milliseconds granted to each EventHandler
// before it gets blacklisted.
timeout = getIntProperty(PROP_TIMEOUT, bundleContext.getProperty(PROP_TIMEOUT), 5000, Integer.MIN_VALUE);
// Are EventHandler required to be registered with a topic? - The default is
// true. The specification says that EventHandler must register with a list
// of topics they are interested in. Setting this value to false will enable
// that handlers without a topic are receiving all events
// (i.e., they are treated the same as with a topic=*).
requireTopic = getBooleanProperty(bundleContext.getProperty(PROP_REQUIRE_TOPIC), true);
final String value = bundleContext.getProperty(PROP_IGNORE_TIMEOUT);
if (value == null) {
ignoreTimeout = null;
} else {
final StringTokenizer st = new StringTokenizer(value, ",");
ignoreTimeout = new String[st.countTokens()];
for (int i = 0; i < ignoreTimeout.length; i++) {
ignoreTimeout[i] = st.nextToken();
}
}
final String whiteStr = bundleContext.getProperty(PROP_DIS_EVENT_WHITELIST);
if (whiteStr == null) {
disWhiteList = null;
} else {
final StringTokenizer st = new StringTokenizer(value, ",");
disWhiteList = new String[st.countTokens()];
for (int i = 0; i < disWhiteList.length; i++) {
disWhiteList[i] = st.nextToken();
}
}
} else {
cacheSize = getIntProperty(PROP_CACHE_SIZE, config.get(PROP_CACHE_SIZE), 30, 10);
threadPoolSize = getIntProperty(PROP_THREAD_POOL_SIZE, config.get(PROP_THREAD_POOL_SIZE), 20, 2);
timeout = getIntProperty(PROP_TIMEOUT, config.get(PROP_TIMEOUT), 5000, Integer.MIN_VALUE);
requireTopic = getBooleanProperty(config.get(PROP_REQUIRE_TOPIC), true);
ignoreTimeout = null;
final Object value = config.get(PROP_IGNORE_TIMEOUT);
if (value instanceof String) {
ignoreTimeout = new String[] { (String) value };
} else if (value instanceof String[]) {
ignoreTimeout = (String[]) value;
} else {
logger.warn("Value for property: " + PROP_IGNORE_TIMEOUT + " is neither a string nor a string array - Using default");
}
final Object whiteObj = config.get(PROP_DIS_EVENT_WHITELIST);
if (whiteObj instanceof String) {
final StringTokenizer st = new StringTokenizer((String) whiteObj, ",");
disWhiteList = new String[st.countTokens()];
for (int i = 0; i < disWhiteList.length; i++) {
disWhiteList[i] = st.nextToken();
}
} else if (whiteObj instanceof String[]) {
disWhiteList = (String[]) value;
} else {
logger.warn("Value for property: " + PROP_DIS_EVENT_WHITELIST + " is neither a string nor a string array - Using default");
}
}
// a timeout less or equals to 100 means : disable timeout
if (timeout <= 100) {
timeout = 0;
}
}
/**
* Returns either the parsed int from the value of the property if it is set and not less then the min value or the
* default. Additionally, a warning is generated in case the value is erroneous (i.e., can not be parsed as an int
* or is less then the min value).
*/
private int getIntProperty(final String key, final Object value, final int defaultValue, final int min) {
if (null != value) {
final int result;
if (value instanceof Integer) {
result = ((Integer) value).intValue();
} else {
try {
result = Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
logger.warn(new StringBuilder().append("Unable to parse property: ").append(key).append(" - Using default").toString(), e);
return defaultValue;
}
}
if (result >= min) {
return result;
}
logger.warn("Value for property: {0} is to low - Using default", key);
}
return defaultValue;
}
/**
* Returns true if the value of the property is set and is either 1, true, or yes Returns false if the value of the
* property is set and is either 0, false, or no Returns the defaultValue otherwise
*/
private boolean getBooleanProperty(final Object obj, final boolean defaultValue) {
if (null != obj) {
if (obj instanceof Boolean) {
return ((Boolean) obj).booleanValue();
}
String value = obj.toString().trim().toLowerCase();
if (0 < value.length() && ("0".equals(value) || "false".equals(value) || "no".equals(value))) {
return false;
}
if (0 < value.length() && ("1".equals(value) || "true".equals(value) || "yes".equals(value))) {
return true;
}
}
return defaultValue;
}
public void start() {
// initial configuration.
configureServer();
startOrUpdateServer();
}
public void start(Dictionary<String, ?> configuration){
configureServer(configuration);
startOrUpdateServer();
}
public void stop() {
shutdown();
if (syn_pool != null) {
syn_pool.shutdown();
syn_pool = null;
}
if (asy_pool != null) {
asy_pool.shutdown();
asy_pool = null;
}
}
}