You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by cs...@apache.org on 2016/03/16 18:35:59 UTC

karaf git commit: [KARAF-4370] EventAdmin commands

Repository: karaf
Updated Branches:
  refs/heads/master b8c2617c2 -> 8aabfbd43


[KARAF-4370] EventAdmin commands


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/8aabfbd4
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/8aabfbd4
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/8aabfbd4

Branch: refs/heads/master
Commit: 8aabfbd4368327117eb20dc79e420aa050613c33
Parents: b8c2617
Author: Christian Schneider <ch...@die-schneider.net>
Authored: Wed Mar 16 18:35:37 2016 +0100
Committer: Christian Schneider <ch...@die-schneider.net>
Committed: Wed Mar 16 18:35:37 2016 +0100

----------------------------------------------------------------------
 event/.gitignore                                |  1 +
 event/pom.xml                                   | 90 ++++++++++++++++++++
 .../apache/karaf/event/EventDisplayCommand.java | 57 +++++++++++++
 .../org/apache/karaf/event/EventPrinter.java    | 69 +++++++++++++++
 .../apache/karaf/event/EventSendCommand.java    | 64 ++++++++++++++
 .../apache/karaf/event/EventTailCommand.java    | 76 +++++++++++++++++
 .../karaf/event/service/EventCollector.java     | 69 +++++++++++++++
 .../karaf/event/service/TopicPredicate.java     | 40 +++++++++
 .../karaf/event/EventDisplayCommandTest.java    | 44 ++++++++++
 .../apache/karaf/event/EventPrinterTest.java    | 70 +++++++++++++++
 .../karaf/event/EventSendCommandTest.java       | 55 ++++++++++++
 .../karaf/event/EventTailCommandTest.java       | 70 +++++++++++++++
 .../karaf/event/service/EventCollectorTest.java | 76 +++++++++++++++++
 .../karaf/event/service/TopicPredicateTest.java | 57 +++++++++++++
 pom.xml                                         |  1 +
 15 files changed, 839 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/.gitignore
