/* ==================================================================
* BeanConfigurationServiceRegistrationListener.java - Dec 8, 2009 10:25:17 AM
*
* Copyright 2007-2009 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
* $Id$
* ==================================================================
*/
package net.solarnetwork.node.util;
import java.util.Hashtable;
import java.util.Map;
import net.solarnetwork.node.util.BeanConfigurationServiceRegistrationListener.BeanConfigurationRegisteredService;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
/**
* An OSGi service registration listener for {@link BeanConfiguration} objects,
* so they can be used to dynamically configure and publish other OSGi services.
*
* <p>
* This object acts like a dynamic OSGi service factory. You configure the type
* of service this factory should create, and when {@link BeanConfiguration} are
* found new instances of the service will be created and configured from the
* discovered {@link BeanConfiguration}. This allows the bundle the
* configuration came from to be completely isolated and unaware of the
* implementation bundle using that configuration.
* </p>
*
* <p>
* The {@link BeanConfiguration#getConfiguration()} Map will be used to
* configure the properties on instantiated service objects. The keys of this
* Map should be standard Spring JavaBean property names.
* </p>
*
* <p>
* The {@link BeanConfiguration#getAttributes()} Map will be used to create OSGi
* service properties when the service is registered.
* </p>
*
* <p>
* The {@Link BeanConfiguration#getOrdering()} Integer will be used to
* assign an OSGi ranking to the service when it is registered.
* </p>
*
* <p>
* When the {@link BeanConfiguration} is unregistered and the
* {@link #onUnbind(BeanConfiguration, Map)} method is called, the associated
* service created by this factory will be unregistered as well.
* </p>
*
* <p>
* For example, this might be configured via Spring DM like this:
* </p>
*
* <pre>
* <osgi:list id="myConfigurationList"
* interface="net.solarnetwork.node.util.BeanConfiguration" cardinality="0..N">
* <osgi:listener bind-method="onBind" unbind-method="onUnbind" ref="myServiceBean"/>
* </osgi:list>
*
* <bean id="myServiceBean"
* class="net.solarnetwork.node.util.BeanConfigurationServiceRegistrationListener">
* <property name="serviceClass"
* value="net.solarnetwork.node.impl.MyServiceImplementation"/>
* <property name="serviceInterfaces"
* value="net.solarnetwork.node.MyService"/>
* <property name="bundleContext" ref="bundleContext"/>
* </bean>
* </pre>
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl class="class-properties">
* <dt>serviceClass</dt>
* <dd>The type of service to create when
* {@link #onBind(BeanConfiguration, Map)} is called.</dd>
*
* <dt>serviceInterfaces</dt>
* <dd>An array of interface names to register the OSGi service as.</dd>
*
* <dt>serviceProperties</dt>
* <dd>An optional Map of properties to register the OSGi service with.</dd>
* </dl>
*
* @author matt
* @version $Id$
*/
public class BeanConfigurationServiceRegistrationListener extends
BaseServiceListener<BeanConfiguration, BeanConfigurationRegisteredService> {
private Class<?> serviceClass = null;
private String[] serviceInterfaces = null;
private Map<String, Object> serviceProperties = null;
/**
* Callback when an object has been registered.
*
* <p>
* This method will instantiate a new instance of {@link #getServiceClass()}
* and configure its properties via the Map returned by
* {@link BeanConfiguration#getConfiguration()}. Afterwards it will register
* the instance as a service, using the {@link #getServiceInterfaces()} as
* the service interfaces and {@link #getServiceProperties()} as the service
* properties (if available) combined with the
* {@link BeanConfiguration#getAttributes()} (if available).
* </p>
*
* @param config
* the configuration object
* @param properties
* the service properties
*/
public void onBind(BeanConfiguration config, Map<String, ?> properties) {
if ( log.isDebugEnabled() ) {
log.debug("Bind called on [" + config + "] with props " + properties);
}
Object service;
try {
service = serviceClass.newInstance();
} catch ( InstantiationException e ) {
throw new RuntimeException(e);
} catch ( IllegalAccessException e ) {
throw new RuntimeException(e);
}
Hashtable<String, Object> props = new Hashtable<String, Object>();
if ( serviceProperties != null ) {
props.putAll(serviceProperties);
}
if ( config.getAttributes() != null ) {
props.putAll(config.getAttributes());
}
props.put(org.osgi.framework.Constants.SERVICE_RANKING, config.getOrdering());
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(service);
wrapper.setPropertyValues(config.getConfiguration());
addRegisteredService(new BeanConfigurationRegisteredService(config, properties), service,
serviceInterfaces, props);
}
/**
* Callback when a trigger has been un-registered.
*
* <p>
* This method will attempt to un-register a previously registered service.
* </p>
*
* @param config
* the configuration object
* @param properties
* the service properties
*/
public void onUnbind(BeanConfiguration config, Map<String, ?> properties) {
if ( config == null ) {
// Gemini Blueprint calls this when availability="optional" and no services available
return;
}
if ( log.isDebugEnabled() ) {
log.debug("Unbind called on [" + config + "] with props " + properties);
}
removeRegisteredService(config, properties);
}
public static class BeanConfigurationRegisteredService extends RegisteredService<BeanConfiguration> {
public BeanConfigurationRegisteredService(BeanConfiguration config, Map<String, ?> properties) {
super(config, properties);
}
@Override
public boolean isSameAs(BeanConfiguration other, Map<String, ?> properties) {
if ( super.isSameAs(other, properties) ) {
if ( !areMapsSame(getConfig().getConfiguration(), other.getConfiguration()) ) {
return false;
}
if ( !areMapsSame(getConfig().getAttributes(), other.getAttributes()) ) {
return false;
}
return true;
}
return false;
}
}
public Class<?> getServiceClass() {
return serviceClass;
}
public void setServiceClass(Class<?> serviceClass) {
this.serviceClass = serviceClass;
}
public String[] getServiceInterfaces() {
return serviceInterfaces;
}
public void setServiceInterfaces(String[] serviceInterfaces) {
this.serviceInterfaces = serviceInterfaces;
}
public Map<String, Object> getServiceProperties() {
return serviceProperties;
}
public void setServiceProperties(Map<String, Object> serviceProperties) {
this.serviceProperties = serviceProperties;
}
}