FELIX-1960 : Fine-grained timeout configuration

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@906102 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
index 8a042cc..a193042 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/Configuration.java
@@ -19,8 +19,7 @@
 package org.apache.felix.eventadmin.impl;
 
 
-import java.util.Dictionary;
-import java.util.Hashtable;
+import java.util.*;
 
 import org.apache.felix.eventadmin.impl.adapter.*;
 import org.apache.felix.eventadmin.impl.dispatch.DefaultThreadPool;
@@ -81,6 +80,21 @@
  * <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
@@ -95,6 +109,7 @@
     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;
@@ -107,6 +122,8 @@
 
     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 ThreadPool m_sync_pool;
 
@@ -206,6 +223,19 @@
             // (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
         {
@@ -213,6 +243,21 @@
             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");
+            }
         }
     }
 
@@ -255,7 +300,9 @@
         m_sync_pool = new DefaultThreadPool(m_threadPoolSize, true);
         m_async_pool = new DefaultThreadPool(m_threadPoolSize > 5 ? m_threadPoolSize / 2 : 2, false);
 
-        final DeliverTask syncExecuter = new SyncDeliverTasks(m_sync_pool, (m_timeout > 100 ? m_timeout : 0));
+        final DeliverTask syncExecuter = new SyncDeliverTasks(m_sync_pool,
+                (m_timeout > 100 ? m_timeout : 0),
+                m_ignoreTimeout);
         m_admin = createEventAdmin(m_bundleContext,
                 handlerTasks,
                 new AsyncDeliverTasks(m_async_pool, syncExecuter),
@@ -366,7 +413,8 @@
         try
         {
             return new MetaTypeProviderImpl((ManagedService)managedService,
-                    m_cacheSize, m_threadPoolSize, m_timeout, m_requireTopic);
+                    m_cacheSize, m_threadPoolSize, m_timeout, m_requireTopic,
+                    m_ignoreTimeout);
         } catch (Throwable t)
         {
             // we simply ignore this
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
index 3605460..736a2f6 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/MetaTypeProviderImpl.java
@@ -36,18 +36,21 @@
     private final int m_threadPoolSize;
     private final int m_timeout;
     private final boolean m_requireTopic;
+    private final String[] m_ignoreTimeout;
 
     private final ManagedService m_delegatee;
 
     public MetaTypeProviderImpl(final ManagedService delegatee,
             final int cacheSize, final int threadPoolSize,
-            final int timeout, final boolean requireTopic)
+            final int timeout, final boolean requireTopic,
+            final String[] ignoreTimeout)
     {
         m_cacheSize = cacheSize;
         m_threadPoolSize = threadPoolSize;
         m_timeout = timeout;
         m_requireTopic = requireTopic;
         m_delegatee = delegatee;
+        m_ignoreTimeout = ignoreTimeout;
     }
 
     private ObjectClassDefinition ocd;
@@ -107,7 +110,17 @@
                     "will enable that handlers without a topic are receiving all events " +
                     "(i.e., they are treated the same as with a topic=*).",
                     m_requireTopic ) );
