/*
* 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.Hashtable;
import java.util.StringTokenizer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.log.LogService;
import org.solmix.eventservice.filter.BlackListImpl;
import org.solmix.eventservice.filter.CachedEventFilter;
import org.solmix.eventservice.filter.CachedTopicFilter;
import org.solmix.eventservice.security.SecureEventAdminFactory;
import org.solmix.eventservice.tasks.EventTaskManagerImpl;
import org.solmix.eventservice.util.EventThreadPool;
import org.solmix.eventservice.util.LeastRecentlyUsedCacheMap;
/**
*
* @author solmix
* @version 110035 2011-9-28
*/
public class EventServer
{
static final String PROP_CACHE_SIZE = "org.solmix.eventadmin.CacheSize";
static final String PROP_THREAD_POOL_SIZE = "org.solmix.eventadmin.ThreadPoolSize";
static final String PROP_TIMEOUT = "org.solmix.eventadmin.Timeout";
static final String PROP_REQUIRE_TOPIC = "org.solmix.eventadmin.RequireTopic";
static final String PROP_IGNORE_TIMEOUT = "org.solmix.eventadmin.IgnoreTimeout";
static final String PROP_LOG_LEVEL = "org.solmix.eventadmin.LogLevel";
/**
* Distributed white-list if the topic in this list.the event will be distributed post.
*/
static final String PROP_DIS_EVENT_WHITELIST = "org.solmix.eventadmin.dis.WhiteList";
static final String PROP_DIS_EVENT_TARGET_TYPE = "org.solmix.eventadmin.dis.TargetType";
static final String PROP_DIS_EVENT_TARGET_ID = "org.solmix.eventadmin.dis.TargetID";
final String DEFAULT_DIS_TARGET_TYPE = "";
final String DEFAULT_DIS_TARGET_ID = "";
private ServiceRegistration msRegistration;
/**
* EventAdmin Service registration.
*/
private volatile ServiceRegistration eaRegistration;
static final String PID = "org.solmix.eventadmin";
int cacheSize;
int threadPoolSize;
int timeout;
boolean requireTopic;
String[] ignoreTimeout;
String[] disWhiteList;
int logLevel;
String disTargetID;
String disTargetType;
BundleContext bundleContext;
private volatile EventThreadPool syn_pool;
private volatile EventThreadPool asy_pool;
private volatile EventAdminImpl eventAdmin;
public EventServer(BundleContext context)
{
this.bundleContext = context;
configureServer();
startOrUpdateServer();
ManagedService service = tryToCreateManagerService();
if (service != null) {
Dictionary<String, String> filter = new Hashtable<String, String>();
filter.put(Constants.SERVICE_PID, PID);
msRegistration = bundleContext.registerService(ManagedService.class.getName(), service, filter);
}
}
public void destory() {
if (this.msRegistration != null) {
msRegistration.unregister();
msRegistration = null;
}
if (this.eaRegistration != null) {
eaRegistration.unregister();
eaRegistration = null;
}
if (eventAdmin != null) {
eventAdmin.shutdown();
}
if (syn_pool != null) {
syn_pool.shutdown();
syn_pool = null;
}
if (asy_pool != null) {
asy_pool.shutdown();
asy_pool = null;
}
}
/**
* Try to create a ManagedService.
*/
private ManagedService tryToCreateManagerService() {
try {
return new ManagedService() {
@Override
public void updated(Dictionary properties) throws ConfigurationException {
updateServer(properties);
}
};
} catch (Exception e) {
}
return null;
}
/**
* Update server
*
* @param properties
*/
void updateServer(final Dictionary properties) {
new Thread() {
@Override
public void run() {
synchronized (EventServer.this) {
EventServer.this.configureServer(properties);
EventServer.this.startOrUpdateServer();
}
}
}.start();
}
/**
*
*/
void startOrUpdateServer() {
/**
* 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);
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);
}
final EventTaskManager taskManager = new EventTaskManagerImpl(bundleContext, new BlackListImpl(), topicFilter, filter);
if (eventAdmin == null) {
// eventAdmin = new EventAdminImpl(taskManager, syn_pool, asy_pool, timeout, ignoreTimeout);
// Registered the eventAdmin service.
eaRegistration = bundleContext.registerService(EventAdmin.class.getName(), new SecureEventAdminFactory(eventAdmin), null);
} else {
// eventAdmin.update(taskManager, timeout, ignoreTimeout);
}
}
private void configureServer() {
configureServer(null);
}
/**
* @param config config properties.
*/
void configureServer(Dictionary 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();
}
}
logLevel = getIntProperty(PROP_LOG_LEVEL, bundleContext.getProperty(PROP_LOG_LEVEL), LogService.LOG_WARNING, // default
// log
// level
// is
// WARNING
LogService.LOG_ERROR);
} 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 {
Activator.getLogService().log(LogService.LOG_WARNING,
"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 {
Activator.getLogService().log(LogService.LOG_WARNING,
"Value for property: " + PROP_DIS_EVENT_WHITELIST + " is neither a string nor a string array - Using default");
}
logLevel = getIntProperty(PROP_LOG_LEVEL, config.get(PROP_LOG_LEVEL), LogService.LOG_WARNING, // default log
// level is
// WARNING
LogService.LOG_ERROR);
}
// a timeout less or equals to 100 means : disable timeout
if (timeout <= 100) {
timeout = 0;
}
}
/**
* @return the bundleContext
*/
public BundleContext getBundleContext() {
return bundleContext;
}
/**
* @param bundleContext the bundleContext to set
*/
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
/**
* 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) {
Activator.getLogService().log(LogService.LOG_WARNING, "Unable to parse property: " + key + " - Using default", e);
return defaultValue;
}
}
if (result >= min) {
return result;
}
Activator.getLogService().log(LogService.LOG_WARNING, "Value for property: " + key + " is to low - Using default");
}
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;
}
}