| /* |
| * 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; |
| } |
| } |