Sunday, January 27, 2013

How to add service properties to services created by Managed Service Factory

When using managed-service-factory,

The bean created will be registered as OSGi service having bean-name set to <factory id>.<uuid> which is of no use to tell the service apart.

In this example, the configuration has a name property and we'd like to set the name to the cache instance so that it can be imported like this,

<reference interface="org.sprintframework.cache.Cache" filter="(cacheName=abcde)"/>

Gemini Blueprint 1.0.2 does allow <service-property> in the managed-service-factory, but it appears it only support simple value, not programatically.

To do that, we need to get the ServiceRegistration for the service (which is returned when a service is added to the registry) as that is the only way to update a service's properties.  Luckily, the managed-service-factory bean will actually return a list of ServiceRegistration that it created.

You can simply get the list of ServiceRegistration from the factory bean if you are sure the configuration is always set before you make the call to get the list but in our case, we use something like FileInstall which will add configuration automatically, we'll have to register a service listener and update the new service's property accordingly. 

Another issue is that the serviceChanged is actually called before the new ServiceRegistration is added to the list.  thus, if we get the list of service registrations in the serviceChanged call, we will always miss the newly created one.

The service properties dictionary returned (in JBoss) is a JBoss internal class which is immutable, so i have to create a new dictionary (didn't use the internal class to avoid dependency) and overwrite the internal implementation.  Didn't test if JBoss will convert the hashtable to its internal implementation when I call setProperties but it seems to work fine.

public class ManagedServicePropertyUpdater implements ApplicationContextAware, InitializingBean, ServiceListener {
    private OsgiBundleXmlApplicationContext osgiAppCtx;
    private BundleContext bundleContext;
    private BeanFactory beanFactory;

    @Getter @Setter
    private String serviceInterfaceClassName;
    @Getter @Setter
    private String managedServiceFactoryBeanName;
    @Getter @Setter
    private String servicePropertyKey;
    @Getter @Setter
    private String servicePropertyValueMethodName;

    @Override
    public void afterPropertiesSet() throws Exception {
        assert (serviceInterfaceClassName!=null);
        assert (managedServiceFactoryBeanName!=null);
        assert (servicePropertyKey!=null);
        assert (servicePropertyValueMethodName!=null);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        osgiAppCtx = (OsgiBundleXmlApplicationContext)applicationContext;
        bundleContext = osgiAppCtx.getBundleContext();
        beanFactory = osgiAppCtx.getBeanFactory();
        String filter = "(" + Constants.OBJECTCLASS + "=" + serviceInterfaceClassName + ")";
        try {
            bundleContext.addServiceListener(this,filter);
        } catch( InvalidSyntaxException  e ) {
            throw new BeanCreationException("fail registering service listener for Cache", e);
        }
        Collection serviceRegistrations = (Collection)beanFactory.getBean(managedServiceFactoryBeanName);
        for( Object sr : serviceRegistrations) {
            addServiceProperty((ServiceRegistration) sr);
        }

    }

    @Override
    public void serviceChanged(ServiceEvent event) {
        final ServiceReference reference = event.getServiceReference();
        ExecutorService exec = Executors.newSingleThreadExecutor();
        exec.submit( new Runnable() {
            @Override
            public void run() {
                ServiceRegistration registration = mustFindServiceRegistration(reference);
                addServiceProperty(registration);
            }
        });
        exec.shutdown();
    }

    private ServiceRegistration findServiceRegistration(ServiceReference reference) {
        Collection serviceRegistrations = (Collection)beanFactory.getBean(managedServiceFactoryBeanName);
        for( Object srObj : serviceRegistrations ) {
            ServiceRegistration sr = (ServiceRegistration) srObj;
            ServiceReference ref = sr.getReference();
            if( ref.compareTo(reference) == 0 ){
                return sr;
            }
        }
        return null;
    }

    private ServiceRegistration mustFindServiceRegistration(ServiceReference serviceReference) {
        ServiceRegistration serviceRegistration = null;
        int count = 0;
        do{
            if( count > 0 ) {
                try {
                    Thread.sleep(1000);
                } catch( InterruptedException e ){}
            }
            count ++;
            serviceRegistration = findServiceRegistration(serviceReference);
        } while( serviceRegistration == null && count < 10);
        if( serviceRegistration == null )
            throw new IllegalArgumentException("cannot find service" );
        return serviceRegistration;
    }

    private void addServiceProperty(ServiceRegistration sr) {
        try {
            ServiceReference ref = sr.getReference();
            Object service = bundleContext.getService(ref);
            if( !(service instanceof Cache) ) {
                return;
            }
            Dictionary dict = OsgiServiceReferenceUtils.getServiceProperties(ref);
            if( dict.get(servicePropertyKey) == null ) {
                Dictionary newProps = new Hashtable();
                Enumeration keys = dict.keys();
                while( keys.hasMoreElements() ) {
                    String key = keys.nextElement().toString();
                    newProps.put(key, dict.get(key) );
                }
                Class serviceClass = service.getClass();
                Method getMethod = serviceClass.getMethod(servicePropertyValueMethodName);
                String servicePropertyValue = (String)getMethod.invoke(service);
                newProps.put( servicePropertyKey, servicePropertyValue );
                sr.setProperties(newProps);
            }
            bundleContext.ungetService(ref);
        } catch( Exception e ) {
            e.printStackTrace();
        }
    }
}