blob: 94c8c16bb09766150420fbc84636b04c9dbb92de [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.felix.eventadmin.impl;
import java.util.*;
import org.apache.felix.eventadmin.impl.adapter.*;
import org.apache.felix.eventadmin.impl.dispatch.DefaultThreadPool;
import org.apache.felix.eventadmin.impl.handler.*;
import org.apache.felix.eventadmin.impl.security.SecureEventAdminFactory;
import org.apache.felix.eventadmin.impl.util.LeastRecentlyUsedCacheMap;
import org.apache.felix.eventadmin.impl.util.LogWrapper;
import org.osgi.framework.*;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.metatype.MetaTypeProvider;
/**
* The <code>Configuration</code> class encapsules the
* configuration for the event admin.
*
* The service knows about the following properties which are read at bundle startup:
* <p>
* <p>
* <tt>org.apache.felix.eventadmin.CacheSize</tt> - The size of various internal
* caches.
* </p>
* The default value is 30. Increase in case of a large number (more then 100) of
* <tt>EventHandler</tt> services. A value less then 10 triggers the default value.
* </p>
* <p>
* <p>
* <tt>org.apache.felix.eventadmin.ThreadPoolSize</tt> - The size of the thread
* pool.
* </p>
* The default value is 10. Increase in case of a large amount of synchronous events
* where the <tt>EventHandler</tt> services in turn send new synchronous events in
* the event dispatching thread or a lot of timeouts are to be expected. A value of
* less then 2 triggers the default value. A value of 2 effectively disables thread
* pooling.
* </p>
* <p>
* <p>
* <tt>org.apache.felix.eventadmin.Timeout</tt> - The black-listing timeout in
* milliseconds
* </p>
* The default value is 5000. Increase or decrease at own discretion. A value of less
* then 100 turns timeouts off. Any other value is the time in milliseconds granted
* to each <tt>EventHandler</tt> before it gets blacklisted.
* </p>
* <p>
* <p>
* <tt>org.apache.felix.eventadmin.RequireTopic</tt> - Are <tt>EventHandler</tt>
* required to be registered with a topic?
* </p>
* The default is <tt>true</tt>. The specification says that <tt>EventHandler</tt>
* must register with a list of topics they are interested in. Setting this value to
* <tt>false</tt> will enable that handlers without a topic are receiving all events
* (i.e., they are treated the same as with a topic=*).
* </p>
* <p>
* <p>
* <tt>org.apache.felix.eventadmin.IgnoreTimeout</tt> - Configure
* <tt>EventHandler</tt>s to be called without a timeout.
* </p>
* If a timeout is configured by default all event handlers are called using the timeout.
* For performance optimization it is possible to configure event handlers where the
* timeout handling is not used - this reduces the thread usage from the thread pools
* as the timout handling requires an additional thread to call the event handler.
* However, the application should work without this configuration property. It is a
* pure optimization!
* The value is a list of string (separated by comma). If the string ends with a dot,
* all handlers in exactly this package are ignored. If the string ends with a star,
* all handlers in this package and all subpackages are ignored. If the string neither
* ends with a dot nor with a start, this is assumed to define an exact class name.
*
* These properties are read at startup and serve as a default configuration.
* If a configuration admin is configured, the event admin can be configured
* through the config admin.
*/
public class Configuration
{
/** The PID for the event admin. */
static final String PID = "org.apache.felix.eventadmin.impl.EventAdmin";
static final String PROP_CACHE_SIZE = "org.apache.felix.eventadmin.CacheSize";
static final String PROP_THREAD_POOL_SIZE = "org.apache.felix.eventadmin.ThreadPoolSize";
static final String PROP_TIMEOUT = "org.apache.felix.eventadmin.Timeout";
static final String PROP_REQUIRE_TOPIC = "org.apache.felix.eventadmin.RequireTopic";
static final String PROP_IGNORE_TIMEOUT = "org.apache.felix.eventadmin.IgnoreTimeout";
/** The bundle context. */
private final BundleContext m_bundleContext;
private int m_cacheSize;
private int m_threadPoolSize;
private int m_timeout;
private boolean m_requireTopic;
private String[] m_ignoreTimeout;
// The thread pool used - this is a member because we need to close it on stop
private volatile DefaultThreadPool m_sync_pool;
private volatile DefaultThreadPool m_async_pool;
// The actual implementation of the service - this is a member because we need to
// close it on stop. Note, security is not part of this implementation but is
// added via a decorator in the start method (this is the wrapped object without
// the wrapper).
private volatile EventAdminImpl m_admin;
// The registration of the security decorator factory (i.e., the service)
private volatile ServiceRegistration m_registration;
// all adapters
private AbstractAdapter[] m_adapters;
private ServiceRegistration m_managedServiceReg;
public Configuration( BundleContext bundleContext )
{
m_bundleContext = bundleContext;
// default configuration
configure( null );
startOrUpdate();
// check for Configuration Admin configuration
try
{
Object service = tryToCreateManagedService();
if ( service != null )
{
// add meta type provider if interfaces are available
Object enhancedService = tryToCreateMetaTypeProvider(service);
final String[] interfaceNames;
if ( enhancedService == null )
{
interfaceNames = new String[] {ManagedService.class.getName()};
}
else
{
interfaceNames = new String[] {ManagedService.class.getName(), MetaTypeProvider.class.getName()};
service = enhancedService;
}
Dictionary props = new Hashtable();
props.put( Constants.SERVICE_PID, PID );
m_managedServiceReg = m_bundleContext.registerService( interfaceNames, service, props );
}
}
catch ( Throwable t )
{
// don't care
}
}
void updateFromConfigAdmin(final Dictionary config)
{
// do this in the background as we don't want to stop
// the config admin
new Thread()
{
public void run()
{
synchronized ( Configuration.this )
{
Configuration.this.configure( config );
Configuration.this.startOrUpdate();
}
}
}.start();
}
/**
* Configures this instance.
*/
void configure( 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.
m_cacheSize = getIntProperty(PROP_CACHE_SIZE,
m_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.
m_threadPoolSize = getIntProperty(
PROP_THREAD_POOL_SIZE, m_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.
m_timeout = getIntProperty(PROP_TIMEOUT,
m_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=*).
m_requireTopic = getBooleanProperty(
m_bundleContext.getProperty(PROP_REQUIRE_TOPIC), true);
final String value = m_bundleContext.getProperty(PROP_IGNORE_TIMEOUT);
if ( value == null )
{
m_ignoreTimeout = null;
}
else
{
final StringTokenizer st = new StringTokenizer(value, ",");
m_ignoreTimeout = new String[st.countTokens()];
for(int i=0; i<m_ignoreTimeout.length; i++)
{
m_ignoreTimeout[i] = st.nextToken();
}
}
}
else
{
m_cacheSize = getIntProperty(PROP_CACHE_SIZE, config.get(PROP_CACHE_SIZE), 30, 10);
m_threadPoolSize = getIntProperty(PROP_THREAD_POOL_SIZE, config.get(PROP_THREAD_POOL_SIZE), 20, 2);
m_timeout = getIntProperty(PROP_TIMEOUT, config.get(PROP_TIMEOUT), 5000, Integer.MIN_VALUE);
m_requireTopic = getBooleanProperty(config.get(PROP_REQUIRE_TOPIC), true);
m_ignoreTimeout = null;
final Object value = config.get(PROP_IGNORE_TIMEOUT);
if ( value instanceof String )
{
m_ignoreTimeout = new String[] {(String)value};
}
else if ( value instanceof String[] )
{
m_ignoreTimeout = (String[])value;
}
else
{
LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
"Value for property: " + PROP_IGNORE_TIMEOUT + " is neither a string nor a string array - Using default");
}
}
// a timeout less or equals to 100 means : disable timeout
if ( m_timeout <= 100 )
{
m_timeout = 0;
}
}
private void startOrUpdate()
{
LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
PROP_CACHE_SIZE + "=" + m_cacheSize);
LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
PROP_THREAD_POOL_SIZE + "=" + m_threadPoolSize);
LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
PROP_TIMEOUT + "=" + m_timeout);
LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG,
PROP_REQUIRE_TOPIC + "=" + m_requireTopic);
final TopicHandlerFilters topicHandlerFilters =
new CacheTopicHandlerFilters(new LeastRecentlyUsedCacheMap(m_cacheSize),
m_requireTopic);
final Filters filters = new CacheFilters(
new LeastRecentlyUsedCacheMap(m_cacheSize), m_bundleContext);
// Note that this uses a lazy thread pool that will create new threads on
// demand - in case none of its cached threads is free - until threadPoolSize
// is reached. Subsequently, a threadPoolSize of 2 effectively disables
// caching of threads.
if ( m_sync_pool == null )
{
m_sync_pool = new DefaultThreadPool(m_threadPoolSize, true);
}
else
{
m_sync_pool.configure(m_threadPoolSize);
}
final int asyncThreadPoolSize = m_threadPoolSize > 5 ? m_threadPoolSize / 2 : 2;
if ( m_async_pool == null )
{
m_async_pool = new DefaultThreadPool(asyncThreadPoolSize, false);
}
else
{
m_async_pool.configure(asyncThreadPoolSize);
}
// The handlerTasks object is responsible to determine concerned EventHandler
// for a given event. Additionally, it keeps a list of blacklisted handlers.
// Note that blacklisting is deactivated by selecting a different scheduler
// below (and not in this HandlerTasks object!)
final HandlerTasks handlerTasks = new BlacklistingHandlerTasks(m_bundleContext,
new CleanBlackList(), topicHandlerFilters, filters);
if ( m_admin == null )
{
m_admin = new EventAdminImpl(handlerTasks, m_sync_pool, m_async_pool, m_timeout, m_ignoreTimeout);
// Finally, adapt the outside events to our kind of events as per spec
adaptEvents(m_admin);
// register the admin wrapped in a service factory (SecureEventAdminFactory)
// that hands-out the m_admin object wrapped in a decorator that checks
// appropriated permissions of each calling bundle
m_registration = m_bundleContext.registerService(EventAdmin.class.getName(),
new SecureEventAdminFactory(m_admin), null);
}
else
{
m_admin.update(handlerTasks, m_timeout, m_ignoreTimeout);
}
}
/**
* Called upon stopping the bundle. This will block until all pending events are
* delivered. An IllegalStateException will be thrown on new events starting with
* the begin of this method. However, it might take some time until we settle
* down which is somewhat cumbersome given that the spec asks for return in
* a timely manner.
*/
public void destroy()
{
synchronized ( this )
{
if ( m_adapters != null )
{
for(int i=0;i<m_adapters.length;i++)
{
m_adapters[i].destroy(m_bundleContext);
}
m_adapters = null;
}
if ( m_managedServiceReg != null )
{
m_managedServiceReg.unregister();
m_managedServiceReg = null;
}
// We need to unregister manually
if ( m_registration != null )
{
m_registration.unregister();
m_registration = null;
}
if ( m_admin != null )
{
m_admin.stop();
m_admin = null;
}
if (m_async_pool != null )
{
m_async_pool.close();
m_async_pool = null;
}
if ( m_sync_pool != null )
{
m_sync_pool.close();
m_sync_pool = null;
}
}
}
/**
* Init the adapters in org.apache.felix.eventadmin.impl.adapter
*/
private void adaptEvents(final EventAdmin admin)
{
m_adapters = new AbstractAdapter[4];
m_adapters[0] = new FrameworkEventAdapter(m_bundleContext, admin);
m_adapters[1] = new BundleEventAdapter(m_bundleContext, admin);
m_adapters[2] = new ServiceEventAdapter(m_bundleContext, admin);
m_adapters[3] = new LogEventAdapter(m_bundleContext, admin);
}
private Object tryToCreateMetaTypeProvider(final Object managedService)
{
try
{
return new MetaTypeProviderImpl((ManagedService)managedService,
m_cacheSize, m_threadPoolSize, m_timeout, m_requireTopic,
m_ignoreTimeout);
}
catch (Throwable t)
{
// we simply ignore this
}
return null;
}
private Object tryToCreateManagedService()
{
try
{
return new ManagedService()
{
public void updated( Dictionary properties ) throws ConfigurationException
{
updateFromConfigAdmin(properties);
}
};
}
catch (Throwable t)
{
// we simply ignore this
}
return null;
}
/**
* 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)
{
LogWrapper.getLogger().log(LogWrapper.LOG_WARNING,
"Unable to parse property: " + key + " - Using default", e);
return defaultValue;
}
}
if(result >= min)
{
return result;
}
LogWrapper.getLogger().log(LogWrapper.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;
}
}