Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 1 | /* |
Karl Pauls | d7db246 | 2006-06-01 11:49:22 +0000 | [diff] [blame] | 2 | * Copyright 2006 The Apache Software Foundation |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | * |
| 16 | */ |
| 17 | package org.apache.felix.eventadmin.impl; |
| 18 | |
| 19 | import org.apache.felix.eventadmin.impl.adapter.BundleEventAdapter; |
| 20 | import org.apache.felix.eventadmin.impl.adapter.FrameworkEventAdapter; |
| 21 | import org.apache.felix.eventadmin.impl.adapter.LogEventAdapter; |
| 22 | import org.apache.felix.eventadmin.impl.adapter.ServiceEventAdapter; |
| 23 | import org.apache.felix.eventadmin.impl.dispatch.CacheThreadPool; |
| 24 | import org.apache.felix.eventadmin.impl.dispatch.DelayScheduler; |
| 25 | import org.apache.felix.eventadmin.impl.dispatch.Scheduler; |
| 26 | import org.apache.felix.eventadmin.impl.dispatch.TaskHandler; |
| 27 | import org.apache.felix.eventadmin.impl.dispatch.ThreadPool; |
| 28 | import org.apache.felix.eventadmin.impl.handler.BlacklistingHandlerTasks; |
| 29 | import org.apache.felix.eventadmin.impl.handler.CacheFilters; |
| 30 | import org.apache.felix.eventadmin.impl.handler.CacheTopicHandlerFilters; |
| 31 | import org.apache.felix.eventadmin.impl.handler.CleanBlackList; |
| 32 | import org.apache.felix.eventadmin.impl.handler.Filters; |
| 33 | import org.apache.felix.eventadmin.impl.handler.HandlerTasks; |
| 34 | import org.apache.felix.eventadmin.impl.handler.TopicHandlerFilters; |
| 35 | import org.apache.felix.eventadmin.impl.security.CacheTopicPermissions; |
| 36 | import org.apache.felix.eventadmin.impl.security.SecureEventAdminFactory; |
| 37 | import org.apache.felix.eventadmin.impl.security.TopicPermissions; |
| 38 | import org.apache.felix.eventadmin.impl.tasks.AsyncDeliverTasks; |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 39 | import org.apache.felix.eventadmin.impl.tasks.BlockTask; |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 40 | import org.apache.felix.eventadmin.impl.tasks.DeliverTasks; |
| 41 | import org.apache.felix.eventadmin.impl.tasks.DispatchTask; |
| 42 | import org.apache.felix.eventadmin.impl.tasks.SyncDeliverTasks; |
| 43 | import org.apache.felix.eventadmin.impl.util.LeastRecentlyUsedCacheMap; |
| 44 | import org.apache.felix.eventadmin.impl.util.LogWrapper; |
| 45 | import org.osgi.framework.BundleActivator; |
| 46 | import org.osgi.framework.BundleContext; |
Karl Pauls | c4785ce | 2006-05-02 22:10:46 +0000 | [diff] [blame] | 47 | import org.osgi.framework.ServiceRegistration; |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 48 | import org.osgi.service.event.EventAdmin; |
| 49 | import org.osgi.service.event.TopicPermission; |
| 50 | |
| 51 | /** |
| 52 | * The activator of the EventAdmin bundle. This class registers an implementation of |
| 53 | * the OSGi R4 <tt>EventAdmin</tt> service (see the Compendium 113) with the |
| 54 | * framework. It features timeout-based blacklisting of event-handlers for both, |
| 55 | * asynchronous and synchronous event-dispatching (as a spec conform optional |
| 56 | * extension). |
| 57 | * |
| 58 | * The service knows about the following properties which are read at bundle startup: |
| 59 | * |
| 60 | * <p> |
| 61 | * <p> |
| 62 | * <tt>org.apache.felix.eventadmin.CacheSize</tt> - The size of various internal |
| 63 | * caches. |
| 64 | * </p> |
| 65 | * The default value is 30. Increase in case of a large number (more then 100) of |
| 66 | * <tt>EventHandler</tt> services. A value less then 10 triggers the default value. |
| 67 | * </p> |
| 68 | * <p> |
| 69 | * <p> |
| 70 | * <tt>org.apache.felix.eventadmin.ThreadPoolSize</tt> - The size of the thread |
| 71 | * pool. |
| 72 | * </p> |
| 73 | * The default value is 10. Increase in case of a large amount of synchronous events |
| 74 | * where the <tt>EventHandler</tt> services in turn send new synchronous events in |
| 75 | * the event dispatching thread or a lot of timeouts are to be expected. A value of |
| 76 | * less then 2 triggers the default value. A value of 2 effectively disables thread |
| 77 | * pooling. |
| 78 | * </p> |
| 79 | * <p> |
| 80 | * <p> |
| 81 | * <tt>org.apache.felix.eventadmin.Timeout</tt> - The black-listing timeout in |
| 82 | * milliseconds |
| 83 | * </p> |
| 84 | * The default value is 5000. Increase or decrease at own discretion. A value of less |
| 85 | * then 100 turns timeouts off. Any other value is the time in milliseconds granted |
| 86 | * to each <tt>EventHandler</tt> before it gets blacklisted. |
| 87 | * </p> |
| 88 | * <p> |
| 89 | * <p> |
| 90 | * <tt>org.apache.felix.eventadmin.RequireTopic</tt> - Are <tt>EventHandler</tt> |
| 91 | * required to be registered with a topic? |
| 92 | * </p> |
| 93 | * The default is <tt>true</tt>. The specification says that <tt>EventHandler</tt> |
| 94 | * must register with a list of topics they are interested in. Setting this value to |
| 95 | * <tt>false</tt> will enable that handlers without a topic are receiving all events |
| 96 | * (i.e., they are treated the same as with a topic=*). |
| 97 | * </p> |
| 98 | * |
| 99 | * @author <a href="mailto:felix-dev@incubator.apache.org">Felix Project Team</a> |
| 100 | */ |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 101 | // TODO: Security is in place but untested due to not being implemented by the |
| 102 | // framework. However, it needs to be revisited once security is implemented. |
| 103 | // Two places are affected by this namely, security/* and handler/* |
| 104 | public class Activator implements BundleActivator |
| 105 | { |
| 106 | // The thread pool used - this is a member because we need to close it on stop |
| 107 | private volatile ThreadPool m_pool; |
| 108 | |
| 109 | // The asynchronous event queue - this is a member because we need to close it on |
| 110 | // stop |
| 111 | private volatile TaskHandler m_asyncQueue; |
| 112 | |
| 113 | // The synchronous event queue - this is a member because we need to close it on |
| 114 | // stop |
| 115 | private volatile TaskHandler m_syncQueue; |
| 116 | |
| 117 | // The actual implementation of the service - this is a member because we need to |
| 118 | // close it on stop. Note, security is not part of this implementation but is |
| 119 | // added via a decorator in the start method (this is the wrapped object without |
| 120 | // the wrapper). |
| 121 | private volatile EventAdminImpl m_admin; |
| 122 | |
Karl Pauls | c4785ce | 2006-05-02 22:10:46 +0000 | [diff] [blame] | 123 | // The registration of the security decorator factory (i.e., the service) |
| 124 | private volatile ServiceRegistration m_registration; |
| 125 | |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 126 | /** |
| 127 | * Called upon starting of the bundle. Constructs and registers the EventAdmin |
| 128 | * service with the framework. Note that the properties of the service are |
| 129 | * requested from the context in this method hence, the bundle has to be |
| 130 | * restarted in order to take changed properties into account. |
| 131 | * |
| 132 | * @param context The bundle context passed by the framework |
| 133 | * |
| 134 | * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) |
| 135 | */ |
| 136 | public void start(final BundleContext context) |
| 137 | { |
| 138 | // init the LogWrapper. Subsequently, the static methods of the LogWrapper |
| 139 | // can be used to log messages similar to the LogService. The effect of a |
| 140 | // call to any of this methods is either a print to standard out (in case |
| 141 | // no LogService is present) or a call to the respective method of |
| 142 | // available LogServices (the reason is that this way the bundle is |
| 143 | // independent of the org.osgi.service.log package) |
| 144 | LogWrapper.setContext(context); |
| 145 | |
| 146 | // The size of various internal caches. At the moment there are 4 |
| 147 | // internal caches affected. Each will cache the determined amount of |
| 148 | // small but frequently used objects (i.e., in case of the default value |
| 149 | // we end-up with a total of 120 small objects being cached). A value of less |
| 150 | // then 10 triggers the default value. |
| 151 | final int cacheSize = getIntProperty("org.apache.felix.eventadmin.CacheSize", |
| 152 | context, 30, 10); |
| 153 | |
| 154 | // The size of the internal thread pool. Note that we must execute |
| 155 | // each synchronous event dispatch that happens in the synchronous event |
| 156 | // dispatching thread in a new thread, hence a small thread pool is o.k. |
| 157 | // A value of less then 2 triggers the default value. A value of 2 |
| 158 | // effectively disables thread pooling. Furthermore, this will be used by |
| 159 | // a lazy thread pool (i.e., new threads are created when needed). Ones the |
| 160 | // the size is reached and no cached thread is available new threads will |
| 161 | // be created. |
| 162 | final int threadPoolSize = getIntProperty( |
| 163 | "org.apache.felix.eventadmin.ThreadPoolSize", context, 10, 2); |
| 164 | |
| 165 | // The timeout in milliseconds - A value of less then 100 turns timeouts off. |
| 166 | // Any other value is the time in milliseconds granted to each EventHandler |
| 167 | // before it gets blacklisted. |
| 168 | final int timeout = getIntProperty("org.apache.felix.eventadmin.Timeout", |
| 169 | context, 5000, Integer.MIN_VALUE); |
| 170 | |
| 171 | // Are EventHandler required to be registered with a topic? - The default is |
| 172 | // true. The specification says that EventHandler must register with a list |
| 173 | // of topics they are interested in. Setting this value to false will enable |
| 174 | // that handlers without a topic are receiving all events |
| 175 | // (i.e., they are treated the same as with a topic=*). |
| 176 | final boolean requireTopic = getBooleanProperty( |
| 177 | "org.apache.felix.eventadmin.RequireTopic", context, true); |
| 178 | |
| 179 | LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG, |
| 180 | "org.apache.felix.eventadmin.CacheSize=" + cacheSize); |
| 181 | |
| 182 | LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG, |
| 183 | "org.apache.felix.eventadmin.ThreadPoolSize=" + threadPoolSize); |
| 184 | |
| 185 | LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG, |
| 186 | "org.apache.felix.eventadmin.Timeout=" + timeout); |
| 187 | |
| 188 | LogWrapper.getLogger().log(LogWrapper.LOG_DEBUG, |
| 189 | "org.apache.felix.eventadmin.RequireTopic=" + requireTopic); |
| 190 | |
| 191 | final TopicPermissions publishPermissions = new CacheTopicPermissions( |
| 192 | new LeastRecentlyUsedCacheMap(cacheSize), TopicPermission.PUBLISH); |
| 193 | |
| 194 | final TopicPermissions subscribePermissions = new CacheTopicPermissions( |
| 195 | new LeastRecentlyUsedCacheMap(cacheSize), TopicPermission.SUBSCRIBE); |
| 196 | |
| 197 | final TopicHandlerFilters topicHandlerFilters = |
| 198 | new CacheTopicHandlerFilters(new LeastRecentlyUsedCacheMap(cacheSize), |
| 199 | requireTopic); |
| 200 | |
| 201 | final Filters filters = new CacheFilters( |
| 202 | new LeastRecentlyUsedCacheMap(cacheSize), context); |
| 203 | |
| 204 | // The handlerTasks object is responsible to determine concerned EventHandler |
| 205 | // for a given event. Additionally, it keeps a list of blacklisted handlers. |
| 206 | // Note that blacklisting is deactivated by selecting a different scheduler |
| 207 | // below (and not in this HandlerTasks object!) |
| 208 | final HandlerTasks handlerTasks = new BlacklistingHandlerTasks(context, |
| 209 | new CleanBlackList(), topicHandlerFilters, filters, |
| 210 | subscribePermissions); |
| 211 | |
| 212 | // Either we need a scheduler that will trigger EventHandler blacklisting |
| 213 | // (timeout >= 100) or a null object (timeout < 100) |
| 214 | final Scheduler scheduler = createScheduler(timeout); |
| 215 | |
| 216 | // Note that this uses a lazy thread pool that will create new threads on |
| 217 | // demand - in case none of its cached threads is free - until threadPoolSize |
| 218 | // is reached. Subsequently, a threadPoolSize of 2 effectively disables |
| 219 | // caching of threads. |
| 220 | m_pool = new CacheThreadPool(threadPoolSize); |
| 221 | |
| 222 | m_asyncQueue = new TaskHandler(); |
| 223 | |
| 224 | m_syncQueue = new TaskHandler(); |
| 225 | |
| 226 | m_admin = new EventAdminImpl(handlerTasks, |
| 227 | createAsyncExecuters(m_asyncQueue, m_syncQueue, scheduler, m_pool), |
| 228 | createSyncExecuters(m_syncQueue, scheduler, m_pool)); |
| 229 | |
| 230 | // register the admin wrapped in a service factory (SecureEventAdminFactory) |
| 231 | // that hands-out the m_admin object wrapped in a decorator that checks |
| 232 | // appropriated permissions of each calling bundle |
Karl Pauls | c4785ce | 2006-05-02 22:10:46 +0000 | [diff] [blame] | 233 | m_registration = context.registerService(EventAdmin.class.getName(), |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 234 | new SecureEventAdminFactory(m_admin, publishPermissions), null); |
| 235 | |
| 236 | // Finally, adapt the outside events to our kind of events as per spec |
| 237 | adaptEvents(context, m_admin); |
| 238 | } |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 239 | |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 240 | /** |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 241 | * Called upon stopping the bundle. This will block until all pending events are |
| 242 | * delivered. An IllegalStateException will be thrown on new events starting with |
| 243 | * the begin of this method. However, it might take some time until we settle |
| 244 | * down which is somewhat cumbersome given that the spec asks for return in |
Karl Pauls | 13f116c | 2006-05-14 15:12:25 +0000 | [diff] [blame] | 245 | * a timely manner. |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 246 | * |
| 247 | * @param context The bundle context passed by the framework |
| 248 | * |
| 249 | * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) |
| 250 | */ |
| 251 | public void stop(final BundleContext context) |
| 252 | { |
Karl Pauls | c4785ce | 2006-05-02 22:10:46 +0000 | [diff] [blame] | 253 | // We need to unregister manually |
| 254 | m_registration.unregister(); |
| 255 | |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 256 | m_admin.stop(); |
| 257 | |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 258 | // This tasks will be unblocked once the queues are empty |
| 259 | final BlockTask asyncShutdownBlock = new BlockTask(); |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 260 | |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 261 | final BlockTask syncShutdownBlock = new BlockTask(); |
| 262 | |
| 263 | // Now close the queues. Note that already added tasks will be delivered |
| 264 | // The given shutdownTask will be executed once the queue is empty |
| 265 | m_asyncQueue.close(asyncShutdownBlock); |
| 266 | |
| 267 | m_syncQueue.close(syncShutdownBlock); |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 268 | |
| 269 | m_admin = null; |
| 270 | |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 271 | m_asyncQueue = null; |
| 272 | |
| 273 | m_syncQueue = null; |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 274 | |
Karl Pauls | c4785ce | 2006-05-02 22:10:46 +0000 | [diff] [blame] | 275 | m_registration = null; |
| 276 | |
Karl Pauls | 13f116c | 2006-05-14 15:12:25 +0000 | [diff] [blame] | 277 | final DispatchTask task = m_pool.getTask(Thread.currentThread(), null); |
| 278 | |
| 279 | if(null != task) |
| 280 | { |
| 281 | task.handover(); |
| 282 | } |
| 283 | |
Karl Pauls | c8f7960 | 2006-05-01 23:01:13 +0000 | [diff] [blame] | 284 | asyncShutdownBlock.block(); |
| 285 | |
| 286 | syncShutdownBlock.block(); |
Karl Pauls | 13f116c | 2006-05-14 15:12:25 +0000 | [diff] [blame] | 287 | |
| 288 | m_pool.close(); |
| 289 | |
| 290 | m_pool = null; |
Karl Pauls | 9176193 | 2006-04-28 11:12:54 +0000 | [diff] [blame] | 291 | } |
| 292 | |
| 293 | /* |
| 294 | * Create an AsyncDeliverTasks object that is used to dispatch asynchronous |
| 295 | * events. Additionally, the asynchronous dispatch queue is initialized and |
| 296 | * activated (i.e., a thread is started via the given ThreadPool). |
| 297 | */ |
| 298 | private DeliverTasks createAsyncExecuters(final TaskHandler handler, |
| 299 | final TaskHandler handoverHandler, final Scheduler scheduler, |
| 300 | final ThreadPool pool) |
| 301 | { |
| 302 | // init the queue |
| 303 | final AsyncDeliverTasks result = new AsyncDeliverTasks(handler, |
| 304 | handoverHandler, pool); |
| 305 | |
| 306 | // set-up the queue for asynchronous event delivery and activate it |
| 307 | // (i.e., a thread is started via the pool) |
| 308 | result.execute(new DispatchTask(handler, scheduler, result)); |
| 309 | |
| 310 | return result; |
| 311 | } |
| 312 | |
| 313 | /* |
| 314 | * Create a SyncDeliverTasks object that is used to dispatch synchronous events. |
| 315 | * Additionally, the synchronous dispatch queue is initialized and activated |
| 316 | * (i.e., a thread is started via the given ThreadPool). |
| 317 | */ |
| 318 | private DeliverTasks createSyncExecuters(final TaskHandler handler, |
| 319 | final Scheduler scheduler, final ThreadPool pool) |
| 320 | { |
| 321 | // init the queue |
| 322 | final SyncDeliverTasks result = new SyncDeliverTasks(handler, pool); |
| 323 | |
| 324 | // set-up the queue for synchronous event delivery and activate it |
| 325 | // (i.e. a thread is started via the pool) |
| 326 | result.execute(new DispatchTask(handler, scheduler, result)); |
| 327 | |
| 328 | return result; |
| 329 | } |
| 330 | |
| 331 | /* |
| 332 | * Returns either a new DelayScheduler with a delay of timeout or the |
| 333 | * Scheduler.NULL_SCHEDULER in case timeout is < 100 in which case timeout and |
| 334 | * subsequently black-listing is disabled. |
| 335 | */ |
| 336 | private Scheduler createScheduler(final int timeout) |
| 337 | { |
| 338 | if(100 > timeout) |
| 339 | { |
| 340 | return Scheduler.NULL_SCHEDULER; |
| 341 | } |
| 342 | |
| 343 | return new DelayScheduler(timeout); |
| 344 | } |
| 345 | |
| 346 | /* |
| 347 | * Init the adapters in org.apache.felix.eventadmin.impl.adapter |
| 348 | */ |
| 349 | private void adaptEvents(final BundleContext context, final EventAdmin admin) |
| 350 | { |
| 351 | new FrameworkEventAdapter(context, admin); |
| 352 | |
| 353 | new BundleEventAdapter(context, admin); |
| 354 | |
| 355 | new ServiceEventAdapter(context, admin); |
| 356 | |
| 357 | new LogEventAdapter(context, admin); |
| 358 | } |
| 359 | |
| 360 | /* |
| 361 | * Returns either the parsed int from the value of the property if it is set and |
| 362 | * not less then the min value or the default. Additionally, a warning is |
| 363 | * generated in case the value is erroneous (i.e., can not be parsed as an int or |
| 364 | * is less then the min value). |
| 365 | */ |
| 366 | private int getIntProperty(final String key, final BundleContext context, |
| 367 | final int defaultValue, final int min) |
| 368 | { |
| 369 | final String value = context.getProperty(key); |
| 370 | |
| 371 | if(null != value) |
| 372 | { |
| 373 | try { |
| 374 | final int result = Integer.parseInt(value); |
| 375 | |
| 376 | if(result >= min) |
| 377 | { |
| 378 | return result; |
| 379 | } |
| 380 | |
| 381 | LogWrapper.getLogger().log(LogWrapper.LOG_WARNING, |
| 382 | "Value for property: " + key + " is to low - Using default"); |
| 383 | } catch (NumberFormatException e) { |
| 384 | LogWrapper.getLogger().log(LogWrapper.LOG_WARNING, |
| 385 | "Unable to parse property: " + key + " - Using default", e); |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | return defaultValue; |
| 390 | } |
| 391 | |
| 392 | |
| 393 | /* |
| 394 | * Returns true if the value of the property is set and is either 1, true, or yes |
| 395 | * Returns false if the value of the property is set and is either 0, false, or no |
| 396 | * Returns the defaultValue otherwise |
| 397 | */ |
| 398 | private boolean getBooleanProperty(final String key, final BundleContext context, |
| 399 | final boolean defaultValue) |
| 400 | { |
| 401 | String value = context.getProperty(key); |
| 402 | |
| 403 | if(null != value) |
| 404 | { |
| 405 | value = value.trim().toLowerCase(); |
| 406 | |
| 407 | if(0 < value.length() && ("0".equals(value) || "false".equals(value) |
| 408 | || "no".equals(value))) |
| 409 | { |
| 410 | return false; |
| 411 | } |
| 412 | |
| 413 | if(0 < value.length() && ("1".equals(value) || "true".equals(value) |
| 414 | || "yes".equals(value))) |
| 415 | { |
| 416 | return true; |
| 417 | } |
| 418 | } |
| 419 | |
| 420 | return defaultValue; |
| 421 | } |
| 422 | } |