You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by cl...@apache.org on 2008/10/24 17:48:19 UTC

svn commit: r707659 [1/3] - in /felix/trunk/ipojo: core/src/main/java/org/apache/felix/ipojo/util/ handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/ handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/publisher/...

Author: clement
Date: Fri Oct 24 08:48:18 2008
New Revision: 707659

URL: http://svn.apache.org/viewvc?rev=707659&view=rev
Log:
Fix the Felix-794 issue
The event admin handler now accepts to receive events on topics containing a '*' (at the end). 

Modified:
    felix/trunk/ipojo/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
    felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/EventUtil.java
    felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/publisher/EventAdminPublisherMetadata.java
    felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/subscriber/EventAdminSubscriberMetadata.java
    felix/trunk/ipojo/tests/handler/eventadmin/src/main/java/org/apache/felix/ipojo/test/BadTests.java
    felix/trunk/ipojo/tests/handler/eventadmin/src/main/java/org/apache/felix/ipojo/test/GoodTests.java
    felix/trunk/ipojo/tests/handler/eventadmin/src/main/resources/metadata.xml

Modified: felix/trunk/ipojo/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java
URL: http://svn.apache.org/viewvc/felix/trunk/ipojo/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java?rev=707659&r1=707658&r2=707659&view=diff
==============================================================================
--- felix/trunk/ipojo/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java (original)
+++ felix/trunk/ipojo/core/src/main/java/org/apache/felix/ipojo/util/DependencyModel.java Fri Oct 24 08:48:18 2008
@@ -24,7 +24,6 @@
 import java.util.List;
 
 import org.apache.felix.ipojo.ConfigurationException;
-import org.apache.felix.ipojo.ServiceContext;
 import org.apache.felix.ipojo.context.ServiceReferenceImpl;
 import org.apache.felix.ipojo.metadata.Element;
 import org.osgi.framework.BundleContext;

Modified: felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/EventUtil.java
URL: http://svn.apache.org/viewvc/felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/EventUtil.java?rev=707659&r1=707658&r2=707659&view=diff
==============================================================================
--- felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/EventUtil.java (original)
+++ felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/EventUtil.java Fri Oct 24 08:48:18 2008
@@ -18,6 +18,8 @@
  */
 package org.apache.felix.ipojo.handlers.event;
 