----------------------------------------------------------------------
diff --git a/event/.gitignore b/event/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/event/.gitignore
@@ -0,0 +1 @@
+/target/

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/pom.xml
----------------------------------------------------------------------
diff --git a/event/pom.xml b/event/pom.xml
new file mode 100644
index 0000000..748e5e6
--- /dev/null
+++ b/event/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>karaf</artifactId>
+        <groupId>org.apache.karaf</groupId>
+        <version>4.1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.karaf.event</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: OSGi Services :: Event</name>
+    <description>EventAdmin Commands</description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <_dsannotations>*</_dsannotations>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.eventadmin</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+            <version>4.0.4</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-all</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/main/java/org/apache/karaf/event/EventDisplayCommand.java
----------------------------------------------------------------------
diff --git a/event/src/main/java/org/apache/karaf/event/EventDisplayCommand.java b/event/src/main/java/org/apache/karaf/event/EventDisplayCommand.java
new file mode 100644
index 0000000..37d8f3c
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/event/EventDisplayCommand.java
@@ -0,0 +1,57 @@
+/*
+ * 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.karaf.event;
+
+import static org.apache.karaf.event.service.TopicPredicate.matchTopic;
+
+import org.apache.karaf.event.service.EventCollector;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.osgi.framework.BundleContext;
+
+@Command(scope = "event", name = "display", description = "Shows events")
+@Service
+public class EventDisplayCommand implements Action {
+
+    @Reference
+    Session session;
+
+    @Reference
+    BundleContext context;
+
+    @Reference
+    EventCollector collector;
+
+    @Argument
+    String topicFilter = "*";
+
+    @Option(name = "-v")
+    boolean verbose = false;
+
+    @Override
+    public Object execute() throws Exception {
+        EventPrinter printer = new EventPrinter(session.getConsole(), verbose);
+        collector.getEvents().filter(matchTopic(topicFilter)).forEach(printer);
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/main/java/org/apache/karaf/event/EventPrinter.java
----------------------------------------------------------------------
diff --git a/event/src/main/java/org/apache/karaf/event/EventPrinter.java b/event/src/main/java/org/apache/karaf/event/EventPrinter.java
new file mode 100644
index 0000000..314dc9d
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/event/EventPrinter.java
@@ -0,0 +1,69 @@
+/*
+ * 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.karaf.event;
+
+import java.io.PrintStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.function.Consumer;
+
+import org.osgi.service.event.Event;
+
+public class EventPrinter implements Consumer<Event>{
+    private PrintStream out;
+    private boolean verbose;
+
+    public EventPrinter(PrintStream out, boolean verbose) {
+        this.out = out;
+        this.verbose = verbose;
+    }
+    
+
+    @Override
+    public void accept(Event event) {
+        out.println(getTimeStamp(event) + " - " + event.getTopic());
+        if (verbose) {
+            for (String key : event.getPropertyNames()) {
+                if (!key.equals("event.topics") && !key.equals("timestamp")) {
+                    out.println(key + ": " + getPrintValue(event, key));
+                }
+            }
+            out.println();
+            out.flush();
+        }
+    }
+
+    private String getTimeStamp(Event event) {
+        Long ts = (Long)event.getProperty("timestamp");
+        if (ts == null) {
+            return "0000-00-00 00:00:00";
+        }
+        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        return df.format(new Date(ts));
+    }
+
+    private Object getPrintValue(Event event, String key) {
+        Object value = event.getProperty(key);
+        if (value.getClass().isArray()) {
+            return Arrays.toString((Object[])value);
+        }
+        return value.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/main/java/org/apache/karaf/event/EventSendCommand.java
----------------------------------------------------------------------
diff --git a/event/src/main/java/org/apache/karaf/event/EventSendCommand.java b/event/src/main/java/org/apache/karaf/event/EventSendCommand.java
new file mode 100644
index 0000000..ba3ab1b
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/event/EventSendCommand.java
@@ -0,0 +1,64 @@
+/*
+ * 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.karaf.event;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+@Command(scope = "event", name = "send", description = "Send a simple event to a topic")
+@Service
+public class EventSendCommand implements Action {
+    @Reference
+    Session session;
+    
+    @Reference
+    EventAdmin eventAdmin;
+
+    @Argument
+    String topic;
+
+    @Argument(multiValued=true)
+    String propertiesSt;
+
+    @Override
+    public Object execute() throws Exception {
+        eventAdmin.sendEvent(new Event(topic, parse(propertiesSt)));
+        return null;
+    }
+
+    Map<String, String> parse(String propSt) {
+        Map<String, String> properties = new HashMap<>();
+        for (String keyValue : propSt.split(",")) {
+            String[] splitted = keyValue.split("=");
+            if (splitted.length != 2) {
+                throw new IllegalArgumentException("Invalid entry " + keyValue);
+            }
+            properties.put(splitted[0], splitted[1]);
+        };
+        return properties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/main/java/org/apache/karaf/event/EventTailCommand.java
----------------------------------------------------------------------
diff --git a/event/src/main/java/org/apache/karaf/event/EventTailCommand.java b/event/src/main/java/org/apache/karaf/event/EventTailCommand.java
new file mode 100644
index 0000000..7be8840
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/event/EventTailCommand.java
@@ -0,0 +1,76 @@
+/*
+ * 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.karaf.event;
+
+import static org.apache.karaf.event.service.TopicPredicate.matchTopic;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import org.apache.karaf.event.service.EventCollector;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.Session;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.event.Event;
+
+@Command(scope = "event", name = "tail", description = "Shows events and listens for incoming events")
+@Service
+public class EventTailCommand implements Action {
+
+    @Reference
+    Session session;
+
+    @Reference
+    BundleContext context;
+
+    @Reference
+    EventCollector collector;
+
+    @Argument
+    String topicFilter = "*";
+
+    @Option(name = "-v")
+    boolean verbose = false;
+
+    @Override
+    public Object execute() throws Exception {
+        EventPrinter printer = new EventPrinter(session.getConsole(), verbose);
+        Consumer<Event> filteredPrinter = executeIf(matchTopic(topicFilter), printer);
+        collector.addConsumer(filteredPrinter);
+        try {
+            waitTillInterrupted();
+        } catch (InterruptedException e) {
+            collector.removeConsumer(filteredPrinter);
+        }
+        return null;
+    }
+    
+    private <T> Consumer<T> executeIf(Predicate<T> pred, Consumer<T> consumer) {
+        return t -> {if (pred.test(t)) consumer.accept(t);};
+    }
+
+    private void waitTillInterrupted() throws InterruptedException {
+        while (true) {
+            Thread.sleep(100);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/main/java/org/apache/karaf/event/service/EventCollector.java
----------------------------------------------------------------------
diff --git a/event/src/main/java/org/apache/karaf/event/service/EventCollector.java b/event/src/main/java/org/apache/karaf/event/service/EventCollector.java
new file mode 100644
index 0000000..0a42d6d
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/event/service/EventCollector.java
@@ -0,0 +1,69 @@
+/*
+ * 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.karaf.event.service;
+
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+
+@Component(
+           service = {EventHandler.class, EventCollector.class},
+           name = "org.apache.karaf.eventadmin.collector",
+           immediate = true,
+           property = "event.topics=*"
+       )
+public class EventCollector implements EventHandler {
+    private Deque<Event> events;
+    private int maxSize;
+    private Set<Consumer<Event>> consumers;
+    
+    public EventCollector() {
+        events = new ConcurrentLinkedDeque<>();
+        maxSize = 100;
+        consumers = new HashSet<>();
+    }
+
+    @Override
+    public synchronized void handleEvent(Event event) {
+        events.addLast(event);
+        if (events.size() > maxSize) {
+            events.removeFirst();
+        }
+        consumers.forEach(c -> c.accept(event));
+    }
+
+    public Stream<Event> getEvents() {
+        return events.stream();
+    }
+    
+    public synchronized void addConsumer(Consumer<Event> eventConsumer) {
+        events.forEach(eventConsumer);
+        consumers.add(eventConsumer);
+    }
+    
+    public synchronized void removeConsumer(Consumer<Event> eventConsumer) {
+        consumers.remove(eventConsumer);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/main/java/org/apache/karaf/event/service/TopicPredicate.java
----------------------------------------------------------------------
diff --git a/event/src/main/java/org/apache/karaf/event/service/TopicPredicate.java b/event/src/main/java/org/apache/karaf/event/service/TopicPredicate.java
new file mode 100644
index 0000000..974f6e6
--- /dev/null
+++ b/event/src/main/java/org/apache/karaf/event/service/TopicPredicate.java
@@ -0,0 +1,40 @@
+/*
+ * 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.karaf.event.service;
+
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+import org.osgi.service.event.Event;
+
+public class TopicPredicate implements Predicate<Event> {
+    private Pattern pattern;
+
+    private TopicPredicate(String topicFilter) {
+        pattern = Pattern.compile(topicFilter.replace("*", ".*"));
+    }
+    
+    @Override
+    public boolean test(Event event) {
+        return pattern.matcher(event.getTopic()).matches();
+    }
+ 
+    public static Predicate<Event> matchTopic(String topicFilter) {
+        return new TopicPredicate(topicFilter);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/test/java/org/apache/karaf/event/EventDisplayCommandTest.java
----------------------------------------------------------------------
diff --git a/event/src/test/java/org/apache/karaf/event/EventDisplayCommandTest.java b/event/src/test/java/org/apache/karaf/event/EventDisplayCommandTest.java
new file mode 100644
index 0000000..0822f73
--- /dev/null
+++ b/event/src/test/java/org/apache/karaf/event/EventDisplayCommandTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.karaf.event;
+
+import static org.easymock.EasyMock.createControl;
+import static org.easymock.EasyMock.expect;
+
+import java.util.HashMap;
+
+import org.apache.karaf.event.service.EventCollector;
+import org.apache.karaf.shell.api.console.Session;
+import org.easymock.IMocksControl;
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+public class EventDisplayCommandTest {
+
+    @Test
+    public void testExecute() throws Exception {
+        IMocksControl c = createControl();
+        EventDisplayCommand display = new EventDisplayCommand();
+        display.session = c.createMock(Session.class);
+        expect(display.session.getConsole()).andReturn(System.out);
+        display.collector = new EventCollector();
+        display.collector.handleEvent(new Event("myTopic", new HashMap<>()));
+        c.replay();
+        display.execute();
+        c.verify();
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/test/java/org/apache/karaf/event/EventPrinterTest.java
----------------------------------------------------------------------
diff --git a/event/src/test/java/org/apache/karaf/event/EventPrinterTest.java b/event/src/test/java/org/apache/karaf/event/EventPrinterTest.java
new file mode 100644
index 0000000..cee5b0f
--- /dev/null
+++ b/event/src/test/java/org/apache/karaf/event/EventPrinterTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.karaf.event;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+public class EventPrinterTest {
+    DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+
+    @Test
+    public void testPrint() throws UnsupportedEncodingException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(baos);
+        new EventPrinter(out, false).accept(event());
+        String result = baos.toString("utf-8");
+        assertThat(result, equalTo("2016-01-01 12:00:00 - myTopic\n"));
+    }
+    
+    @Test
+    public void testPrintVerbose() throws UnsupportedEncodingException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream out = new PrintStream(baos);
+        new EventPrinter(out, true).accept(event());
+        String result = baos.toString("utf-8");
+        assertThat(result, equalTo("2016-01-01 12:00:00 - myTopic\n" 
+            + "a: b\n"
+            + "c: [d, e]\n\n"));
+    }
+
+    private Event event() {
+        HashMap<String, Object> props = new HashMap<>();
+        props.put("a", "b");
+        props.put("c", new String[]{"d", "e"});
+        Date date;
+        try {
+            date = df.parse("2016-01-01 12:00:00");
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
+        props.put("timestamp",  date.getTime());
+        return new Event("myTopic", props);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/test/java/org/apache/karaf/event/EventSendCommandTest.java
----------------------------------------------------------------------
diff --git a/event/src/test/java/org/apache/karaf/event/EventSendCommandTest.java b/event/src/test/java/org/apache/karaf/event/EventSendCommandTest.java
new file mode 100644
index 0000000..fb87c4b
--- /dev/null
+++ b/event/src/test/java/org/apache/karaf/event/EventSendCommandTest.java
@@ -0,0 +1,55 @@
+package org.apache.karaf.event;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.mock;
+import static org.easymock.EasyMock.newCapture;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.easymock.Capture;
+import org.junit.Test;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+
+public class EventSendCommandTest {
+    @Test
+    public void testExecute() throws Exception {
+        EventSendCommand send = new EventSendCommand();
+        send.eventAdmin = mock(EventAdmin.class);
+        Capture<Event> eventCapture = newCapture();
+        send.eventAdmin.sendEvent(capture(eventCapture));
+        expectLastCall();
+
+        replay(send.eventAdmin);
+        send.topic = "myTopic";
+        send.propertiesSt = "a=b";
+        send.execute();
+        verify(send.eventAdmin);
+        
+        Event event = eventCapture.getValue();
+        assertThat(event.getTopic(), equalTo("myTopic"));
+        assertThat(event.getProperty("a"), equalTo("b"));
+    }
+    
+    @Test
+    public void testParse() {
+        String propSt = "a=b,b=c";
+        Map<String, String> expectedMap = new HashMap<>();
+        expectedMap.put("a", "b");
+        expectedMap.put("b", "c");
+        Map<String, String> props = new EventSendCommand().parse(propSt);
+        assertThat(props.entrySet(), equalTo(expectedMap.entrySet())); 
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testParseError() {
+        String propSt = "a=b,c=";
+        new EventSendCommand().parse(propSt);
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/test/java/org/apache/karaf/event/EventTailCommandTest.java
----------------------------------------------------------------------
diff --git a/event/src/test/java/org/apache/karaf/event/EventTailCommandTest.java b/event/src/test/java/org/apache/karaf/event/EventTailCommandTest.java
new file mode 100644
index 0000000..b18f725
--- /dev/null
+++ b/event/src/test/java/org/apache/karaf/event/EventTailCommandTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.karaf.event;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.mock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.karaf.event.service.EventCollector;
+import org.apache.karaf.shell.api.console.Session;
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+public class EventTailCommandTest {
+
+    private Exception exception;
+
+    @Test
+    public void testTail() throws Exception {
+        EventTailCommand tail = new EventTailCommand();
+        tail.session = mock(Session.class);
+        tail.collector = new EventCollector();
+        PrintStream out = System.out;
+        expect(tail.session.getConsole()).andReturn(out);
+        exception = null;
+        replay(tail.session);
+        
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        executor.execute(() -> {
+            try {
+                tail.execute();
+            } catch (Exception e) {
+                exception = e;
+            }
+        });
+        tail.collector.handleEvent(event());
+        Thread.sleep(200);
+        executor.shutdownNow();
+        executor.awaitTermination(100, TimeUnit.SECONDS);
+        if (exception != null) {
+            throw exception;
+        }
+        verify(tail.session);
+    }
+
+    private Event event() {
+        return new Event("myTopic", new HashMap<>());
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/test/java/org/apache/karaf/event/service/EventCollectorTest.java
----------------------------------------------------------------------
diff --git a/event/src/test/java/org/apache/karaf/event/service/EventCollectorTest.java b/event/src/test/java/org/apache/karaf/event/service/EventCollectorTest.java
new file mode 100644
index 0000000..535ee0d
--- /dev/null
+++ b/event/src/test/java/org/apache/karaf/event/service/EventCollectorTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.karaf.event.service;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.IntStream;
+
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+public class EventCollectorTest {
+
+    @Test
+    public void testHandleEvent() throws Exception {
+        EventCollector collector = new EventCollector();
+        assertThat(collector.getEvents().count(), equalTo(0l));
+        collector.handleEvent(event("myTopic"));
+        assertThat(collector.getEvents().count(), equalTo(1l));
+        assertThat(collector.getEvents().findFirst().get().getTopic(), equalTo("myTopic"));
+        
+    }
+    
+    @Test
+    public void testLimit() {
+        EventCollector collector = new EventCollector();
+        collector.handleEvent(event("first"));
+        IntStream.rangeClosed(1, 99).forEach(c -> collector.handleEvent(event("myTopic")));
+        assertThat(collector.getEvents().count(), equalTo(100l));
+        collector.handleEvent(event("last"));
+        assertThat(collector.getEvents().count(), equalTo(100l));
+        assertTrue(collector.getEvents().noneMatch(event -> event.getTopic().endsWith("first")));
+        assertTrue(collector.getEvents().anyMatch(event -> event.getTopic().endsWith("last")));
+    }
+
+    @Test
+    public void testAddRemoveConsumer() {
+        final AtomicInteger count = new AtomicInteger();
+        Consumer<Event> countingConsumer = event -> count.incrementAndGet();
+        EventCollector collector = new EventCollector();
+        collector.handleEvent(event("myTopic"));
+        collector.addConsumer(countingConsumer);
+        assertThat(count.get(), equalTo(1));
+
+        collector.handleEvent(event("another"));
+        assertThat(count.get(), equalTo(2));
+
+        collector.removeConsumer(countingConsumer);
+        collector.handleEvent(event("and/another"));
+        assertThat(count.get(), equalTo(2));
+    }
+
+    private Event event(String topic) {
+        return new Event(topic, new HashMap<>());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/event/src/test/java/org/apache/karaf/event/service/TopicPredicateTest.java
----------------------------------------------------------------------
diff --git a/event/src/test/java/org/apache/karaf/event/service/TopicPredicateTest.java b/event/src/test/java/org/apache/karaf/event/service/TopicPredicateTest.java
new file mode 100644
index 0000000..fca6f59
--- /dev/null
+++ b/event/src/test/java/org/apache/karaf/event/service/TopicPredicateTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.karaf.event.service;
+
+import static org.apache.karaf.event.service.TopicPredicate.matchTopic;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.function.Predicate;
+
+import org.junit.Test;
+import org.osgi.service.event.Event;
+
+public class TopicPredicateTest {
+
+    @Test
+    public void testMatchAll() {
+        Predicate<Event> matcher = matchTopic("*");
+        assertTrue(matcher.test(event("myTopic")));
+        assertTrue(matcher.test(event("my/other")));
+    }
+     
+    @Test
+    public void testMatchSpecific() {
+        Predicate<Event> matcher = matchTopic("myTopic");
+        assertTrue(matcher.test(event("myTopic")));
+        assertFalse(matcher.test(event("myTopic/test")));
+        assertFalse(matcher.test(event("my/other")));
+    }
+    
+    @Test
+    public void testMatchSubTopics() {
+        Predicate<Event> matcher = matchTopic("myTopic*");
+        assertTrue(matcher.test(event("myTopic")));
+        assertTrue(matcher.test(event("myTopic/test")));
+        assertFalse(matcher.test(event("my/other")));
+    }
+
+    private Event event(String topic) {
+        return new Event(topic, new HashMap<String, String>());
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/8aabfbd4/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 31826b9..4bcf505 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,7 @@
         <module>services</module>
         <module>subsystem</module>
         <module>profile</module>
+        <module>event</module>
         <module>tooling</module>
         <module>assemblies</module>
         <module>demos</module>