This is an automated email from the ASF dual-hosted git repository. timothyjward pushed a commit to branch feature/v1.1 in repository https://gitbox.apache.org/repos/asf/aries-typedevent.git
commit 42dbada0d79660fec2edabcc7fdf3229bb2b0414 Author: Tim Ward <[email protected]> AuthorDate: Tue Sep 5 16:37:34 2023 +0100 Initial implementation for Typed Events 1.1 This commit adds in the prototype OSGi API for Typed Events 1.1, which must be removed before release. It also implements the new API and fixes up dependencies so that the build keeps running. Signed-off-by: Tim Ward <[email protected]> --- org.apache.aries.typedevent.bus/pom.xml | 35 +- .../typedevent/bus/impl/TypedEventBusImpl.java | 68 +++ .../osgi/service/typedevent/TopicPermission.java | 557 +++++++++++++++++++++ .../org/osgi/service/typedevent/TypedEventBus.java | 114 +++++ .../service/typedevent/TypedEventConstants.java | 94 ++++ .../osgi/service/typedevent/TypedEventHandler.java | 63 +++ .../service/typedevent/TypedEventPublisher.java | 94 ++++ .../service/typedevent/UnhandledEventHandler.java | 45 ++ .../service/typedevent/UntypedEventHandler.java | 59 +++ .../typedevent/annotations/RequireTypedEvent.java | 52 ++ .../typedevent/annotations/package-info.java | 39 ++ .../service/typedevent/monitor/MonitorEvent.java | 45 ++ .../typedevent/monitor/TypedEventMonitor.java | 64 +++ .../service/typedevent/monitor/package-info.java | 45 ++ .../org/osgi/service/typedevent/package-info.java | 46 ++ .../typedevent/propertytypes/EventFilter.java | 57 +++ .../typedevent/propertytypes/EventTopics.java | 56 +++ .../typedevent/propertytypes/EventType.java | 55 ++ .../typedevent/propertytypes/package-info.java | 43 ++ .../typedevent/bus/impl/TypedEventBusImplTest.java | 105 +++- .../bus/osgi/EventDeliveryIntegrationTest.java | 17 +- .../typedevent/bus/osgi/FilterIntegrationTest.java | 17 +- .../bus/osgi/TypedEventMonitorIntegrationTest.java | 13 +- .../osgi/UnhandledEventHandlerIntegrationTest.java | 17 +- org.apache.aries.typedevent.bus/test.bndrun | 8 +- .../org.apache.aries.typedevent.remote.api/pom.xml | 10 +- .../pom.xml | 9 +- .../osgi/RemoteEventBusIntegrationTest.java | 2 +- .../test.bndrun | 9 +- .../org.apache.aries.typedevent.remote.spi/pom.xml | 3 +- pom.xml | 3 +- 31 files changed, 1763 insertions(+), 81 deletions(-) diff --git a/org.apache.aries.typedevent.bus/pom.xml b/org.apache.aries.typedevent.bus/pom.xml index a9fc494..2ee877a 100644 --- a/org.apache.aries.typedevent.bus/pom.xml +++ b/org.apache.aries.typedevent.bus/pom.xml @@ -37,10 +37,11 @@ <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> </dependency> + <!-- Temporary removal while using the locally built interfaces <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.service.typedevent</artifactId> - </dependency> + </dependency> --> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.annotation</artifactId> @@ -89,6 +90,38 @@ <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> + + <!-- Temporary additions to compile the OSGi interfaces --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.annotation.bundle</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.component.annotations</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.namespace.implementation</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.pushstream</artifactId> + <version>1.0.0</version> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.function</artifactId> + <version>1.1.0</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.promise</artifactId> + <version>1.1.0</version> + <scope>runtime</scope> + </dependency> </dependencies> <build> diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java index e055443..0c4337b 100644 --- a/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java +++ b/org.apache.aries.typedevent.bus/src/main/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImpl.java @@ -50,6 +50,7 @@ import org.osgi.framework.InvalidSyntaxException; import org.osgi.service.typedevent.TypedEventBus; import org.osgi.service.typedevent.TypedEventConstants; import org.osgi.service.typedevent.TypedEventHandler; +import org.osgi.service.typedevent.TypedEventPublisher; import org.osgi.service.typedevent.UnhandledEventHandler; import org.osgi.service.typedevent.UntypedEventHandler; import org.osgi.util.converter.TypeReference; @@ -568,4 +569,71 @@ public class TypedEventBusImpl implements TypedEventBus { } } + + @Override + public <T> TypedEventPublisher<T> createPublisher(Class<T> eventType) { + Objects.requireNonNull(eventType, "The event type must not be null"); + String topicName = eventType.getName().replace('.', '/'); + checkTopicSyntax(topicName); + return new TypedEventPublisherImpl<>(topicName); + } + + @Override + public <T> TypedEventPublisher<T> createPublisher(String topic, Class<T> eventType) { + Objects.requireNonNull(topic, "The event topic must not be null"); + Objects.requireNonNull(eventType, "The event type must not be null"); + checkTopicSyntax(topic); + return new TypedEventPublisherImpl<>(topic); + } + + @Override + public TypedEventPublisher<Object> createPublisher(String topic) { + Objects.requireNonNull(topic, "The event topic must not be null"); + checkTopicSyntax(topic); + return new TypedEventPublisherImpl<>(topic); + } + + private class TypedEventPublisherImpl<T> implements TypedEventPublisher<T> { + + private final String topic; + + private final AtomicBoolean open = new AtomicBoolean(true); + + public TypedEventPublisherImpl(String topic) { + this.topic = topic; + } + + private void checkOpen() { + if(!open.get()) { + throw new IllegalStateException("The TypedEventPublisher for topic " + topic + " is closed."); + } + } + + @Override + public void deliver(T event) { + checkOpen(); + TypedEventBusImpl.this.deliver(topic, EventConverter.forTypedEvent(event)); + } + + @Override + public void deliverUntyped(Map<String, ?> event) { + checkOpen(); + TypedEventBusImpl.this.deliver(topic, EventConverter.forUntypedEvent(event)); + } + + @Override + public String getTopic() { + return topic; + } + + @Override + public void close() { + open.set(false); + } + + @Override + public boolean isOpen() { + return open.get(); + } + } } diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TopicPermission.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TopicPermission.java new file mode 100644 index 0000000..8bf1b44 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TopicPermission.java @@ -0,0 +1,557 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.security.Permission; +import java.security.PermissionCollection; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +/** + * A bundle's authority to publish or subscribe to typed events on a topic. + * <p> + * A topic is a slash-separated string that defines a topic. + * <p> + * For example: + * + * <pre> + * org / osgi / service / foo / FooEvent / ACTION + * </pre> + * <p> + * Topics may also be given a default name based on the event type that is + * published to the topic. These use the fully qualified class name of the event + * object as the name of the topic. + * <p> + * For example: + * + * <pre> + * com.acme.foo.event.EventData + * </pre> + * <p> + * {@code TopicPermission} has two actions: {@code publish} and + * {@code subscribe}. + * + * @ThreadSafe + * @author $Id: 35b621041b08d51844397f56fc8c51a682bf7b7b $ + */ +public final class TopicPermission extends Permission { + static final long serialVersionUID = -5855563886961618300L; + /** + * The action string {@code publish}. + */ + public final static String PUBLISH = "publish"; + /** + * The action string {@code subscribe}. + */ + public final static String SUBSCRIBE = "subscribe"; + private final static int ACTION_PUBLISH = 0x00000001; + private final static int ACTION_SUBSCRIBE = 0x00000002; + private final static int ACTION_ALL = ACTION_PUBLISH | ACTION_SUBSCRIBE; + private final static int ACTION_NONE = 0; + /** + * The actions mask. + */ + private transient int action_mask; + + /** + * prefix if the name is wildcarded. + */ + private transient volatile String prefix; + + /** + * The actions in canonical form. + * + * @serial + */ + private volatile String actions = null; + + /** + * Defines the authority to publish and/or subscribe to a topic within the + * Typed Event service specification. + * <p> + * The name is specified as a slash-separated string. Wildcards may be used. + * For example: + * + * <pre> + * org/osgi/service/fooFooEvent/ACTION + * com/isv/* + * * + * </pre> + * <p> + * A bundle that needs to publish events on a topic must have the + * appropriate {@code TopicPermission} for that topic; similarly, a bundle + * that needs to subscribe to events on a topic must have the appropriate + * {@code TopicPermssion} for that topic. + * <p> + * + * @param name Topic name. + * @param actions {@code publish},{@code subscribe} (canonical order). + */ + public TopicPermission(String name, String actions) { + this(name, parseActions(actions)); + } + + /** + * Package private constructor used by TopicPermissionCollection. + * + * @param name class name + * @param mask action mask + */ + TopicPermission(String name, int mask) { + super(name); + setTransients(mask); + } + + /** + * Called by constructors and when deserialized. + * + * @param name topic name + * @param mask action mask + */ + private synchronized void setTransients(final int mask) { + final String name = getName(); + if ((name == null) || name.length() == 0) { + throw new IllegalArgumentException("invalid name"); + } + + if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) { + throw new IllegalArgumentException("invalid action string"); + } + action_mask = mask; + + if (name.equals("*")) { + prefix = ""; + } else { + if (name.endsWith("/*")) { + prefix = name.substring(0, name.length() - 1); + } else { + prefix = null; + } + } + } + + /** + * Returns the current action mask. + * <p> + * Used by the TopicPermissionCollection class. + * + * @return Current action mask. + */ + synchronized int getActionsMask() { + return action_mask; + } + + /** + * Parse action string into action mask. + * + * @param actions Action string. + * @return action mask. + */ + private static int parseActions(final String actions) { + boolean seencomma = false; + int mask = ACTION_NONE; + if (actions == null) { + return mask; + } + char[] a = actions.toCharArray(); + int i = a.length - 1; + if (i < 0) + return mask; + while (i != -1) { + char c; + // skip whitespace + while ((i != -1) && ((c = a[i]) == ' ' || c == '\r' || c == '\n' || c == '\f' || c == '\t')) + i--; + // check for the known strings + int matchlen; + if (i >= 8 && (a[i - 8] == 's' || a[i - 8] == 'S') + && (a[i - 7] == 'u' || a[i - 7] == 'U') + && (a[i - 6] == 'b' || a[i - 6] == 'B') + && (a[i - 5] == 's' || a[i - 5] == 'S') + && (a[i - 4] == 'c' || a[i - 4] == 'C') + && (a[i - 3] == 'r' || a[i - 3] == 'R') + && (a[i - 2] == 'i' || a[i - 2] == 'I') + && (a[i - 1] == 'b' || a[i - 1] == 'B') + && (a[i] == 'e' || a[i] == 'E')) { + matchlen = 9; + mask |= ACTION_SUBSCRIBE; + } + else + if (i >= 6 && (a[i - 6] == 'p' || a[i - 6] == 'P') + && (a[i - 5] == 'u' || a[i - 5] == 'U') + && (a[i - 4] == 'b' || a[i - 4] == 'B') + && (a[i - 3] == 'l' || a[i - 3] == 'L') + && (a[i - 2] == 'i' || a[i - 2] == 'I') + && (a[i - 1] == 's' || a[i - 1] == 'S') + && (a[i] == 'h' || a[i] == 'H')) { + matchlen = 7; + mask |= ACTION_PUBLISH; + } + else { + // parse error + throw new IllegalArgumentException("invalid permission: " + + actions); + } + // make sure we didn't just match the tail of a word + // like "ackbarfpublish". Also, skip to the comma. + seencomma = false; + while (i >= matchlen && !seencomma) { + switch (a[i - matchlen]) { + case ',' : + seencomma = true; + /* FALLTHROUGH */ + case ' ' : + case '\r' : + case '\n' : + case '\f' : + case '\t' : + break; + default : + throw new IllegalArgumentException("invalid permission: " + actions); + } + i--; + } + // point i at the location of the comma minus one (or -1). + i -= matchlen; + } + if (seencomma) { + throw new IllegalArgumentException("invalid permission: " + actions); + } + return mask; + } + + /** + * Determines if the specified permission is implied by this object. + * + * <p> + * This method checks that the topic name of the target is implied by the + * topic name of this object. The list of {@code TopicPermission} actions + * must either match or allow for the list of the target object to imply the + * target {@code TopicPermission} action. + * + * <pre> + * x/y/*,"publish" -> x/y/z,"publish" is true + * *,"subscribe" -> x/y,"subscribe" is true + * *,"publish" -> x/y,"subscribe" is false + * x/y,"publish" -> x/y/z,"publish" is false + * </pre> + * + * @param p The target permission to interrogate. + * @return {@code true} if the specified {@code TopicPermission} action is + * implied by this object; {@code false} otherwise. + */ + @Override + public boolean implies(Permission p) { + if (p instanceof TopicPermission) { + TopicPermission requested = (TopicPermission) p; + int requestedMask = requested.getActionsMask(); + if ((getActionsMask() & requestedMask) == requestedMask) { + String requestedName = requested.getName(); + String pre = prefix; + if (pre != null) { + return requestedName.startsWith(pre); + } + + return requestedName.equals(getName()); + } + } + return false; + } + + /** + * Returns the canonical string representation of the + * {@code TopicPermission} actions. + * + * <p> + * Always returns present {@code TopicPermission} actions in the following + * order: {@code publish},{@code subscribe}. + * + * @return Canonical string representation of the {@code TopicPermission} + * actions. + */ + @Override + public String getActions() { + String result = actions; + if (result == null) { + StringBuilder sb = new StringBuilder(); + boolean comma = false; + int mask = getActionsMask(); + if ((mask & ACTION_PUBLISH) == ACTION_PUBLISH) { + sb.append(PUBLISH); + comma = true; + } + if ((mask & ACTION_SUBSCRIBE) == ACTION_SUBSCRIBE) { + if (comma) + sb.append(','); + sb.append(SUBSCRIBE); + } + actions = result = sb.toString(); + } + return result; + } + + /** + * Returns a new {@code PermissionCollection} object suitable for storing + * {@code TopicPermission} objects. + * + * @return A new {@code PermissionCollection} object. + */ + @Override + public PermissionCollection newPermissionCollection() { + return new TopicPermissionCollection(); + } + + /** + * Determines the equality of two {@code TopicPermission} objects. + * + * This method checks that specified {@code TopicPermission} has the same + * topic name and actions as this {@code TopicPermission} object. + * + * @param obj The object to test for equality with this + * {@code TopicPermission} object. + * @return {@code true} if {@code obj} is a {@code TopicPermission}, and has + * the same topic name and actions as this {@code TopicPermission} + * object; {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TopicPermission)) { + return false; + } + TopicPermission tp = (TopicPermission) obj; + return (getActionsMask() == tp.getActionsMask()) && getName().equals(tp.getName()); + } + + /** + * Returns the hash code value for this object. + * + * @return A hash code value for this object. + */ + @Override + public int hashCode() { + int h = 31 * 17 + getName().hashCode(); + h = 31 * h + getActions().hashCode(); + return h; + } + + /** + * WriteObject is called to save the state of this permission object to a + * stream. The actions are serialized, and the superclass takes care of the + * name. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException { + // Write out the actions. The superclass takes care of the name + // call getActions to make sure actions field is initialized + if (actions == null) + getActions(); + s.defaultWriteObject(); + } + + /** + * readObject is called to restore the state of this permission from a + * stream. + */ + private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { + // Read in the action, then initialize the rest + s.defaultReadObject(); + setTransients(parseActions(actions)); + } +} + +/** + * Stores a set of {@code TopicPermission} permissions. + * + * @see java.security.Permission + * @see java.security.Permissions + * @see java.security.PermissionCollection + */ +final class TopicPermissionCollection extends PermissionCollection { + static final long serialVersionUID = -614647783533924048L; + /** + * Table of permissions. + * + * @GuardedBy this + */ + private transient Map<String, TopicPermission> permissions; + /** + * Boolean saying if "*" is in the collection. + * + * @serial + * @GuardedBy this + */ + private boolean all_allowed; + + /** + * Create an empty TopicPermissions object. + * + */ + public TopicPermissionCollection() { + permissions = new HashMap<String, TopicPermission>(); + all_allowed = false; + } + + /** + * Adds a permission to the {@code TopicPermission} objects. The key for the + * hash is the name. + * + * @param permission The {@code TopicPermission} object to add. + * + * @throws IllegalArgumentException If the permission is not a + * {@code TopicPermission} instance. + * + * @throws SecurityException If this {@code TopicPermissionCollection} + * object has been marked read-only. + */ + @Override + public void add(final Permission permission) { + if (!(permission instanceof TopicPermission)) { + throw new IllegalArgumentException("invalid permission: " + permission); + } + if (isReadOnly()) { + throw new SecurityException("attempt to add a Permission to a " + "readonly PermissionCollection"); + } + final TopicPermission tp = (TopicPermission) permission; + final String name = tp.getName(); + final int newMask = tp.getActionsMask(); + + synchronized (this) { + final TopicPermission existing = permissions.get(name); + if (existing != null) { + final int oldMask = existing.getActionsMask(); + if (oldMask != newMask) { + permissions.put(name, new TopicPermission(name, oldMask | newMask)); + } + } else { + permissions.put(name, tp); + } + if (!all_allowed) { + if (name.equals("*")) + all_allowed = true; + } + } + } + + /** + * Determines if the specified permissions implies the permissions expressed + * in {@code permission}. + * + * @param permission The Permission object to compare with this + * {@code TopicPermission} object. + * + * @return {@code true} if {@code permission} is a proper subset of a + * permission in the set; {@code false} otherwise. + */ + @Override + public boolean implies(final Permission permission) { + if (!(permission instanceof TopicPermission)) { + return false; + } + final TopicPermission requested = (TopicPermission) permission; + String name = requested.getName(); + final int desired = requested.getActionsMask(); + int effective = 0; + + TopicPermission x; + // short circuit if the "*" Permission was added + synchronized (this) { + if (all_allowed) { + x = permissions.get("*"); + if (x != null) { + effective |= x.getActionsMask(); + if ((effective & desired) == desired) { + return true; + } + } + } + x = permissions.get(name); + } + // strategy: + // Check for full match first. Then work our way up the + // name looking for matches on a/b/* + if (x != null) { + // we have a direct hit! + effective |= x.getActionsMask(); + if ((effective & desired) == desired) { + return true; + } + } + // work our way up the tree... + int last; + int offset = name.length() - 1; + while ((last = name.lastIndexOf('/', offset)) != -1) { + name = name.substring(0, last + 1) + "*"; + synchronized (this) { + x = permissions.get(name); + } + if (x != null) { + effective |= x.getActionsMask(); + if ((effective & desired) == desired) { + return true; + } + } + offset = last - 1; + } + // we don't have to check for "*" as it was already checked + // at the top (all_allowed), so we just return false + return false; + } + + /** + * Returns an enumeration of all {@code TopicPermission} objects in the + * container. + * + * @return Enumeration of all {@code TopicPermission} objects. + */ + @Override + public synchronized Enumeration<Permission> elements() { + List<Permission> all = new ArrayList<Permission>(permissions.values()); + return Collections.enumeration(all); + } + + /* serialization logic */ + private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("permissions", Hashtable.class), new ObjectStreamField("all_allowed", Boolean.TYPE)}; + + private synchronized void writeObject(ObjectOutputStream out) throws IOException { + Hashtable<String, TopicPermission> hashtable = new Hashtable<String, TopicPermission>(permissions); + ObjectOutputStream.PutField pfields = out.putFields(); + pfields.put("permissions", hashtable); + pfields.put("all_allowed", all_allowed); + out.writeFields(); + } + + private synchronized void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { + ObjectInputStream.GetField gfields = in.readFields(); + @SuppressWarnings("unchecked") + Hashtable<String, TopicPermission> hashtable = (Hashtable<String, TopicPermission>) gfields.get("permissions", null); + permissions = new HashMap<String, TopicPermission>(hashtable); + all_allowed = gfields.get("all_allowed", false); + } +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventBus.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventBus.java new file mode 100644 index 0000000..a73d508 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventBus.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import java.util.Map; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * The Typed Event service. Bundles wishing to publish events must obtain + * this service and call one of the event delivery methods. + * + * @ThreadSafe + * @author $Id: cace0c5ed2b2e0d6abdf96f4a30e24b9c1610eed $ + */ +@ProviderType +public interface TypedEventBus { + /** + * Initiate asynchronous, ordered delivery of an event. This method returns + * to the caller before delivery of the event is completed. Events are + * delivered in the order that they are received by this method. + * <p> + * The topic for this event will be automatically set to the fully qualified + * type name for the supplied event object. + * <p> + * Logically equivalent to calling + * {@code deliver(event.getClass().getName().replace('.', '/'), event)} + * + * @param event The event to send to all listeners which subscribe to the + * topic of the event. + * @throws NullPointerException if the event object is null + */ + void deliver(Object event); + + /** + * Initiate asynchronous, ordered delivery of an event. This method returns + * to the caller before delivery of the event is completed. Events are + * delivered in the order that they are received by this method. + * + * @param topic The topic to which this event should be sent. + * @param event The event to send to all listeners which subscribe to the + * topic. + * @throws NullPointerException if the event object is null + * @throws IllegalArgumentException if the topic name is not valid + */ + void deliver(String topic, Object event); + + /** + * Initiate asynchronous, ordered delivery of event data. This method + * returns to the caller before delivery of the event is completed. Events + * are delivered in the order that they are received by this method. + * + * @param topic The topic to which this event should be sent. + * @param event A Map representation of the event data to send to all + * listeners which subscribe to the topic. + * @throws NullPointerException if the event map is null + * @throws IllegalArgumentException if the topic name is not valid + */ + void deliverUntyped(String topic, Map<String, ? > event); + + /** + * Creates a {@link TypedEventPublisher} for the topic automatically + * generated from the passed in event type. + * + * @param <T> The type of events to be sent by the + * {@link TypedEventPublisher} + * @param eventType The type of events to be sent by the + * {@link TypedEventPublisher} + * @return A {@link TypedEventPublisher} that will publish events to the + * topic name automatically generated from {@code eventType} + */ + <T> TypedEventPublisher<T> createPublisher(Class<T> eventType); + + /** + * Creates a {@link TypedEventPublisher} for the supplied topic and event + * type + * + * @param <T> The type of events to be sent by the + * {@link TypedEventPublisher} + * @param topic The topic to publish events to + * @param eventType The type of events to be sent by the + * {@link TypedEventPublisher} + * @return A {@link TypedEventPublisher} that will publish events to the + * supplied topic + */ + <T> TypedEventPublisher<T> createPublisher(String topic, + Class<T> eventType); + + /** + * Creates a {@link TypedEventPublisher} for the supplied topic + * + * @param topic The topic to publish events to + * @return A {@link TypedEventPublisher} that will publish events to the + * supplied topic + */ + TypedEventPublisher<Object> createPublisher(String topic); + +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventConstants.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventConstants.java new file mode 100644 index 0000000..fcf7fe6 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventConstants.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * Defines standard names for Typed Event properties. + * + * @author $Id: b3c3bb7f79af344b6e1f60f13373feaec5d54bd4 $ + */ +@ProviderType +public final class TypedEventConstants { + + /** + * The name of the service property used to indicate the type of the event + * objects received by a {@link TypedEventHandler} service. + * <p> + * If this service property is not present then the reified type parameter + * from the TypedEventHandler implementation class will be used. + */ + public static final String TYPED_EVENT_TYPE = "event.type"; + + /** + * The name of the service property used to indicate the topic(s) to which + * an a {@link TypedEventHandler} or {@link UntypedEventHandler} service is + * listening. + * <p> + * If this service property is not present then the reified type parameter + * from the TypedEventHandler implementation class will be used to determine + * the topic. + */ + public static final String TYPED_EVENT_TOPICS = "event.topics"; + + /** + * The name of the service property used to indicate a filter that should be + * applied to events from the {@link #TYPED_EVENT_TOPICS}. Only events which + * match the filter will be delivered to the Event Handler service. + * <p> + * If this service property is not present then all events from the topic(s) + * will be delivered to the Event Handler service. + */ + public static final String TYPED_EVENT_FILTER = "event.filter"; + + /** + * The name of the service property used to indicate that an event handler + * would like to receive one or more historical events matching its + * {@link #TYPED_EVENT_TOPICS} and {@link #TYPED_EVENT_FILTER} before + * receiving any new data. The value of this property is an {@link Integer} + * greater than or equal to zero. + * <p> + * If this property is set then when the event hander is discovered by the + * whiteboard the event handler will deliver, in order, up to the requested + * number of historical events. This will occur before the delivery of any + * new events. If no history is available then zero events will be + * delivered. If insufficient history is available then fewer events than + * requested will be delivered. + */ + public static final String TYPED_EVENT_HISTORY = "event.history"; + + /** + * The name of the implementation capability for the Typed Event + * specification + */ + public static final String TYPED_EVENT_IMPLEMENTATION = "osgi.typedevent"; + + /** + * The version of the implementation capability for the Typed Event + * specification + */ + public static final String TYPED_EVENT_SPECIFICATION_VERSION = "1.1"; + + /** + * Private constructor to prevent instantiation + */ + private TypedEventConstants() { + } +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventHandler.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventHandler.java new file mode 100644 index 0000000..63b5e6e --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventHandler.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * Listener for Typed Events. + * <p> + * {@code TypedEventHandler} objects are registered with the Framework service + * registry and are notified with an event object when an event is sent. + * <p> + * {@code TypedEventHandler} objects are expected to reify the type parameter + * {@code T} with the type of object they wish to receive when implementing this + * interface. This type can be overridden using the + * {@link TypedEventConstants#TYPED_EVENT_TOPICS} service property. + * <p> + * {@code TypedEventHandler} objects may be registered with a service property + * {@link TypedEventConstants#TYPED_EVENT_TOPICS} whose value is the list of + * topics in which the event handler is interested. + * <p> + * For example: + * + * <pre> + * String[] topics = new String[] { + * "com/isv/*" + * }; + * Hashtable ht = new Hashtable(); + * ht.put(EventConstants.TYPE_SAFE_EVENT_TOPICS, topics); + * context.registerService(TypedEventHandler.class, this, ht); + * </pre> + * + * @ThreadSafe + * @author $Id: 1ef1b856280d23975dcb3d2d2c5046eb82cdb6fc $ + * @param <T> The type of the event to be received + */ +@ConsumerType +public interface TypedEventHandler<T> { + /** + * Called by the {@link TypedEventBus} service to notify the listener of an + * event. + * + * @param topic The topic to which the event was sent + * @param event The event that occurred. + */ + void notify(String topic, T event); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventPublisher.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventPublisher.java new file mode 100644 index 0000000..f7c7b90 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/TypedEventPublisher.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import java.util.Map; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * A Typed Event publisher for a single topic. Bundles wishing to publish events + * may obtain this object from the {@link TypedEventBus}. + * <p> + * All events published by this publisher are sent to the same topic, as + * returned by {@link #getTopic()}. + * <p> + * This object should not be shared as it will not re-validate the caller's + * permission to send to the target topic. + * + * @ThreadSafe + * @author $Id: cace0c5ed2b2e0d6abdf96f4a30e24b9c1610eed $ + * @param <T> The type of events that may be sent using this publisher + */ +@ProviderType +public interface TypedEventPublisher<T> extends AutoCloseable { + /** + * Initiate asynchronous, ordered delivery of an event. This method returns + * to the caller before delivery of the event is completed. Events are + * delivered in the order that they are received by this method. + * + * @param event The event to send to all listeners which subscribe to the + * topic of the event. + * @throws NullPointerException if the event object is null + * @throws IllegalStateException if the {@link TypedEventPublisher} has been + * closed + */ + void deliver(T event); + + /** + * Initiate asynchronous, ordered delivery of event data. This method + * returns to the caller before delivery of the event is completed. Events + * are delivered in the order that they are received by this method. + * + * @param event A Map representation of the event data to send to all + * listeners which subscribe to the topic. + * @throws NullPointerException if the event map is null + * @throws IllegalStateException if the {@link TypedEventPublisher} has been + * closed + */ + void deliverUntyped(Map<String, ? > event); + + /** + * Get the topic for this {@link TypedEventPublisher}. This topic is set + * when the {@link TypedEventPublisher} is created and cannot be changed. + * + * @return The topic for this {@link TypedEventPublisher} + */ + String getTopic(); + + /** + * Closes this {@link TypedEventPublisher} so that no further events can be + * sent. After closure all delivery methods throw + * {@link IllegalStateException}. + * <p> + * Calling {@link #close()} on a closed {@link TypedEventPublisher} has no + * effect. + */ + @Override + void close(); + + /** + * This method allows the caller to check whether the + * {@link TypedEventPublisher} has been closed. + * + * @return <code>false</code> if the {@link TypedEventPublisher} has been + * closed, <code>true</code> otherwise. + */ + boolean isOpen(); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/UnhandledEventHandler.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/UnhandledEventHandler.java new file mode 100644 index 0000000..ddba2ca --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/UnhandledEventHandler.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import java.util.Map; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * Listener for Unhandled Events. + * <p> + * {@code UnhandledEventHandler} objects are registered with the Framework + * service registry and are notified with an event object when an event is sent, + * but no other handler is found to receive the event + * + * @ThreadSafe + * @author $Id: c6f3ac2430c0caf881b78ffbfdb983331727a9f4 $ + */ +@ConsumerType +public interface UnhandledEventHandler { + /** + * Called by the {@link TypedEventBus} service to notify the listener of + * an unhandled event. + * + * @param topic The topic to which the event was sent + * @param event The event that occurred. + */ + void notifyUnhandled(String topic, Map<String,Object> event); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/UntypedEventHandler.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/UntypedEventHandler.java new file mode 100644 index 0000000..fbdfac5 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/UntypedEventHandler.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent; + +import java.util.Map; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * Listener for Untyped Events. + * <p> + * {@code UntypedEventHandler} objects are registered with the Framework service + * registry and are notified with an event object when an event is sent. + * <p> + * {@code UntypedEventHandler} objects must be registered with a service + * property {@link TypedEventConstants#TYPED_EVENT_TOPICS} whose value is the + * list of topics in which the event handler is interested. + * <p> + * For example: + * + * <pre> + * String[] topics = new String[] { + * "com/isv/*" + * }; + * Hashtable ht = new Hashtable(); + * ht.put(EventConstants.TYPE_SAFE_EVENT_TOPICS, topics); + * context.registerService(UntypedEventHandler.class, this, ht); + * </pre> + * + * @ThreadSafe + * @author $Id: 24d2143a364d9f4d7d1b7e17d743d3c1a10b63a9 $ + */ +@ConsumerType +public interface UntypedEventHandler { + /** + * Called by the {@link TypedEventBus} service to notify the listener of an + * event. + * + * @param topic The topic to which the event was sent + * @param event The event that occurred. + */ + void notifyUntyped(String topic, Map<String,Object> event); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/annotations/RequireTypedEvent.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/annotations/RequireTypedEvent.java new file mode 100644 index 0000000..e90e16f --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/annotations/RequireTypedEvent.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.osgi.annotation.bundle.Requirement; +import org.osgi.namespace.implementation.ImplementationNamespace; +import org.osgi.service.typedevent.TypedEventConstants; + +/** + * This annotation can be used to require the Typed Event implementation. It can + * be used directly, or as a meta-annotation. + * <p> + * This annotation is applied to several of the Typed Event component property + * type annotations meaning that it does not normally need to be applied to + * Declarative Services components which use the Typed Event specification. + * + * @author $Id: 5919b947393ff3b07a0e8cd1dc881dcf4637e6b4 $ + * @since 1.0 + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ + ElementType.TYPE, ElementType.PACKAGE +}) +@Requirement(namespace = ImplementationNamespace.IMPLEMENTATION_NAMESPACE, // + name = TypedEventConstants.TYPED_EVENT_IMPLEMENTATION, // + version = TypedEventConstants.TYPED_EVENT_SPECIFICATION_VERSION) +public @interface RequireTypedEvent { + // This is a marker annotation. +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/annotations/package-info.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/annotations/package-info.java new file mode 100644 index 0000000..fa94dbb --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/annotations/package-info.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +/** + * Typed Event Annotations Package Version 1.0. + * <p> + * This package contains annotations that can be used to require the Typed Event + * implementation. + * <p> + * Bundles should not normally need to import this package as the annotations + * are only used at build-time. + * + * @author $Id: 627e9ee98bd7b640ebad077fccfbd6994c9af41e $ + */ + +@Export +@Version(TYPED_EVENT_SPECIFICATION_VERSION) +package org.osgi.service.typedevent.annotations; + +import static org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_SPECIFICATION_VERSION; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; + diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/MonitorEvent.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/MonitorEvent.java new file mode 100644 index 0000000..71bea5d --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/MonitorEvent.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ +package org.osgi.service.typedevent.monitor; + +import java.time.Instant; +import java.util.Map; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * A monitoring event. + */ +@ProviderType +public class MonitorEvent { + + /** + * The Event Topic + */ + public String topic; + + /** + * The Data from the Event in Map form + */ + public Map<String, Object> eventData; + + /** + * The time at which the event was published + */ + public Instant publicationTime; +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/TypedEventMonitor.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/TypedEventMonitor.java new file mode 100644 index 0000000..a366b24 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/TypedEventMonitor.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ +package org.osgi.service.typedevent.monitor; + +import java.time.Instant; + +import org.osgi.annotation.versioning.ProviderType; +import org.osgi.util.pushstream.PushStream; + +/** + * The EventMonitor service can be used to monitor the events that are sent + * using the EventBus, and that are received from remote EventBus instances + * + * @ThreadSafe + * @author $Id: 7f7a493225439de463d0ad550f4b6b206b54277c $ + */ +@ProviderType +public interface TypedEventMonitor { + + /** + * Get a stream of events, starting now. + * + * @return A stream of event data + */ + PushStream<MonitorEvent> monitorEvents(); + + /** + * Get a stream of events, including up to the requested number of + * historical data events. + * + * @param history The requested number of historical events, note that fewer + * than this number of events may be returned if history is + * unavailable, or if insufficient events have been sent. + * @return A stream of event data + */ + PushStream<MonitorEvent> monitorEvents(int history); + + /** + * Get a stream of events, including historical data events prior to the + * supplied time + * + * @param history The requested time after which historical events, should + * be included. Note that events may have been discarded, or + * history unavailable. + * @return A stream of event data + */ + PushStream<MonitorEvent> monitorEvents(Instant history); + +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/package-info.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/package-info.java new file mode 100644 index 0000000..0a46d1e --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/monitor/package-info.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +/** + * Typed Event Monitoring Package Version 1.0. + * <p> + * Bundles wishing to use this package must list the package in the + * Import-Package header of the bundle's manifest. This package has two types of + * users: the consumers that use the API in this package and the providers that + * implement the API in this package. + * <p> + * Example import for consumers using the API in this package: + * <p> + * {@code Import-Package: org.osgi.service.typedevent.monitor; version="[1.0,2.0)"} + * <p> + * Example import for providers implementing the API in this package: + * <p> + * {@code Import-Package: org.osgi.service.typedevent.monitor; version="[1.0,1.1)"} + * + * @author $Id: 9e2a95c6b50c545e6b5148469c6e6c577df042b3 $ + */ + +@Export +@Version(TYPED_EVENT_SPECIFICATION_VERSION) +package org.osgi.service.typedevent.monitor; + +import static org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_SPECIFICATION_VERSION; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/package-info.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/package-info.java new file mode 100644 index 0000000..b720bfa --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/package-info.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +/** + * Typed Event Package Version 1.0. + * <p> + * Bundles wishing to use this package must list the package in the + * Import-Package header of the bundle's manifest. This package has two types of + * users: the consumers that use the API in this package and the providers that + * implement the API in this package. + * <p> + * Example import for consumers using the API in this package: + * <p> + * {@code Import-Package: org.osgi.service.typedevent; version="[1.0,2.0)"} + * <p> + * Example import for providers implementing the API in this package: + * <p> + * {@code Import-Package: org.osgi.service.typedevent; version="[1.0,1.1)"} + * + * @author $Id: 4931ddeaf6f300dbb0ef21877b73d676365e5ae5 $ + */ + +@Export +@Version(TYPED_EVENT_SPECIFICATION_VERSION) +package org.osgi.service.typedevent; + +import static org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_SPECIFICATION_VERSION; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; + diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventFilter.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventFilter.java new file mode 100644 index 0000000..534e117 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventFilter.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent.propertytypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.osgi.service.component.annotations.ComponentPropertyType; +import org.osgi.service.typedevent.TypedEventConstants; +import org.osgi.service.typedevent.TypedEventHandler; +import org.osgi.service.typedevent.UntypedEventHandler; +import org.osgi.service.typedevent.annotations.RequireTypedEvent; + +/** + * Component Property Type for the + * {@link TypedEventConstants#TYPED_EVENT_FILTER} service property of an Event + * Handler service. + * <p> + * This annotation can be used on an {@link TypedEventHandler} or + * {@link UntypedEventHandler} component to declare the value of the + * {@link TypedEventConstants#TYPED_EVENT_FILTER} service property. + * + * @see "Component Property Types" + * @author $Id: 3c99b991160eff4d897ce5040a15a591597d5dce $ + */ +@ComponentPropertyType +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +@RequireTypedEvent +public @interface EventFilter { + /** + * Service property specifying the event filter for a + * {@link TypedEventHandler} or {@link UntypedEventHandler} service. + * + * @return The event filter. + * @see TypedEventConstants#TYPED_EVENT_FILTER + */ + String value(); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventTopics.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventTopics.java new file mode 100644 index 0000000..857e1d0 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventTopics.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent.propertytypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.osgi.service.component.annotations.ComponentPropertyType; +import org.osgi.service.typedevent.TypedEventConstants; +import org.osgi.service.typedevent.TypedEventHandler; +import org.osgi.service.typedevent.UntypedEventHandler; +import org.osgi.service.typedevent.annotations.RequireTypedEvent; + +/** + * Component Property Type for the {@link TypedEventConstants#TYPED_EVENT_TOPICS} + * service property of a {@link TypedEventHandler} or + * {@link UntypedEventHandler} service. + * <p> + * This annotation can be used on a component to declare the values of the + * {@link TypedEventConstants#TYPED_EVENT_TOPICS} service property. + * + * @see "Component Property Types" + * @author $Id: a82d95f6131235a5f94b7a778ea8e19ef968df41 $ + */ +@ComponentPropertyType +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +@RequireTypedEvent +public @interface EventTopics { + /** + * Service property specifying the {@code Event} topics of interest to an + * {@link TypedEventHandler} or {@link UntypedEventHandler} service. + * + * @return The event topics. + * @see TypedEventConstants#TYPED_EVENT_TOPICS + */ + String[] value(); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventType.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventType.java new file mode 100644 index 0000000..5be6da4 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/EventType.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +package org.osgi.service.typedevent.propertytypes; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.osgi.service.component.annotations.ComponentPropertyType; +import org.osgi.service.typedevent.TypedEventConstants; +import org.osgi.service.typedevent.TypedEventHandler; +import org.osgi.service.typedevent.annotations.RequireTypedEvent; + +/** + * Component Property Type for the {@link TypedEventConstants#TYPED_EVENT_TYPE} + * service property of an {@link TypedEventHandler} service. + * <p> + * This annotation can be used on an {@link TypedEventHandler} component to + * declare the value of the {@link TypedEventConstants#TYPED_EVENT_TYPE} service + * property. + * + * @see "Component Property Types" + * @author $Id: 3799448fcf60ec8a125bf857b2b7da23d9f45570 $ + */ +@ComponentPropertyType +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +@RequireTypedEvent +public @interface EventType { + /** + * Service property specifying the {@code EventType} for a + * {@link TypedEventHandler} service. + * + * @return The event filter. + * @see TypedEventConstants#TYPED_EVENT_TYPE + */ + Class<?> value(); +} diff --git a/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/package-info.java b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/package-info.java new file mode 100644 index 0000000..e937468 --- /dev/null +++ b/org.apache.aries.typedevent.bus/src/main/java/org/osgi/service/typedevent/propertytypes/package-info.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) Contributors to the Eclipse Foundation + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +/** + * Typed Event Component Property Types Package Version 1.0. + * <p> + * When used as annotations, component property types are processed by tools to + * generate Component Descriptions which are used at runtime. + * <p> + * Bundles wishing to use this package at runtime must list the package in the + * Import-Package header of the bundle's manifest. + * <p> + * Example import for consumers using the API in this package: + * <p> + * {@code Import-Package: org.osgi.service.typedevent.propertytypes; version="[1.0,2.0)"} + * + * @author $Id: 75ea41c54d3a9602dfaf4e2d31e0019cef207c0f $ + */ + +@Export +@Version(TYPED_EVENT_SPECIFICATION_VERSION) +package org.osgi.service.typedevent.propertytypes; + +import static org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_SPECIFICATION_VERSION; + +import org.osgi.annotation.bundle.Export; +import org.osgi.annotation.versioning.Version; + diff --git a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImplTest.java b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImplTest.java index 017e1be..749a149 100644 --- a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImplTest.java +++ b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/impl/TypedEventBusImplTest.java @@ -18,9 +18,11 @@ package org.apache.aries.typedevent.bus.impl; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mock.Strictness.LENIENT; import static org.osgi.framework.Constants.SERVICE_ID; import static org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_FILTER; import static org.osgi.service.typedevent.TypedEventConstants.TYPED_EVENT_TOPICS; @@ -35,16 +37,19 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentMatcher; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.osgi.service.typedevent.TypedEventHandler; +import org.osgi.service.typedevent.TypedEventPublisher; import org.osgi.service.typedevent.UnhandledEventHandler; import org.osgi.service.typedevent.UntypedEventHandler; +@ExtendWith(MockitoExtension.class) public class TypedEventBusImplTest { private static final String SPECIAL_TEST_EVENT_TOPIC = SpecialTestEvent.class.getName().replace(".", "/"); @@ -65,16 +70,16 @@ public class TypedEventBusImplTest { } - @Mock(lenient = true) + @Mock(strictness = LENIENT) Bundle registeringBundle; - @Mock(lenient = true) + @Mock(strictness = LENIENT) TypedEventHandler<Object> handlerA, handlerB; - @Mock(lenient = true) + @Mock(strictness = LENIENT) UntypedEventHandler untypedHandlerA, untypedHandlerB; - @Mock(lenient = true) + @Mock(strictness = LENIENT) UnhandledEventHandler unhandledHandler; Semaphore semA = new Semaphore(0), semB = new Semaphore(0), untypedSemA = new Semaphore(0), @@ -83,13 +88,9 @@ public class TypedEventBusImplTest { TypedEventBusImpl impl; TypedEventMonitorImpl monitorImpl; - private AutoCloseable mocks; - @BeforeEach public void start() throws ClassNotFoundException { - mocks = MockitoAnnotations.openMocks(this); - Mockito.doAnswer(i -> TestEvent.class.getClassLoader().loadClass(i.getArgument(0, String.class))) .when(registeringBundle).loadClass(Mockito.anyString()); @@ -128,7 +129,6 @@ public class TypedEventBusImplTest { public void stop() throws Exception { impl.stop(); monitorImpl.destroy(); - mocks.close(); } /** @@ -550,6 +550,91 @@ public class TypedEventBusImplTest { assertFalse(semA.tryAcquire(1, TimeUnit.SECONDS)); } + + /** + * Tests that events are delivered correctly using a publisher + * + * @throws InterruptedException + */ + @Test + public void testEventPublisher() throws InterruptedException { + + TestEvent event = new TestEvent(); + event.message = "boo"; + + Map<String, Object> serviceProperties = new HashMap<>(); + + serviceProperties.put(TYPED_EVENT_TOPICS, TEST_EVENT_TOPIC); + serviceProperties.put(TYPED_EVENT_TYPE, TestEvent.class.getName()); + serviceProperties.put(SERVICE_ID, 42L); + + impl.addTypedEventHandler(registeringBundle, handlerA, serviceProperties); + + serviceProperties = new HashMap<>(); + + serviceProperties.put(TYPED_EVENT_TOPICS, TestEvent2.class.getName().replace(".", "/")); + serviceProperties.put(TYPED_EVENT_TYPE, TestEvent2.class.getName()); + serviceProperties.put(SERVICE_ID, 43L); + + impl.addTypedEventHandler(registeringBundle, handlerB, serviceProperties); + + serviceProperties = new HashMap<>(); + + serviceProperties.put(TYPED_EVENT_TOPICS, TEST_EVENT_TOPIC); + serviceProperties.put(SERVICE_ID, 44L); + + impl.addUntypedEventHandler(untypedHandlerA, serviceProperties); + + serviceProperties = new HashMap<>(); + + serviceProperties.put(TYPED_EVENT_TOPICS, TestEvent2.class.getName().replace(".", "/")); + serviceProperties.put(SERVICE_ID, 45L); + + impl.addUntypedEventHandler(untypedHandlerB, serviceProperties); + + TypedEventPublisher<TestEvent> publisher = impl.createPublisher(TestEvent.class); + + publisher.deliver(event); + + assertTrue(semA.tryAcquire(1, TimeUnit.SECONDS)); + + Mockito.verify(handlerA).notify(Mockito.eq(TestEvent.class.getName().replace('.', '/')), + Mockito.argThat(isTestEventWithMessage("boo"))); + + assertFalse(semB.tryAcquire(1, TimeUnit.SECONDS)); + + assertTrue(untypedSemA.tryAcquire(1, TimeUnit.SECONDS)); + + Mockito.verify(untypedHandlerA).notifyUntyped(Mockito.eq(TestEvent.class.getName().replace('.', '/')), + Mockito.argThat(isUntypedTestEventWithMessage("boo"))); + + assertFalse(untypedSemB.tryAcquire(1, TimeUnit.SECONDS)); + + + Map<String, Object> eventMap = Map.of("message", "far"); + publisher.deliverUntyped(eventMap); + + assertTrue(semA.tryAcquire(1, TimeUnit.SECONDS)); + + Mockito.verify(handlerA).notify(Mockito.eq(TestEvent.class.getName().replace('.', '/')), + Mockito.argThat(isTestEventWithMessage("far"))); + + assertFalse(semB.tryAcquire(1, TimeUnit.SECONDS)); + + assertTrue(untypedSemA.tryAcquire(1, TimeUnit.SECONDS)); + + Mockito.verify(untypedHandlerA).notifyUntyped(Mockito.eq(TestEvent.class.getName().replace('.', '/')), + Mockito.argThat(isUntypedTestEventWithMessage("far"))); + + assertFalse(untypedSemB.tryAcquire(1, TimeUnit.SECONDS)); + + assertTrue(publisher.isOpen()); + publisher.close(); + assertFalse(publisher.isOpen()); + + assertThrows(IllegalStateException.class, () -> publisher.deliver(event)); + assertThrows(IllegalStateException.class, () -> publisher.deliverUntyped(eventMap)); + } ArgumentMatcher<TestEvent> isTestEventWithMessage(String message) { return new ArgumentMatcher<TestEvent>() { diff --git a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/EventDeliveryIntegrationTest.java b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/EventDeliveryIntegrationTest.java index 40543fa..d435b7a 100644 --- a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/EventDeliveryIntegrationTest.java +++ b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/EventDeliveryIntegrationTest.java @@ -29,14 +29,12 @@ import org.apache.aries.typedevent.bus.common.TestEvent2; import org.apache.aries.typedevent.bus.common.TestEvent2.EventType; import org.apache.aries.typedevent.bus.common.TestEvent2Consumer; import org.apache.aries.typedevent.bus.common.TestEventConsumer; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.osgi.framework.BundleContext; import org.osgi.service.typedevent.TypedEventBus; import org.osgi.service.typedevent.TypedEventConstants; @@ -56,6 +54,7 @@ import org.osgi.test.junit5.service.ServiceExtension; */ @ExtendWith(BundleContextExtension.class) @ExtendWith(ServiceExtension.class) +@ExtendWith(MockitoExtension.class) public class EventDeliveryIntegrationTest extends AbstractIntegrationTest { @InjectBundleContext @@ -72,18 +71,6 @@ public class EventDeliveryIntegrationTest extends AbstractIntegrationTest { @Mock UntypedEventHandler untypedEventHandler, untypedEventHandler2; - - private AutoCloseable mocks; - - @BeforeEach - public void setupMocks() { - mocks = MockitoAnnotations.openMocks(this); - } - - @AfterEach - public void stop() throws Exception { - mocks.close(); - } /** * Tests that events are delivered to untyped Event Handlers diff --git a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/FilterIntegrationTest.java b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/FilterIntegrationTest.java index 2d17847..67e5066 100644 --- a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/FilterIntegrationTest.java +++ b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/FilterIntegrationTest.java @@ -23,13 +23,11 @@ import java.util.Hashtable; import org.apache.aries.typedevent.bus.common.TestEvent; import org.apache.aries.typedevent.bus.common.TestEventConsumer; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.osgi.framework.BundleContext; import org.osgi.service.typedevent.TypedEventBus; import org.osgi.service.typedevent.TypedEventHandler; @@ -47,6 +45,7 @@ import org.osgi.test.junit5.service.ServiceExtension; */ @ExtendWith(BundleContextExtension.class) @ExtendWith(ServiceExtension.class) +@ExtendWith(MockitoExtension.class) public class FilterIntegrationTest extends AbstractIntegrationTest { @InjectBundleContext @@ -58,18 +57,6 @@ public class FilterIntegrationTest extends AbstractIntegrationTest { @Mock TestEventConsumer typedEventHandler, typedEventHandlerB; - private AutoCloseable mocks; - - @BeforeEach - public void setupMocks() { - mocks = MockitoAnnotations.openMocks(this); - } - - @AfterEach - public void stop() throws Exception { - mocks.close(); - } - @Test public void testFilteredListener() throws Exception { Dictionary<String, Object> props = new Hashtable<>(); diff --git a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/TypedEventMonitorIntegrationTest.java b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/TypedEventMonitorIntegrationTest.java index 9c3729d..ee630df 100644 --- a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/TypedEventMonitorIntegrationTest.java +++ b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/TypedEventMonitorIntegrationTest.java @@ -32,12 +32,11 @@ import org.apache.aries.typedevent.bus.common.TestEvent; import org.apache.aries.typedevent.bus.common.TestEventConsumer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.service.typedevent.TypedEventBus; @@ -53,13 +52,12 @@ import org.osgi.util.promise.Promise; @ExtendWith(BundleContextExtension.class) @ExtendWith(ServiceExtension.class) +@ExtendWith(MockitoExtension.class) public class TypedEventMonitorIntegrationTest extends AbstractIntegrationTest { @Mock TestEventConsumer typedEventHandler; - private AutoCloseable mocks; - private static Bundle eventBusBundle; @BeforeAll @@ -76,15 +74,8 @@ public class TypedEventMonitorIntegrationTest extends AbstractIntegrationTest { .findAny().orElse(null); } - @BeforeEach - public void setupMocks() { - mocks = MockitoAnnotations.openMocks(this); - } - @AfterEach public void stop() throws Exception { - mocks.close(); - // Needed to clear history from previous tests eventBusBundle.stop(); eventBusBundle.start(); diff --git a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/UnhandledEventHandlerIntegrationTest.java b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/UnhandledEventHandlerIntegrationTest.java index 084a92a..5a8675e 100644 --- a/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/UnhandledEventHandlerIntegrationTest.java +++ b/org.apache.aries.typedevent.bus/src/test/java/org/apache/aries/typedevent/bus/osgi/UnhandledEventHandlerIntegrationTest.java @@ -31,12 +31,10 @@ import java.util.Hashtable; import org.apache.aries.typedevent.bus.common.TestEvent; import org.apache.aries.typedevent.bus.common.TestEventConsumer; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; import org.osgi.framework.BundleContext; import org.osgi.service.typedevent.TypedEventBus; import org.osgi.service.typedevent.TypedEventHandler; @@ -56,6 +54,7 @@ import org.osgi.test.junit5.service.ServiceExtension; */ @ExtendWith(BundleContextExtension.class) @ExtendWith(ServiceExtension.class) +@ExtendWith(MockitoExtension.class) public class UnhandledEventHandlerIntegrationTest extends AbstractIntegrationTest { @InjectBundleContext @@ -73,18 +72,6 @@ public class UnhandledEventHandlerIntegrationTest extends AbstractIntegrationTes @Mock UnhandledEventHandler unhandledEventHandler; - private AutoCloseable mocks; - - @BeforeEach - public void setupMocks() { - mocks = MockitoAnnotations.openMocks(this); - } - - @AfterEach - public void stop() throws Exception { - mocks.close(); - } - /** * Tests that the unhandledEventHandler gets called appropriately * @throws InterruptedException diff --git a/org.apache.aries.typedevent.bus/test.bndrun b/org.apache.aries.typedevent.bus/test.bndrun index fe7f98b..b8cbb94 100644 --- a/org.apache.aries.typedevent.bus/test.bndrun +++ b/org.apache.aries.typedevent.bus/test.bndrun @@ -31,11 +31,8 @@ -resolve.effective: active -runbundles: \ - org.osgi.service.typedevent;version='[1.0.0,1.0.1)',\ org.osgi.util.converter;version='[1.0.9,1.0.10)',\ org.osgi.util.function;version='[1.1.0,1.1.1)',\ - org.osgi.util.promise;version='[1.1.1,1.1.2)',\ - org.osgi.util.pushstream;version='[1.0.1,1.0.2)',\ junit-jupiter-api;version='[5.10.0,5.10.1)',\ junit-platform-commons;version='[1.10.0,1.10.1)',\ junit-jupiter-engine;version='[5.10.0,5.10.1)',\ @@ -55,4 +52,7 @@ ch.qos.logback.core;version='[1.2.3,1.2.4)',\ org.apache.aries.component-dsl.component-dsl;version='[1.2.2,1.2.3)',\ org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ - slf4j.api;version='[1.7.30,1.7.31)' \ No newline at end of file + slf4j.api;version='[1.7.30,1.7.31)',\ + org.mockito.junit-jupiter;version='[5.5.0,5.5.1)',\ + org.osgi.util.promise;version='[1.1.0,1.1.1)',\ + org.osgi.util.pushstream;version='[1.0.0,1.0.1)' \ No newline at end of file diff --git a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.api/pom.xml b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.api/pom.xml index 7c43d9f..2d3fba9 100644 --- a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.api/pom.xml +++ b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.api/pom.xml @@ -30,10 +30,11 @@ <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> </dependency> + <!-- Temporary removal while using the locally built interfaces <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.service.typedevent</artifactId> - </dependency> + </dependency> --> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.annotation</artifactId> @@ -46,6 +47,13 @@ <groupId>org.osgi</groupId> <artifactId>org.osgi.annotation.bundle</artifactId> </dependency> + + <!-- Temporary additions to compile with the new OSGi interfaces --> + <dependency> + <groupId>org.apache.aries.typedevent</groupId> + <artifactId>org.apache.aries.typedevent.bus</artifactId> + <version>1.1.0-SNAPSHOT</version> + </dependency> </dependencies> <build> diff --git a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/pom.xml b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/pom.xml index 46fcd8b..887fe55 100644 --- a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/pom.xml +++ b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/pom.xml @@ -37,10 +37,11 @@ <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> </dependency> + <!-- Temporary removal while using the locally built interfaces <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.service.typedevent</artifactId> - </dependency> + </dependency> --> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.annotation</artifactId> @@ -65,6 +66,12 @@ <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.util.converter</artifactId> + <exclusions> + <exclusion> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.function</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> diff --git a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/src/test/java/org/apache/aries/typedevent/remote/remoteservices/osgi/RemoteEventBusIntegrationTest.java b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/src/test/java/org/apache/aries/typedevent/remote/remoteservices/osgi/RemoteEventBusIntegrationTest.java index 5b12943..cbb461b 100644 --- a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/src/test/java/org/apache/aries/typedevent/remote/remoteservices/osgi/RemoteEventBusIntegrationTest.java +++ b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/src/test/java/org/apache/aries/typedevent/remote/remoteservices/osgi/RemoteEventBusIntegrationTest.java @@ -324,7 +324,7 @@ public class RemoteEventBusIntegrationTest extends AbstractIntegrationTest { BundleContext remoteContext = frameworks.values().stream() .filter(fw -> !getUUID(fw).equals(getMasterFrameworkUUID())) .flatMap(fw -> Arrays.stream(fw.getBundleContext().getBundles())) - .filter(b -> b.getSymbolicName().equals("org.osgi.service.typedevent")) + .filter(b -> b.getSymbolicName().equals("org.apache.aries.typedevent.bus")) .map(Bundle::getBundleContext) .findFirst() .get(); diff --git a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/test.bndrun b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/test.bndrun index ecc0976..2cf83af 100644 --- a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/test.bndrun +++ b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.remoteservices/test.bndrun @@ -32,14 +32,10 @@ ch.qos.logback.classic;version='[1.2.3,1.2.4)',\ ch.qos.logback.core;version='[1.2.3,1.2.4)',\ org.apache.aries.component-dsl.component-dsl;version='[1.2.2,1.2.3)',\ - org.osgi.service.typedevent;version='[1.0.0,1.0.1)',\ org.osgi.util.converter;version='[1.0.9,1.0.10)',\ org.osgi.util.function;version='[1.1.0,1.1.1)',\ - org.osgi.util.promise;version='[1.1.1,1.1.2)',\ - org.osgi.util.pushstream;version='[1.0.1,1.0.2)',\ slf4j.api;version='[1.7.30,1.7.31)',\ org.apache.aries.typedevent.bus;version='[1.1.0,1.1.1)',\ - org.apache.aries.typedevent.remote.api;version='[1.1.0,1.1.1)',\ org.apache.aries.typedevent.remote.remoteservices;version='[1.1.0,1.1.1)',\ org.apache.aries.typedevent.remote.remoteservices-tests;version='[1.1.0,1.1.1)',\ org.apache.aries.typedevent.remote.spi;version='[1.1.0,1.1.1)',\ @@ -56,4 +52,7 @@ org.objenesis;version='[3.3.0,3.3.1)',\ org.opentest4j;version='[1.3.0,1.3.1)',\ org.osgi.test.common;version='[1.2.1,1.2.2)',\ - org.osgi.test.junit5;version='[1.2.1,1.2.2)' + org.osgi.test.junit5;version='[1.2.1,1.2.2)',\ + org.apache.aries.typedevent.remote.api;version='[0.0.2,0.0.3)',\ + org.osgi.util.promise;version='[1.1.0,1.1.1)',\ + org.osgi.util.pushstream;version='[1.0.0,1.0.1)' diff --git a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.spi/pom.xml b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.spi/pom.xml index f9ad1e8..680caa8 100644 --- a/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.spi/pom.xml +++ b/org.apache.aries.typedevent.remote/org.apache.aries.typedevent.remote.spi/pom.xml @@ -30,10 +30,11 @@ <groupId>org.osgi</groupId> <artifactId>org.osgi.core</artifactId> </dependency> + <!-- Temporary removal while using the locally built interfaces <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.service.typedevent</artifactId> - </dependency> + </dependency> --> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.annotation</artifactId> diff --git a/pom.xml b/pom.xml index 00f1f66..6884091 100644 --- a/pom.xml +++ b/pom.xml @@ -97,12 +97,13 @@ <version>1.5.0</version> <scope>compile</scope> </dependency> + <!-- Temporary removal while using the locally built interfaces <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.service.typedevent</artifactId> <version>1.0.0-RC1</version> <scope>compile</scope> - </dependency> + </dependency> --> <dependency> <groupId>org.osgi</groupId> <artifactId>org.osgi.service.component.annotations</artifactId>