-
+            adList.add( new AttributeDefinitionImpl( Configuration.PROP_IGNORE_TIMEOUT, "Ignore Timeouts",
+                    "Configure event handlers to be called without a timeout. 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 strings. If a 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.",
+                    AttributeDefinition.STRING, m_ignoreTimeout, Integer.MAX_VALUE, null, null));
             ocd = new ObjectClassDefinition()
             {
 
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
index 33289a0..668f333 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTask.java
@@ -27,6 +27,11 @@
 public interface HandlerTask
 {
     /**
+     * Return the class name of the handler
+     */
+    String getHandlerClassName();
+
+    /**
      * Deliver the event to the handler.
      */
     void execute();
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java
index 213620e..16b975f 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/HandlerTaskImpl.java
@@ -62,6 +62,18 @@
     }
 
     /**
+     * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#getHandlerClassName()
+     */
+    public String getHandlerClassName() {
+        final EventHandler handler = m_handlerTasks.getEventHandler(m_eventHandlerRef);
+        try {
+            return handler.getClass().getName();
+        } finally {
+            m_handlerTasks.ungetEventHandler(handler, m_eventHandlerRef);
+        }
+    }
+
+    /**
      * @see org.apache.felix.eventadmin.impl.tasks.HandlerTask#execute()
      */
     public void execute()
diff --git a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
index 3b674c3..a27146a 100644
--- a/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
+++ b/eventadmin/impl/src/main/java/org/apache/felix/eventadmin/impl/tasks/SyncDeliverTasks.java
@@ -57,15 +57,95 @@
     /** The timeout for event handlers, 0 = disabled. */
     final long m_timeout;
 
+    private static interface Matcher
+    {
+        boolean match(String className);
+    }
+    private static final class PackageMatcher implements Matcher
+    {
+        private final String m_packageName;
+
+        public PackageMatcher(final String name)
+        {
+            m_packageName = name;
+        }
+        public boolean match(String className)
+        {
+            final int pos = className.lastIndexOf('.');
+            return pos > -1 && className.substring(0, pos).equals(m_packageName);
+        }
+    }
+    private static final class SubPackageMatcher implements Matcher
+    {
+        private final String m_packageName;
+
+        public SubPackageMatcher(final String name)
+        {
+            m_packageName = name + '.';
+        }
+        public boolean match(String className)
+        {
+            final int pos = className.lastIndexOf('.');
+            return pos > -1 && className.substring(0, pos + 1).startsWith(m_packageName);
+        }
+    }
+    private static final class ClassMatcher implements Matcher
+    {
+        private final String m_className;
+
+        public ClassMatcher(final String name)
+        {
+            m_className = name;
+        }
+        public boolean match(String className)
+        {
+            return m_className.equals(className);
+        }
+    }
+
+    /** The matchers for ignore timeout handling. */
+    private final Matcher[] m_ignoreTimeoutMatcher;
+
     /**
      * Construct a new sync deliver tasks.
      * @param pool The thread pool used to spin-off new threads.
      * @param timeout The timeout for an event handler, 0 = disabled
      */
-    public SyncDeliverTasks(final ThreadPool pool, final long timeout)
+    public SyncDeliverTasks(final ThreadPool pool, final long timeout, final String[] ignoreTimeout)
     {
         m_pool = pool;
         m_timeout = timeout;
+        if ( ignoreTimeout == null || ignoreTimeout.length == 0 )
+        {
+            m_ignoreTimeoutMatcher = null;
+        }
+        else
+        {
+            m_ignoreTimeoutMatcher = new Matcher[ignoreTimeout.length];
+            for(int i=0;i<ignoreTimeout.length;i++)
+            {
+                String value = ignoreTimeout[i];
+                if ( value != null )
+                {
+                    value = value.trim();
+                }
+                if ( value != null && value.length() > 0 )
+                {
+                    if ( value.endsWith(".") )
+                    {
+                        m_ignoreTimeoutMatcher[i] = new PackageMatcher(value.substring(0, value.length() - 1));
+                    }
+                    else if ( value.endsWith("*") )
+                    {
+                        m_ignoreTimeoutMatcher[i] = new SubPackageMatcher(value.substring(0, value.length() - 1));
+                    }
+                    else
+                    {
+                        m_ignoreTimeoutMatcher[i] = new ClassMatcher(value);
+                    }
+                }
+            }
+        }
     }
 
     /**
@@ -75,7 +155,26 @@
      */
     private boolean useTimeout(final HandlerTask task)
     {
-        return m_timeout > 0;
+        // we only check the classname if a timeout is configured
+        if ( m_timeout > 0)
+        {
+            if ( m_ignoreTimeoutMatcher != null )
+            {
+                final String className = task.getHandlerClassName();
+                for(int i=0;i<m_ignoreTimeoutMatcher.length;i++)
+                {
+                    if ( m_ignoreTimeoutMatcher[i] != null)
+                    {
+                        if ( m_ignoreTimeoutMatcher[i].match(className) )
+                        {
+                            return false;
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
     }
 
     /**