blob: edbd7a3466dcd766d490674d3648eec43f3af848 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.ipojo.handlers.event.subscriber;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.PrimitiveHandler;
import org.apache.felix.ipojo.architecture.ComponentTypeDescription;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.architecture.PropertyDescription;
import org.apache.felix.ipojo.handlers.event.EventUtil;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.apache.felix.ipojo.util.Callback;
import org.osgi.framework.Filter;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* Event Subscriber Handler.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class EventAdminSubscriberHandler extends PrimitiveHandler implements
EventHandler {
/**
* The handler namespace.
*/
public static final String NAMESPACE = "org.apache.felix.ipojo.handlers.event";
// Names of instance configuration properties.
/**
* The event's topics instance configuration property.
*/
public static final String TOPICS_PROPERTY = "event.topics";
/**
* The event's filter instance configuration property.
*/
public static final String FILTER_PROPERTY = "event.filter";
/**
* The prefix for logged messages.
*/
private static final String LOG_PREFIX = "EVENT ADMIN SUBSCRIBER HANDLER : ";
/**
* The instance manager.
*/
private InstanceManager m_manager;
/**
* The list of subscriber accessible by name.
*/
private final Map m_subscribersByName = new HashMap();
/**
* The list of callbacks accessible by subscribers' names.
*/
private final Map m_callbacksByName = new Hashtable();
/**
* The iPOJO properties representing all the topics.
* Immutable.
*/
private String[] m_topics;
/**
* Listening to received events ?
*/
private boolean m_isListening;
/**
* The handler description
*/
private EventAdminSubscriberHandlerDescription m_description;
/**
* Initializes the component type.
*
* @param cd component type description to populate.
* @param metadata component type metadata.
* @throws ConfigurationException if the metadata are incorrect.
* @see org.apache.felix.ipojo.Handler#initializeComponentFactory(
* org.apache.felix.ipojo.architecture.ComponentTypeDescription, org.apache.felix.ipojo.metadata.Element)
*/
public void initializeComponentFactory(ComponentTypeDescription cd,
Element metadata)
throws ConfigurationException {
// Update the current component description
Dictionary dict = new Properties();
cd.addProperty(new PropertyDescription(TOPICS_PROPERTY,
Dictionary.class.getName(), dict.toString()));
dict = new Properties();
cd.addProperty(new PropertyDescription(FILTER_PROPERTY,
Dictionary.class.getName(), dict.toString()));
// Get Metadata subscribers
Element[] subscribers = metadata.getElements("subscriber", NAMESPACE);
if (subscribers != null) {
// Maps used to check name and field are unique
Set nameSet = new HashSet();
Set callbackSet = new HashSet();
// Check all subscribers are well formed
for (int i = 0; i < subscribers.length; i++) {
// Check the subscriber configuration is correct by creating an
// unused subscriber metadata
EventAdminSubscriberMetadata subscriberMetadata = new EventAdminSubscriberMetadata(
getFactory().getBundleContext(), subscribers[i]);
String name = subscriberMetadata.getName();
info(LOG_PREFIX + "Checking subscriber " + name);
// Determine the event callback prototype
PojoMetadata pojoMetadata = getPojoMetadata();
String callbackType;
if (subscriberMetadata.getDataKey() == null) {
callbackType = Event.class.getName();
} else {
callbackType = subscriberMetadata.getDataType().getName();
}
// Check the event callback method is present
MethodMetadata methodMetadata = pojoMetadata.getMethod(
subscriberMetadata.getCallback(),
new String[] { callbackType });
String callbackSignature = subscriberMetadata.getCallback()
+ "(" + callbackType + ")";
if (methodMetadata == null) {
throw new ConfigurationException(
"Cannot find callback method " + callbackSignature);
}
// Warn if the same callback is used by several subscribers
if (callbackSet.contains(callbackSignature)) {
warn("The callback method is already used by another subscriber : "
+ callbackSignature);
} else {
callbackSet.add(callbackSignature);
}
// Check name is unique
if (nameSet.contains(name)) {
throw new ConfigurationException(
"A subscriber with the same name already exists : "
+ name);
}
nameSet.add(name);
}
m_description = new EventAdminSubscriberHandlerDescription(this,
subscribers);
} else {
info(LOG_PREFIX + "No subscriber to check");
}
}
/**
* Constructor.
*
* @param metadata the omponent type metadata
* @param conf the instance configuration
* @throws ConfigurationException if one event subscription is not correct
* @see org.apache.felix.ipojo.Handler#configure(org.apache.felix.ipojo.metadata.Element, java.util.Dictionary)
*/
public void configure(Element metadata, Dictionary conf)
throws ConfigurationException {
// Store the component manager
m_manager = getInstanceManager();
// Get the topics and filter instance configuration
Dictionary instanceTopics = (Dictionary) conf.get(TOPICS_PROPERTY);
Dictionary instanceFilter = (Dictionary) conf.get(FILTER_PROPERTY);
// Get Metadata subscribers
Element[] subscribers = metadata.getElements("subscriber", NAMESPACE);
// The topics to listen
Set topics = new TreeSet();
if (subscribers != null) {
// Configure all subscribers
for (int i = 0; i < subscribers.length; i++) {
// Extract the subscriber configuration
EventAdminSubscriberMetadata subscriberMetadata = new EventAdminSubscriberMetadata(
m_manager.getContext(), subscribers[i]);
String name = subscriberMetadata.getName();
info(LOG_PREFIX + "Configuring subscriber " + name);
// Get the topics instance configuration if redefined
String topicsString = (instanceTopics != null) ? (String) instanceTopics.get(name) : null;
if (topicsString != null) {
subscriberMetadata.setTopics(topicsString);
}
// Get the filter instance configuration if redefined
String filterString = (instanceFilter != null) ? (String) instanceFilter.get(name) : null;
if (filterString != null) {
subscriberMetadata.setFilter(filterString);
}
// Check the publisher is correctly configured
subscriberMetadata.check();
// Add this subscriber's topics to the global list
String[] subscriberTopics = subscriberMetadata.getTopics();
for (int j = 0; j < subscriberTopics.length; j++) {
topics.add(subscriberTopics[j]);
}
// Determine the event callback prototype
PojoMetadata pojoMetadata = getPojoMetadata();
String callbackType;
if (subscriberMetadata.getDataKey() == null) {
callbackType = Event.class.getName();
} else {
callbackType = subscriberMetadata.getDataType().getName();
}
// Create the specified callback and register it
MethodMetadata methodMetadata = pojoMetadata.getMethod(
subscriberMetadata.getCallback(),
new String[] { callbackType });
Callback callback = new Callback(methodMetadata, m_manager);
m_callbacksByName.put(name, callback);
// Add the subscriber list gloal map
m_subscribersByName.put(name, subscriberMetadata);
}
// Construct the global topic list
m_topics = new String[topics.size()];
int i = 0;
for (Iterator iterator = topics.iterator(); iterator.hasNext();) {
String tmp = (String) iterator.next();
m_topics[i++] = tmp;
}
m_description = new EventAdminSubscriberHandlerDescription(this,
subscribers);
} else {
info(LOG_PREFIX + "No subscriber to configure");
}
}
/**
* Handler start method.
*
* @see org.apache.felix.ipojo.Handler#start()
*/
public synchronized void start() {
m_isListening = true;
}
/**
* Handler stop method.
*
* @see org.apache.felix.ipojo.Handler#stop()
*/
public synchronized void stop() {
m_isListening = false;
}
/**
* @see org.apache.felix.ipojo.Handler#getDescription()
*/
public HandlerDescription getDescription() {
return m_description;
}
/***************************************************************************
* OSGi EventHandler callback
**************************************************************************/
/**
* Receives an event. The event is dispatch to attached subscribers.
*
* @param event the received event.
* @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
*/
public void handleEvent(final Event event) {
EventAdminSubscriberMetadata subscriberMetadata = null;
// Retrieve the event's topic
String topic = event.getTopic();
// For each subscribers
Collection subscribers = m_subscribersByName.values();
for (Iterator i = subscribers.iterator(); i.hasNext();) {
subscriberMetadata = (EventAdminSubscriberMetadata) i.next();
// Stack confinement
boolean isListening = false;
Object callbackParam = null;
Callback callback = null;
synchronized (this) {
isListening = m_isListening;
}
// Check if the subscriber's topic and filter match
Filter filter = subscriberMetadata.getFilter();
if (EventUtil.matches(topic, subscriberMetadata.getTopics())
&& (filter == null || event.matches(filter))) {
String name = subscriberMetadata.getName();
callback = (Callback) m_callbacksByName.get(name);
try {
// Depending on the subscriber type...
callbackParam = getCallbackParameter(event,
subscriberMetadata);
} catch (ClassCastException e) {
// Ignore the data event if type doesn't match
warn(LOG_PREFIX + "Ignoring data event : Bad data type", e);
} catch (NoSuchFieldException e) {
// Ignore events without data field for data events
// subscriber
warn(LOG_PREFIX + "Ignoring data event : No data", e);
}
}
// Run the callback (final check to avoid
// NullPointerExceptions)
if (isListening && callback != null && callbackParam != null) {
try {
callback.call(new Object[] { callbackParam });
} catch (InvocationTargetException e) {
error(LOG_PREFIX
+ "The callback has thrown an exception",
e.getTargetException());
} catch (Exception e) {
error(LOG_PREFIX
+ "Unexpected exception when calling callback",
e);
}
}
}
}
/**
* Computes the callback parameter.
* @param event the event
* @param subscriberMetadata the subscribe metadata
* @param dataKey the data key
* @return the parameter of the callback
* @throws ClassCastException the data class does not match the found value.
* @throws NoSuchFieldException the datakey is not present in the event.
*/
private Object getCallbackParameter(final Event event,
final EventAdminSubscriberMetadata subscriberMetadata
) throws ClassCastException,NoSuchFieldException {
String dataKey = subscriberMetadata.getDataKey();
if (dataKey == null) {
// Generic event subscriber : pass the event to the
// registered callback
return event;
} else {
// Check for a data key in the event
boolean dataKeyPresent = false;
String[] properties = event.getPropertyNames();
for (int j = 0; j < properties.length && !dataKeyPresent; j++) {
if (dataKey.equals(properties[j])) {
dataKeyPresent = true;
}
}
if (dataKeyPresent) {
// Data event : check type compatibility and
// pass the given object to the registered
// callback
Object data = event.getProperty(dataKey);
Class dataType = subscriberMetadata.getDataType();
Class dataClazz = data.getClass();
if (dataType.isAssignableFrom(dataClazz)) {
return data;
} else {
throw new ClassCastException("Cannot convert " + dataClazz.getName() + " to "
+ dataType.getName());
}
} else {
throw new java.lang.NoSuchFieldException(dataKey);
}
}
}
}