Skip to content

Commit

Permalink
Support more Filters in EventDispatcher
Browse files Browse the repository at this point in the history
Goal is to have all filters generated by iPojo runtime fully
supported by iPojo's internal event dispatcher.

That includes filters generated by Handlers, ExtensionDeclarations,
InstanceDeclaration and Factories.
  • Loading branch information
TheItivitist committed Apr 22, 2022
1 parent 7b778e6 commit 3c2a0af
Show file tree
Hide file tree
Showing 8 changed files with 763 additions and 82 deletions.
10 changes: 8 additions & 2 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<packaging>bundle</packaging>
<name>Apache Felix iPOJO</name>
<artifactId>org.apache.felix.ipojo</artifactId>
<version>1.12.1.4-ullink-SNAPSHOT</version>
<version>1.12.1.5-ullink-SNAPSHOT</version>

<properties>
<!--
Expand Down Expand Up @@ -109,7 +109,13 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
Expand Down
210 changes: 171 additions & 39 deletions core/src/main/java/org/apache/felix/ipojo/EventDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
package org.apache.felix.ipojo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.felix.ipojo.extender.ExtensionDeclaration;
import org.apache.felix.ipojo.extender.InstanceDeclaration;
import org.osgi.framework.AllServiceListener;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
Expand All @@ -40,44 +46,152 @@
*/
public final class EventDispatcher implements AllServiceListener
{
private static class ListenerInfo
static class ListenerKey
{
private static final Pattern OBJECT_CLASS_FILTER = Pattern.compile("\\(object[cC]lass=(\\S+)\\)");
private static final Pattern HANDLER_TYPE_FILTER = Pattern.compile("\\(&\\(handler\\.type=primitive\\)\\(factory\\.state=1\\)\\)");
private static final Pattern EXTENSION_DECLARATION_FILTER = Pattern.compile("\\(&\\(object[cC]lass=org\\.apache\\.felix\\.ipojo\\.extender\\.ExtensionDeclaration\\)\\(ipojo\\.extension\\.name=(\\S+)\\)\\)");
private static final Pattern INSTANCE_DECLARATION_FILTER = Pattern.compile("\\(&\\(object[cC]lass=org\\.apache\\.felix\\.ipojo\\.extender\\.InstanceDeclaration\\)\\(\\|\\(ipojo\\.component\\.name=(\\S+)\\)\\(ipojo\\.component\\.name=(\\S+)\\)\\)\\(!\\(ipojo\\.component\\.version=\\*\\)\\)\\)");
private static final String EXTENSION_DECLARATION_CLASSNAME = "org.apache.felix.ipojo.extender.ExtensionDeclaration";
private static final String INSTANCE_DECLARATION_CLASSNAME = "org.apache.felix.ipojo.extender.InstanceDeclaration";
private static final String FACTORY_STATE = "factory.state";

static List<ListenerKey> build(String filter) {
if (filter == null || filter.isEmpty()) {
return null;
}
Matcher objectClassMatcher = OBJECT_CLASS_FILTER.matcher(filter);
if (objectClassMatcher.matches()) {
String objectClass = objectClassMatcher.group(1);
return Collections.singletonList(new ListenerKey(objectClass, null, null, null, null, null));
}
Matcher handlerTypeMatcher = HANDLER_TYPE_FILTER.matcher(filter);
if (handlerTypeMatcher.matches()) {
return Collections.singletonList(new ListenerKey(null, null, null, null, PrimitiveHandler.HANDLER_TYPE, Factory.VALID));
}
Matcher extensionDeclarationMatcher = EXTENSION_DECLARATION_FILTER.matcher(filter);
if (extensionDeclarationMatcher.matches()) {
String extensionName = extensionDeclarationMatcher.group(1);
return Collections.singletonList(new ListenerKey(EXTENSION_DECLARATION_CLASSNAME, extensionName, null, null, null, null));
}
Matcher instanceDeclarationMatcher = INSTANCE_DECLARATION_FILTER.matcher(filter);
if (instanceDeclarationMatcher.matches()) {
String componentName1 = instanceDeclarationMatcher.group(1);
String componentName2 = instanceDeclarationMatcher.group(2);
return Arrays.asList(
new ListenerKey(INSTANCE_DECLARATION_CLASSNAME, null, componentName1, false, null, null),
new ListenerKey(INSTANCE_DECLARATION_CLASSNAME, null, componentName2, false, null, null)
);
}
return null;
}

static List<ListenerKey> build(ServiceReference<?> serviceReference) {
String[] objectClasses = (String[]) serviceReference.getProperty(Constants.OBJECTCLASS);
String handlerType = (String) serviceReference.getProperty(Handler.HANDLER_TYPE_PROPERTY);
List<ListenerKey> results = new ArrayList<ListenerKey>(objectClasses.length + (handlerType != null ? 1 : 0));
if (PrimitiveHandler.HANDLER_TYPE.equals(handlerType)) {
Integer factoryState = (Integer) serviceReference.getProperty(FACTORY_STATE);
if (factoryState != null && factoryState == Factory.VALID) {
results.add(new ListenerKey(null, null, null, null, handlerType, factoryState));
}
}
for (String objectClass : objectClasses) {
if (EXTENSION_DECLARATION_CLASSNAME.equals(objectClass)) {
String extensionName = (String) serviceReference.getProperty(ExtensionDeclaration.EXTENSION_NAME_PROPERTY);
results.add(new ListenerKey(objectClass, extensionName, null, null, null, null));
} else if (INSTANCE_DECLARATION_CLASSNAME.equals(objectClass)) {
String componentName = (String) serviceReference.getProperty(InstanceDeclaration.COMPONENT_NAME_PROPERTY);
String componentVersion = (String) serviceReference.getProperty(InstanceDeclaration.COMPONENT_VERSION_PROPERTY);
results.add(new ListenerKey(objectClass, null, componentName, componentVersion != null && !componentVersion.isEmpty(), null, null));
} else {
results.add(new ListenerKey(objectClass, null, null, null, null, null));
}
}
return results;
}

final String objectClass;
final String extensionName;
final String componentName;
final Boolean componentVersion;
final String handleType;
final Integer factoryState;

ListenerKey(String objectClass, String extensionName, String componentName, Boolean componentVersion, String handleType, Integer factoryState) {
this.objectClass = objectClass;
this.extensionName = extensionName;
this.componentName = componentName;
this.componentVersion = componentVersion;
this.handleType = handleType;
this.factoryState = factoryState;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof ListenerKey) {
ListenerKey listenerKey = (ListenerKey) o;
return objectClass != null ? objectClass.equals(listenerKey.objectClass) : listenerKey.objectClass == null
&& extensionName != null ? extensionName.equals(listenerKey.extensionName) : listenerKey.extensionName == null
&& componentName != null ? componentName.equals(listenerKey.componentName) : listenerKey.componentName == null
&& (componentVersion == null || componentVersion.equals(listenerKey.componentVersion))
&& handleType != null ? handleType.equals(listenerKey.handleType) : listenerKey.handleType == null
&& factoryState != null ? factoryState.equals(listenerKey.factoryState) : listenerKey.factoryState == null;
}
return false;
}

@Override
public int hashCode() {
int result = objectClass != null ? objectClass.hashCode() : 0;
result = 31 * result + (extensionName != null ? extensionName.hashCode() : 0);
result = 31 * result + (componentName != null ? componentName.hashCode() : 0);
result = 31 * result + (componentVersion != null ? componentVersion.hashCode() : 0);
result = 31 * result + (handleType != null ? handleType.hashCode() : 0);
result = 31 * result + (factoryState != null ? factoryState.hashCode() : 0);
return result;
}
}