+import org.apache.felix.ipojo.parser.ParseUtils;
+
 /**
  * Utility methods.
  * 
@@ -25,40 +27,153 @@
  */
 public class EventUtil {
 
-    /**
-     * Tests that the given topic match with the given topic pattern.
-     * 
-     * @param topic the topic to test
-     * @param topicPattern the topic pattern
-     * @return true if it matches.
-     */
-    public static boolean matches(String topic, String topicPattern) {
-        if (topicPattern.equals("*")) {
-            return true;
-        }
-        int star;
-        if ((star = topicPattern.indexOf("*")) > 0) {
-            return topic.startsWith(topicPattern.substring(0, star - 1));
-        } else {
-            return topic.equals(topicPattern);
-        }
-    }
-
-    /**
-     * Tests that the given topic match with the given topic patterns.
-     * 
-     * @param topic the topic to test
-     * @param topicPatterns the topic patterns
-     * @return true if it matches.
-     */
-    public static boolean matches(String topic, String[] topicPatterns) {
-        int n = topicPatterns.length;
-        for (int i = 0; i < n; i++) {
-            if (matches(topic, topicPatterns[i])) {
-                return true;
-            }
-        }
-        return false;
-    }
+	/**
+	 * The separator between topics.
+	 */
+	public static final String TOPIC_SEPARATOR = ",";
+
+	/**
+	 * The separator between topics.
+	 */
+	public static final String TOPIC_TOKEN_SEPARATOR = "/";
+
+	/**
+	 * The topic token alphabet.
+	 */
+	private static final String tokenAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
+
+	/**
+	 * Tests that the given topic match with the given topic pattern.
+	 * 
+	 * @param topic
+	 *            the topic to test
+	 * @param topicPattern
+	 *            the topic pattern
+	 * @return true if it matches.
+	 */
+	public static final boolean matches(String topic, String topicPattern) {
+		if (topicPattern.equals("*")) {
+			return true;
+		}
+		int star;
+		if ((star = topicPattern.indexOf("*")) > 0) {
+			return topic.startsWith(topicPattern.substring(0, star - 1));
+		} else {
+			return topic.equals(topicPattern);
+		}
+	}
+
+	/**
+	 * Tests that the given topic match with the given topic patterns.
+	 * 
+	 * @param topic
+	 *            the topic to test
+	 * @param topicPatterns
+	 *            the topic patterns
+	 * @return true if it matches.
+	 */
+	public static final boolean matches(String topic, String[] topicPatterns) {
+		int n = topicPatterns.length;
+		for (int i = 0; i < n; i++) {
+			if (matches(topic, topicPatterns[i])) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Check the given topic scope is valid.
+	 * 
+	 * topicScope ::= "*" | ( topic "/*" ? )
+	 * 
+	 * @param topicScope
+	 *            the topic scope to check.
+	 * 
+	 * @return {@code true} if the given topic scope is valid, {@code false}
+	 *         otherwise.
+	 */
+	public static final boolean isValidTopicScope(String topicScope) {
+		if (topicScope.equals("*")) {
+			// Wildcard character only accepted.
+			return true;
+		}
+		
+		// Remove trailing "/*" if present, to check the topic radix.
+		String topicWithoutWildcard;
+		if (topicScope.endsWith("/*")) {
+			topicWithoutWildcard = topicScope.substring(0, topicScope.length() - 2);
+		} else {
+			topicWithoutWildcard = topicScope;
+		}
+		
+		// Validate the topic radix.
+		return isValidTopic(topicWithoutWildcard);
+	}
+
+	/**
+	 * Check the given topic is valid.
+	 * 
+	 * topic ::= token ( ’/’ token ) *
+	 * 
+	 * @param topic
+	 *            the topic to check.
+	 * 
+	 * @return {@code true} if the given topic is valid, {@code false}
+	 *         otherwise.
+	 */
+	public static final boolean isValidTopic(String topic) {
+		if (topic.startsWith(TOPIC_TOKEN_SEPARATOR)
+				|| topic.endsWith(TOPIC_TOKEN_SEPARATOR)) {
+			// A topic cannot start nor end with '/'.
+			return false;
+		}
+
+		String[] tokens = ParseUtils.split(topic, TOPIC_TOKEN_SEPARATOR);
+		if (tokens.length < 1) {
+			// A topic must contain at least one token.
+			return false;
+		}
+
+		// Check each token is valid.
+		for (int i = 0; i < tokens.length; i++) {
+			String token = tokens[i];
+			if (!isValidToken(token)) {
+				return false;
+			}
+		}
+
+		// The topic is valid.
+		return true;
+	}
+
+	/**
+	 * Check the given token is valid.
+	 * 
+	 * token ::= ( alphanum | "_" | "-" )+
+	 * 
+	 * @param token
+	 *            the token to check.
+	 * 
+	 * @return {@code true} if the given topic token is valid, {@code false}
+	 *         otherwise.
+	 */
+	private static boolean isValidToken(String token) {
+		int length = token.length();
+		if (length < 1) {
+			// Token must contain at least one character.
+			return false;
+		}
+
+		for (int i = 0; i < length; i++) {
+			// Each character in the token must belong to the token alphabet.
+			if (tokenAlphabet.indexOf(token.charAt(i)) == -1) {
+				return false;
+			}
+		}
+
+		// The token is valid.
+		return true;
+	}
 
 }

Modified: felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/publisher/EventAdminPublisherMetadata.java
URL: http://svn.apache.org/viewvc/felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/publisher/EventAdminPublisherMetadata.java?rev=707659&r1=707658&r2=707659&view=diff
==============================================================================
--- felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/publisher/EventAdminPublisherMetadata.java (original)
+++ felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/publisher/EventAdminPublisherMetadata.java Fri Oct 24 08:48:18 2008
@@ -18,13 +18,10 @@
  */
 package org.apache.felix.ipojo.handlers.event.publisher;
 
-import java.util.Dictionary;
-import java.util.Hashtable;
-
 import org.apache.felix.ipojo.ConfigurationException;
+import org.apache.felix.ipojo.handlers.event.EventUtil;
 import org.apache.felix.ipojo.metadata.Element;
 import org.apache.felix.ipojo.parser.ParseUtils;
-import org.osgi.service.event.Event;
 
 /**
  * Represent a publisher.
@@ -33,221 +30,212 @@
  */
 class EventAdminPublisherMetadata {
 
-    // Names of metadata attributes
+	// Names of metadata attributes
 
-    /**
-     * The name attribute in the component metadata.
-     */
-    public static final String NAME_ATTRIBUTE = "name";
-
-    /**
-     * The field attribute in the component metadata.
-     */
-    public static final String FIELD_ATTRIBUTE = "field";
-
-    /**
-     * The topics attribute in the component metadata.
-     */
-    public static final String TOPICS_ATTRIBUTE = "topics";
-
-    /**
-     * The synchronous attribute in the component metadata.
-     */
-    public static final String SYNCHRONOUS_ATTRIBUTE = "synchronous";
-
-    /**
-     * The data key attribute in the component metadata.
-     */
-    public static final String DATA_KEY_ATTRIBUTE = "data-key";
-
-    // Default values
-
-    /**
-     * The data key attribute's default value.
-     */
-    public static final String DEFAULT_DATA_KEY_VALUE = "user.data";
-
-    /**
-     * The synchronous attribute's default value.
-     */
-    public static final boolean DEFAULT_SYNCHRONOUS_VALUE = false;
-
-    /**
-     * The name which acts as an identifier.
-     */
-    private final String m_name;
-
-    /**
-     * The name of the field representing the publisher in the component.
-     */
-    private final String m_field;
-
-    /**
-     * Topics to which events are sent.
-     */
-    private String[] m_topics;
-
-    /**
-     * Events sending mode.
-     */
-    private final boolean m_synchronous;
-
-    /**
-     * The key where user data are stored in the event dictionary.
-     */
-    private final String m_dataKey;
-
-    /**
-     * Constructs a publisher from its metadata description.
-     * 
-     * @param publisher the publisher metadata description.
-     * @throws ConfigurationException if the configuration of the component or the instance is invalid.
-     */
-    public EventAdminPublisherMetadata(Element publisher)
-        throws ConfigurationException {
-
-        /**
-         * Setup required attributes
-         */
-
-        // NAME_ATTRIBUTE
-        if (publisher.containsAttribute(NAME_ATTRIBUTE)) {
-            m_name = publisher.getAttribute(NAME_ATTRIBUTE);
-        } else {
-            throw new ConfigurationException(
-                    "Missing required attribute in component configuration : "
-                            + NAME_ATTRIBUTE);
-        }
-
-        // FIELD_ATTRIBUTE
-        if (publisher.containsAttribute(FIELD_ATTRIBUTE)) {
-            m_field = publisher.getAttribute(FIELD_ATTRIBUTE);
-        } else {
-            throw new ConfigurationException(
-                    "Missing required attribute in component configuration : "
-                            + FIELD_ATTRIBUTE);
-        }
-
-        // TOPICS_ATTRIBUTE
-        if (publisher.containsAttribute(TOPICS_ATTRIBUTE)) {
-            m_topics = ParseUtils.split(publisher
-                    .getAttribute(TOPICS_ATTRIBUTE), ",");
-            // Check each topic is valid
-            Dictionary empty = new Hashtable();
-            for (int i = 0; i < m_topics.length; i++) {
-                String topic = m_topics[i];
-                try {
-                    new Event(topic, empty);
-                } catch (IllegalArgumentException e) {
-                    throw new ConfigurationException("Malformed topic : "
-                            + topic);
-                }
-            }
-
-        } else {
-            m_topics = null;
-            // Nothing to do if TOPICS_ATTRIBUTE is not present as it can be
-            // overridden in the instance configuration.
-        }
-
-        /**
-         * Setup optional attributes
-         */
-
-        // SYNCHRONOUS_ATTRIBUTE
-        if (publisher.containsAttribute(SYNCHRONOUS_ATTRIBUTE)) {
-            m_synchronous = "true".equalsIgnoreCase(publisher
-                    .getAttribute(SYNCHRONOUS_ATTRIBUTE));
-        } else {
-            m_synchronous = DEFAULT_SYNCHRONOUS_VALUE;
-        }
-
-        // DATA_KEY_ATTRIBUTE
-        if (publisher.containsAttribute(DATA_KEY_ATTRIBUTE)) {
-            m_dataKey = publisher.getAttribute(DATA_KEY_ATTRIBUTE);
-        } else {
-            m_dataKey = DEFAULT_DATA_KEY_VALUE;
-        }
-    }
-
-    /**
-     * Sets the topics attribute of the publisher.
-     * 
-     * @param topicsString the comma separated list of the topics on which events are sent
-     * @throws ConfigurationException the specified topic string is malformed
-     */
-    public void setTopics(String topicsString)
-        throws ConfigurationException {
-        m_topics = ParseUtils.split(topicsString, ",");
-        // Check each topic is valid
-        Dictionary empty = new Hashtable();
-        for (int i = 0; i < m_topics.length; i++) {
-            String topic = m_topics[i];
-            try {
-                new Event(topic, empty);
-            } catch (IllegalArgumentException e) {
-                throw new ConfigurationException("Malformed topic : " + topic);
-            }
-        }
-
-    }
-
-    /**
-     * Checks that the required instance configurable attributes are all set.
-     * 
-     * @throws ConfigurationException if a required attribute is missing
-     */
-    public void check()
-        throws ConfigurationException {
-        if (m_topics == null || m_topics.length == 0) {
-            throw new ConfigurationException(
-                    "Missing required attribute in component or instance configuration : "
-                            + TOPICS_ATTRIBUTE);
-        }
-    }
-
-    /**
-     * Gets the name attribute of the publisher.
-     * 
-     * @return the name
-     */
-    public String getName() {
-        return m_name;
-    }
-
-    /**
-     * Gets the field attribute of the publisher.
-     * 
-     * @return the field
-     */
-    public String getField() {
-        return m_field;
-    }
-
-    /**
-     * Gets the topics attribute of the publisher.
-     * 
-     * @return the topics
-     */
-    public String[] getTopics() {
-        return m_topics;
-    }
-
-    /**
-     * Gets the synchronous attribute of the publisher.
-     * 
-     * @return the synchronous mode
-     */
-    public boolean isSynchronous() {
-        return m_synchronous;
-    }
-
-    /**
-     * Gets the dataKey attribute of the publisher.
-     * 
-     * @return the data key
-     */
-    public String getDataKey() {
-        return m_dataKey;
-    }
+	/**
+	 * The name attribute in the component metadata.
+	 */
+	public static final String NAME_ATTRIBUTE = "name";
+
+	/**
+	 * The field attribute in the component metadata.
+	 */
+	public static final String FIELD_ATTRIBUTE = "field";
+
+	/**
+	 * The topics attribute in the component metadata.
+	 */
+	public static final String TOPICS_ATTRIBUTE = "topics";
+
+	/**
+	 * The synchronous attribute in the component metadata.
+	 */
+	public static final String SYNCHRONOUS_ATTRIBUTE = "synchronous";
+
+	/**
+	 * The data key attribute in the component metadata.
+	 */
+	public static final String DATA_KEY_ATTRIBUTE = "data-key";
+
+	// Default values
+
+	/**
+	 * The data key attribute's default value.
+	 */
+	public static final String DEFAULT_DATA_KEY_VALUE = "user.data";
+
+	/**
+	 * The synchronous attribute's default value.
+	 */
+	public static final boolean DEFAULT_SYNCHRONOUS_VALUE = false;
+
+	/**
+	 * The name which acts as an identifier.
+	 */
+	private final String m_name;
+
+	/**
+	 * The name of the field representing the publisher in the component.
+	 */
+	private final String m_field;
+
+	/**
+	 * Topics to which events are sent.
+	 */
+	private String[] m_topics;
+
+	/**
+	 * Events sending mode.
+	 */
+	private final boolean m_synchronous;
+
+	/**
+	 * The key where user data are stored in the event dictionary.
+	 */
+	private final String m_dataKey;
+
+	/**
+	 * Constructs a publisher from its metadata description.
+	 * 
+	 * @param publisher
+	 *            the publisher metadata description.
+	 * @throws ConfigurationException
+	 *             if the configuration of the component or the instance is
+	 *             invalid.
+	 */
+	public EventAdminPublisherMetadata(Element publisher)
+			throws ConfigurationException {
+
+		/**
+		 * Setup required attributes
+		 */
+
+		// NAME_ATTRIBUTE
+		if (publisher.containsAttribute(NAME_ATTRIBUTE)) {
+			m_name = publisher.getAttribute(NAME_ATTRIBUTE);
+		} else {
+			throw new ConfigurationException(
+					"Missing required attribute in component configuration : "
+							+ NAME_ATTRIBUTE);
+		}
+
+		// FIELD_ATTRIBUTE
+		if (publisher.containsAttribute(FIELD_ATTRIBUTE)) {
+			m_field = publisher.getAttribute(FIELD_ATTRIBUTE);
+		} else {
+			throw new ConfigurationException(
+					"Missing required attribute in component configuration : "
+							+ FIELD_ATTRIBUTE);
+		}
+
+		// TOPICS_ATTRIBUTE
+		if (publisher.containsAttribute(TOPICS_ATTRIBUTE)) {
+			setTopics(publisher.getAttribute(TOPICS_ATTRIBUTE));
+		} else {
+			m_topics = null;
+			// Nothing to do if TOPICS_ATTRIBUTE is not present as it can be
+			// overridden in the instance configuration.
+		}
+
+		/**
+		 * Setup optional attributes
+		 */
+
+		// SYNCHRONOUS_ATTRIBUTE
+		if (publisher.containsAttribute(SYNCHRONOUS_ATTRIBUTE)) {
+			m_synchronous = "true".equalsIgnoreCase(publisher
+					.getAttribute(SYNCHRONOUS_ATTRIBUTE));
+		} else {
+			m_synchronous = DEFAULT_SYNCHRONOUS_VALUE;
+		}
+
+		// DATA_KEY_ATTRIBUTE
+		if (publisher.containsAttribute(DATA_KEY_ATTRIBUTE)) {
+			m_dataKey = publisher.getAttribute(DATA_KEY_ATTRIBUTE);
+		} else {
+			m_dataKey = DEFAULT_DATA_KEY_VALUE;
+		}
+	}
+
+	/**
+	 * Sets the topics attribute of the publisher.
+	 * 
+	 * @param topicsString
+	 *            the comma separated list of the topics on which events are
+	 *            sent
+	 * @throws ConfigurationException
+	 *             the specified topic string is malformed
+	 */
+	public void setTopics(String topicsString) throws ConfigurationException {
+		String[] newTopics = ParseUtils.split(topicsString,
+				EventUtil.TOPIC_SEPARATOR);
+		// Check each topic is valid
+		for (int i = 0; i < newTopics.length; i++) {
+			String topic = newTopics[i];
+			if (!EventUtil.isValidTopic(topic)) {
+				throw new ConfigurationException("Invalid topic : \"" + topic
+						+ "\".");
+			}
+		}
+		m_topics = newTopics;
+	}
+
+	/**
+	 * Checks that the required instance configurable attributes are all set.
+	 * 
+	 * @throws ConfigurationException
+	 *             if a required attribute is missing
+	 */
+	public void check() throws ConfigurationException {
+		if (m_topics == null || m_topics.length == 0) {
+			throw new ConfigurationException(
+					"Missing required attribute in component or instance configuration : "
+							+ TOPICS_ATTRIBUTE);
+		}
+	}
+
+	/**
+	 * Gets the name attribute of the publisher.
+	 * 
+	 * @return the name
+	 */
+	public String getName() {
+		return m_name;
+	}
+
+	/**
+	 * Gets the field attribute of the publisher.
+	 * 
+	 * @return the field
+	 */
+	public String getField() {
+		return m_field;
+	}
+
+	/**
+	 * Gets the topics attribute of the publisher.
+	 * 
+	 * @return the topics
+	 */
+	public String[] getTopics() {
+		return m_topics;
+	}
+
+	/**
+	 * Gets the synchronous attribute of the publisher.
+	 * 
+	 * @return the synchronous mode
+	 */
+	public boolean isSynchronous() {
+		return m_synchronous;
+	}
+
+	/**
+	 * Gets the dataKey attribute of the publisher.
+	 * 
+	 * @return the data key
+	 */
+	public String getDataKey() {
+		return m_dataKey;
+	}
 }

Modified: felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/subscriber/EventAdminSubscriberMetadata.java
URL: http://svn.apache.org/viewvc/felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/subscriber/EventAdminSubscriberMetadata.java?rev=707659&r1=707658&r2=707659&view=diff
==============================================================================
--- felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/subscriber/EventAdminSubscriberMetadata.java (original)
+++ felix/trunk/ipojo/handler/eventadmin/src/main/java/org/apache/felix/ipojo/handlers/event/subscriber/EventAdminSubscriberMetadata.java Fri Oct 24 08:48:18 2008
@@ -18,16 +18,13 @@
  */
 package org.apache.felix.ipojo.handlers.event.subscriber;
 
-import java.util.Dictionary;
-import java.util.Hashtable;
-
 import org.apache.felix.ipojo.ConfigurationException;
+import org.apache.felix.ipojo.handlers.event.EventUtil;
 import org.apache.felix.ipojo.metadata.Element;
 import org.apache.felix.ipojo.parser.ParseUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.service.event.Event;
 
 /**
  * Represent an subscriber.
@@ -36,269 +33,255 @@
  */
 class EventAdminSubscriberMetadata {
 
-    // Names of metadata attributes
-
-    /**
-     * The name attribute in the component metadata.
-     */
-    public static final String NAME_ATTRIBUTE = "name";
-
-    /**
-     * The callback attribute in the component metadata.
-     */
-    public static final String CALLBACK_ATTRIBUTE = "callback";
-
-    /**
-     * The topics attribute in the component metadata.
-     */
-    public static final String TOPICS_ATTRIBUTE = "topics";
-
-    /**
-     * The data key attribute in the component metadata.
-     */
-    public static final String DATA_KEY_ATTRIBUTE = "data-key";
-
-    /**
-     * The data type attribute in the component metadata.
-     */
-    public static final String DATA_TYPE_ATTRIBUTE = "data-type";
-
-    /**
-     * The filter attribute in the component metadata.
-     */
-    public static final String FILTER_ATTRIBUTE = "filter";
-
-    // Default values
-
-    /**
-     * The data type atttribute's default value.
-     */
-    public static final Class DEFAULT_DATA_TYPE_VALUE = java.lang.Object.class;
-
-    /**
-     * The name which acts as an identifier.
-     */
-    private final String m_name;
-
-    /**
-     * Name of the callback method.
-     */
-    private final String m_callback;
-
-    /**
-     * Listened topics.
-     */
-    private String[] m_topics;
-
-    /**
-     * The key where user data are stored in the event dictionary.
-     */
-    private final String m_dataKey;
-
-    /**
-     * The type of received data.
-     */
-    private final Class m_dataType;
-
-    /**
-     * Event filter.
-     */
-    private Filter m_filter;
-
-    /**
-     * The context of the bundle.
-     */
-    private final BundleContext m_bundleContext;
-
-    /**
-     * Constructor.
-     * 
-     * @param bundleContext the bundle context of the managed instance.
-     * @param subscriber the subscriber metadata.
-     * @throws ConfigurationException if the configuration of the component or the instance is invalid.
-     */
-    public EventAdminSubscriberMetadata(BundleContext bundleContext,
-            Element subscriber)
-        throws ConfigurationException {
-
-        m_bundleContext = bundleContext;
-
-        /**
-         * Setup required attributes
-         */
-
-        // NAME_ATTRIBUTE
-        if (subscriber.containsAttribute(NAME_ATTRIBUTE)) {
-            m_name = subscriber.getAttribute(NAME_ATTRIBUTE);
-        } else {
-            throw new ConfigurationException(
-                    "Missing required attribute in component configuration : "
-                            + NAME_ATTRIBUTE);
-        }
-
-        // CALLBACK_ATTRIBUTE
-        if (subscriber.containsAttribute(CALLBACK_ATTRIBUTE)) {
-            m_callback = subscriber.getAttribute(CALLBACK_ATTRIBUTE);
-        } else {
-            throw new ConfigurationException(
-                    "Missing required attribute in component configuration : "
-                            + CALLBACK_ATTRIBUTE);
-        }
-
-        // TOPICS_ATTRIBUTE
-        if (subscriber.containsAttribute(TOPICS_ATTRIBUTE)) {
-            m_topics = ParseUtils.split(subscriber
-                    .getAttribute(TOPICS_ATTRIBUTE), ",");
-            // Check each topic is valid
-            Dictionary empty = new Hashtable();
-            for (int i = 0; i < m_topics.length; i++) {
-                String topic = m_topics[i];
-                try {
-                    new Event(topic, empty);
-                } catch (IllegalArgumentException e) {
-                    throw new ConfigurationException("Malformed topic : "
-                            + topic);
-                }
-            }
-        } else {
-            m_topics = null;
-            // Nothing to do if TOPICS_ATTRIBUTE is not present as it can be
-            // overridden in the instance configuration.
-        }
-
-        /**
-         * Setup optional attributes
-         */
-
-        // DATA_KEY_ATTRIBUTE
-        m_dataKey = subscriber.getAttribute(DATA_KEY_ATTRIBUTE);
-        if (subscriber.containsAttribute(DATA_TYPE_ATTRIBUTE)) {
-            Class type;
-            String typeName = subscriber.getAttribute(DATA_TYPE_ATTRIBUTE);
-            try {
-                type = m_bundleContext.getBundle().loadClass(typeName);
-            } catch (ClassNotFoundException e) {
-                throw new ConfigurationException("Data type class not found : "
-                        + typeName);
-            }
-            m_dataType = type;
-        } else {
-            m_dataType = DEFAULT_DATA_TYPE_VALUE;
-        }
-
-        // FILTER_ATTRIBUTE
-        if (subscriber.containsAttribute(FILTER_ATTRIBUTE)) {
-            try {
-                m_filter = m_bundleContext.createFilter(subscriber
-                        .getAttribute(FILTER_ATTRIBUTE));
-            } catch (InvalidSyntaxException e) {
-                throw new ConfigurationException("Invalid filter syntax");
-            }
-        }
-    }
-
-    /**
-     * Sets the topics attribute of the subscriber.
-     * 
-     * @param topicsString the comma separated list of the topics to listen
-     * @throws ConfigurationException if  the specified topic list is malformed
-     */
-    public void setTopics(String topicsString)
-        throws ConfigurationException {
-        m_topics = ParseUtils.split(topicsString, ",");
-        // Check each topic is valid
-        Dictionary empty = new Hashtable();
-        for (int i = 0; i < m_topics.length; i++) {
-            String topic = m_topics[i];
-            try {
-                new Event(topic, empty);
-            } catch (IllegalArgumentException e) {
-                throw new ConfigurationException("Malformed topic : " + topic);
-            }
-        }
-    }
-
-    /**
-     * Sets the filter attribute of the subscriber.
-     * 
-     * @param filterString the string representation of the event filter
-     * @throws ConfigurationException if the LDAP filter is malformed
-     */
-    public void setFilter(String filterString)
-        throws ConfigurationException {
-        try {
-            m_filter = m_bundleContext.createFilter(filterString);
-        } catch (InvalidSyntaxException e) {
-            throw new ConfigurationException("Invalid filter syntax");
-        }
-    }
-
-    /**
-     * Checks that the required instance configurable attributes are all set.
-     * 
-     * @throws ConfigurationException if a required attribute is missing
-     */
-    public void check()
-        throws ConfigurationException {
-        if (m_topics == null || m_topics.length == 0) {
-            throw new ConfigurationException(
-                    "Missing required attribute in component or instance configuration : "
-                            + TOPICS_ATTRIBUTE);
-        }
-    }
-
-    /**
-     * Gets the name attribute of the subscriber.
-     * 
-     * @return the name
-     */
-    public String getName() {
-        return m_name;
-    }
-
-    /**
-     * Gets the topics attribute of the subscriber.
-     * 
-     * @return the topics
-     */
-    public String[] getTopics() {
-        return m_topics;
-    }
-
-    /**
-     * Gets the callback attribute of the subscriber.
-     * 
-     * @return the callback
-     */
-    public String getCallback() {
-        return m_callback;
-    }
-
-    /**
-     * Gets the data key attribute of the subscriber.
-     * 
-     * @return the dataKey
-     */
-    public String getDataKey() {
-        return m_dataKey;
-    }
-
-    /**
-     * Gets the data type attribute of the subscriber.
-     * 
-     * @return the dataType
-     */
-    public Class getDataType() {
-        return m_dataType;
-    }
-
-    /**
-     * Gets the filter attribute of the subscriber.
-     * 
-     * @return the filter
-     */
-    public Filter getFilter() {
-        return m_filter;
-    }
+	// Names of metadata attributes
 
+	/**
+	 * The name attribute in the component metadata.
+	 */
+	public static final String NAME_ATTRIBUTE = "name";
+
+	/**
+	 * The callback attribute in the component metadata.
+	 */
+	public static final String CALLBACK_ATTRIBUTE = "callback";
+
+	/**
+	 * The topics attribute in the component metadata.
+	 */
+	public static final String TOPICS_ATTRIBUTE = "topics";
+
+	/**
+	 * The data key attribute in the component metadata.
+	 */
+	public static final String DATA_KEY_ATTRIBUTE = "data-key";
+
+	/**
+	 * The data type attribute in the component metadata.
+	 */
+	public static final String DATA_TYPE_ATTRIBUTE = "data-type";
+
+	/**
+	 * The filter attribute in the component metadata.
+	 */
+	public static final String FILTER_ATTRIBUTE = "filter";
+
+	// Default values
+
+	/**
+	 * The data type atttribute's default value.
+	 */
+	public static final Class DEFAULT_DATA_TYPE_VALUE = java.lang.Object.class;
+
+	/**
+	 * The name which acts as an identifier.
+	 */
+	private final String m_name;
+
+	/**
+	 * Name of the callback method.
+	 */
+	private final String m_callback;
+
+	/**
+	 * Listened topics.
+	 */
+	private String[] m_topics;
+
+	/**
+	 * The key where user data are stored in the event dictionary.
+	 */
+	private final String m_dataKey;
+
+	/**
+	 * The type of received data.
+	 */
+	private final Class m_dataType;
+
+	/**
+	 * Event filter.
+	 */
+	private Filter m_filter;
+
+	/**
+	 * The context of the bundle.
+	 */
+	private final BundleContext m_bundleContext;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param bundleContext
+	 *            the bundle context of the managed instance.
+	 * @param subscriber
+	 *            the subscriber metadata.
+	 * @throws ConfigurationException
+	 *             if the configuration of the component or the instance is
+	 *             invalid.
+	 */
+	public EventAdminSubscriberMetadata(BundleContext bundleContext,
+			Element subscriber) throws ConfigurationException {
+		m_bundleContext = bundleContext;
+
+		/**
+		 * Setup required attributes
+		 */
+
+		// NAME_ATTRIBUTE
+		if (subscriber.containsAttribute(NAME_ATTRIBUTE)) {
+			m_name = subscriber.getAttribute(NAME_ATTRIBUTE);
+		} else {
+			throw new ConfigurationException(
+					"Missing required attribute in component configuration : "
+							+ NAME_ATTRIBUTE);
+		}
+
+		// CALLBACK_ATTRIBUTE
+		if (subscriber.containsAttribute(CALLBACK_ATTRIBUTE)) {
+			m_callback = subscriber.getAttribute(CALLBACK_ATTRIBUTE);
+		} else {
+			throw new ConfigurationException(
+					"Missing required attribute in component configuration : "
+							+ CALLBACK_ATTRIBUTE);
+		}
+
+		// TOPICS_ATTRIBUTE
+		if (subscriber.containsAttribute(TOPICS_ATTRIBUTE)) {
+			setTopics(subscriber.getAttribute(TOPICS_ATTRIBUTE));
+		} else {
+			m_topics = null;
+			// Nothing to do if TOPICS_ATTRIBUTE is not present as it can be
+			// overridden in the instance configuration.
+		}
+
+		/**
+		 * Setup optional attributes
+		 */
+
+		// DATA_KEY_ATTRIBUTE
+		m_dataKey = subscriber.getAttribute(DATA_KEY_ATTRIBUTE);
+		if (subscriber.containsAttribute(DATA_TYPE_ATTRIBUTE)) {
+			Class type;
+			String typeName = subscriber.getAttribute(DATA_TYPE_ATTRIBUTE);
+			try {
+				type = m_bundleContext.getBundle().loadClass(typeName);
+			} catch (ClassNotFoundException e) {
+				throw new ConfigurationException("Data type class not found : "
+						+ typeName);
+			}
+			m_dataType = type;
+		} else {
+			m_dataType = DEFAULT_DATA_TYPE_VALUE;
+		}
+
+		// FILTER_ATTRIBUTE
+		if (subscriber.containsAttribute(FILTER_ATTRIBUTE)) {
+			setFilter(subscriber.getAttribute(FILTER_ATTRIBUTE));
+		}
+	}
+
+	/**
+	 * Sets the topics attribute of the subscriber.
+	 * 
+	 * @param topicsString
+	 *            the comma separated list of the topics to listen
+	 * @throws ConfigurationException
+	 *             if the specified topic list is malformed
+	 */
+	public void setTopics(String topicsString) throws ConfigurationException {
+		String[] newTopics = ParseUtils.split(topicsString,
+				EventUtil.TOPIC_SEPARATOR);
+		// Check each topic is valid
+		for (int i = 0; i < newTopics.length; i++) {
+			String topicScope = newTopics[i];
+			if (!EventUtil.isValidTopicScope(topicScope)) {
+				throw new ConfigurationException("Invalid topic scope : \""
+						+ topicScope + "\".");
+			}
+		}
+		m_topics = newTopics;
+	}
+
+	/**
+	 * Sets the filter attribute of the subscriber.
+	 * 
+	 * @param filterString
+	 *            the string representation of the event filter
+	 * @throws ConfigurationException
+	 *             if the LDAP filter is malformed
+	 */
+	public void setFilter(String filterString) throws ConfigurationException {
+		try {
+			m_filter = m_bundleContext.createFilter(filterString);
+		} catch (InvalidSyntaxException e) {
+			throw new ConfigurationException("Invalid filter syntax");
+		}
+	}
+
+	/**
+	 * Checks that the required instance configurable attributes are all set.
+	 * 
+	 * @throws ConfigurationException
+	 *             if a required attribute is missing
+	 */
+	public void check() throws ConfigurationException {
+		if (m_topics == null || m_topics.length == 0) {
+			throw new ConfigurationException(
+					"Missing required attribute in component or instance configuration : "
+							+ TOPICS_ATTRIBUTE);
+		}
+	}
+
+	/**
+	 * Gets the name attribute of the subscriber.
+	 * 
+	 * @return the name
+	 */
+	public String getName() {
+		return m_name;
+	}
+
+	/**
+	 * Gets the topics attribute of the subscriber.
+	 * 
+	 * @return the topics
+	 */
+	public String[] getTopics() {
+		return m_topics;
+	}
+
+	/**
+	 * Gets the callback attribute of the subscriber.
+	 * 
+	 * @return the callback
+	 */
+	public String getCallback() {
+		return m_callback;
+	}
+
+	/**
+	 * Gets the data key attribute of the subscriber.
+	 * 
+	 * @return the dataKey
+	 */
+	public String getDataKey() {
+		return m_dataKey;
+	}
+
+	/**
+	 * Gets the data type attribute of the subscriber.
+	 * 
+	 * @return the dataType
+	 */
+	public Class getDataType() {
+		return m_dataType;
+	}
+
+	/**
+	 * Gets the filter attribute of the subscriber.
+	 * 
+	 * @return the filter
+	 */
+	public Filter getFilter() {
+		return m_filter;
+	}
 }