private static class ListenerInfo {
private final Bundle bundle;
private final ServiceListener listener;

ListenerInfo(Bundle bundle, ServiceListener listener)
{
ListenerInfo(Bundle bundle, ServiceListener listener) {
this.bundle = bundle;
this.listener = listener;
}

Bundle getBundle()
{
Bundle getBundle() {
return bundle;
}

ServiceListener getListener()
{
ServiceListener getListener() {
return listener;
}
}

/**
* The internal event dispatcher.
* This dispatcher is a singleton.
*/
private static EventDispatcher DISPATCHER;


private static final ListenerInfo[] NO_LISTENERINFO = new ListenerInfo[0];

/**
* The list of listeners.
* Service interface -> List of {@link ServiceListener}
*/
private Map<String, List<ListenerInfo>> m_listeners;
private final Map<ListenerKey, List<ListenerInfo>> m_listeners;
/**
* The global bundle context.
* This is the bundle context from iPOJO.
*/
private BundleContext m_context;
private final BundleContext m_context;

/**
* Creates the EventDispatcher.
Expand All @@ -86,7 +200,7 @@ ServiceListener getListener()
*/
private EventDispatcher(BundleContext bc) {
m_context = bc;
m_listeners = new HashMap<String, List<ListenerInfo>>();
m_listeners = new HashMap<ListenerKey, List<ListenerInfo>>();
// Only one thread can call the start method.
m_context.addServiceListener(this);
}
Expand Down Expand Up @@ -145,18 +259,21 @@ private void stop() {
* @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
*/
public void serviceChanged(ServiceEvent event) {
ServiceReference<?> serviceReference = event.getServiceReference();
String[] itfs = (String[]) serviceReference.getProperty(Constants.OBJECTCLASS);
for (String specification : itfs) {
List<ListenerInfo> listenerInfos;
List<ListenerKey> listenerKeys = ListenerKey.build(event.getServiceReference());
for(ListenerKey listenerKey : listenerKeys) {
ListenerInfo[] listenerInfos;
synchronized (this) {
List<ListenerInfo> stored = m_listeners.get(specification);
if (stored == null) {
return; // Nothing to do
}
// Creates a new list (stack confinement)
listenerInfos = new ArrayList<ListenerInfo>(stored);
List<ListenerInfo> list = m_listeners.get(listenerKey);
listenerInfos = list == null || list.isEmpty()
? null
: list.toArray(NO_LISTENERINFO);
}
fireServiceEvents(listenerKey.objectClass, listenerInfos, event);
}
}

private void fireServiceEvents(String specification, ListenerInfo[] listenerInfos, ServiceEvent event) {
if (listenerInfos != null) {
for (ListenerInfo listenerInfo : listenerInfos) {
fireServiceEvent(specification, listenerInfo.getListener(), listenerInfo.getBundle(), event);
}
Expand All @@ -178,27 +295,36 @@ private boolean canFireServiceEvent(String specification, ServiceListener listen
return false;
}
return listener instanceof AllServiceListener
|| specification == null
|| event.getServiceReference().isAssignableTo(bundle, specification);
}

/**
* Adds a new service listener to the {@link EventDispatcher#m_listeners}
* map. This method specifies the listen service interface
* @param itf the service interface
* @param filter the filter
* @param bundle the bundle that is registering the listener
* @param listener the service listener
*/
public void addListener(String itf, Bundle bundle, ServiceListener listener) {
public boolean addListener(String filter, Bundle bundle, ServiceListener listener) {
List<ListenerKey> listenerKeys = ListenerKey.build(filter);
if (listenerKeys == null) {
return false;
}
ListenerInfo listenerInfo = new ListenerInfo(bundle, listener);
synchronized (this) {
List<ListenerInfo> list = m_listeners.get(itf);
if (list == null) {
list = new ArrayList(1);
m_listeners.put(itf, list);
for (ListenerKey listenerKey : listenerKeys) {
List<ListenerInfo> list = m_listeners.get(listenerKey);
if (list == null) {
list = new ArrayList(1);
m_listeners.put(listenerKey, list);
}
list.add(listenerInfo);
}
list.add(new ListenerInfo(bundle, listener));
}
return true;
}

/**
* Removes a service listener.
* @param listener the service listener to remove
Expand All @@ -207,16 +333,22 @@ public void addListener(String itf, Bundle bundle, ServiceListener listener) {
*/
public boolean removeListener(ServiceListener listener) {
synchronized (this) {
boolean removed = false;
for (List<ListenerInfo> listenerInfoList : m_listeners.values()) {
Iterator<ListenerInfo> iterator = listenerInfoList.iterator();
while (iterator.hasNext()) {
if (iterator.next().getListener() == listener) {
iterator.remove();
return true;
}
}
removed = removeListener(listenerInfoList, listener) || removed;
}
return removed;
}
}

private boolean removeListener(List<ListenerInfo> listenerInfoList, ServiceListener listener) {
Iterator<ListenerInfo> iterator = listenerInfoList.iterator();
while (iterator.hasNext()) {
if (iterator.next().getListener() == listener) {
iterator.remove();
return true;
}
return false;
}
return false;
}
}
35 changes: 4 additions & 31 deletions core/src/main/java/org/apache/felix/ipojo/IPojoContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@
import java.io.InputStream;
import java.util.Collection;
import java.util.Dictionary;

import org.apache.felix.ipojo.dependency.interceptors.TransformedServiceReference;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.InvalidSyntaxException;
Expand Down Expand Up @@ -110,9 +108,9 @@ public void addFrameworkListener(FrameworkListener listener) {
* This methods registers the listener on the service context
* if it specified. Otherwise, if the internal dispatcher is enabled,
* it registers the listener inside the internal dispatcher (if
* the filter match against the iPOJO Filter format
* {@link IPojoContext#match(String)}). Finally, if the internal
* dispatcher is disabled, it uses the "regular" bundle context.
* the filter match against the iPOJO Filter format)
* Finally, if the internal dispatcher is disabled, it uses the
* "regular" bundle context.
* @param listener the service listener to add.
* @param filter the LDAP filter
* @throws InvalidSyntaxException if LDAP filter is malformed
Expand All @@ -121,14 +119,7 @@ public void addFrameworkListener(FrameworkListener listener) {
public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
if (m_serviceContext == null) {
EventDispatcher dispatcher = EventDispatcher.getDispatcher();
if (dispatcher != null) { // getDispatcher returns null if not enable.
String itf = match(filter);
if (itf != null) {
dispatcher.addListener(itf, getBundle(), listener);
} else {
m_bundleContext.addServiceListener(listener, filter);
}
} else {
if (dispatcher == null || !dispatcher.addListener(filter, getBundle(), listener)) {
m_bundleContext.addServiceListener(listener, filter);
}
} else {
Expand All @@ -151,24 +142,6 @@ public void addServiceListener(ServiceListener listener) {
}
}

/**
* This method checks if the filter matches with the iPOJO
* filter format: <code>(OBJECTCLASS=$ITF)</code>. It tries
* to extract the required interface (<code>$ITF</code>).
* @param filter the filter to analyze
* @return the required interface or <code>null</code>
* if the filter doesn't match with the iPOJO format.
*/
private String match(String filter) {
if (filter != null && filter.startsWith("(" + Constants.OBJECTCLASS + "=") // check the beginning (OBJECTCLASS
&& filter.lastIndexOf(')') == filter.indexOf(')')) { // check that there is only one )
return filter.substring(("(" + Constants.OBJECTCLASS + "=").length(), filter.length() - 1);
}
return null;
}



/**
* Creates a filter objects.
* This method always uses the bundle context.
Expand Down
Loading

0 comments on commit 3c2a0af

Please sign in to comment.