/******************************************************************************* * Copyright (c) 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Jan S. Rellermeyer, IBM Research - initial API and implementation *******************************************************************************/ package org.eclipse.concierge; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; import org.osgi.framework.FrameworkEvent; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceException; import org.osgi.framework.ServiceFactory; import org.osgi.framework.ServiceReference; import org.osgi.framework.ServiceRegistration; /** * @author Jan S. Rellermeyer */ final class ServiceReferenceImpl<S> implements ServiceReference<S> { /** * the framework */ protected final Concierge framework; /** * the bundle object. */ Bundle bundle; /** * the service object. */ protected S service; /** * the service properties. */ final Map<String, Object> properties; /** * the bundles that are using the service. */ final Map<Bundle, Integer> useCounters = new HashMap<Bundle, Integer>(0); /** * cached service objects if the registered service is a service factory. */ private HashMap<Bundle, S> cachedServices = null; /** * the registration. */ ServiceRegistration<S> registration; /** * the next service id. */ private static long nextServiceID = 0; private final boolean isServiceFactory; /** * these service properties must not be overwritten by property updates. */ protected final static HashSet<String> forbidden; static { forbidden = new HashSet<String>(2); forbidden.add(Constants.SERVICE_ID.toLowerCase()); forbidden.add(Constants.OBJECTCLASS.toLowerCase()); } /** * create a new service reference implementation instance. * * @param bundle * the bundle. * @param service * the service object. * @param props * the service properties. * @param clazzes * the interface classes that the service is registered under. * @throws ClassNotFoundException */ ServiceReferenceImpl(final Concierge framework, final Bundle bundle, final S service, final Dictionary<String, ?> props, final String[] clazzes) { if (service instanceof ServiceFactory) { isServiceFactory = true; } else { isServiceFactory = false; checkService(service, clazzes); } this.framework = framework; this.bundle = bundle; this.service = service; this.properties = new HashMap<String, Object>(props == null ? 2 : props.size() + 2); if (props != null) { for (final Enumeration<String> keys = props.keys(); keys .hasMoreElements();) { final String key = keys.nextElement(); properties.put(key, props.get(key)); } } properties.put(Constants.OBJECTCLASS, clazzes); properties.put(Constants.SERVICE_ID, new Long(++nextServiceID)); final Integer ranking = props == null ? null : (Integer) props .get(Constants.SERVICE_RANKING); properties.put(Constants.SERVICE_RANKING, ranking == null ? new Integer(0) : ranking); this.registration = new ServiceRegistrationImpl(); } private void checkService(final Object service, final String[] clazzes) throws IllegalArgumentException { if (service == null) { throw new IllegalArgumentException("ServiceFactory produced /null/"); } for (int i = 0; i < clazzes.length; i++) { try { final Class<?> current = Class.forName(clazzes[i], false, service.getClass().getClassLoader()); if (!current.isInstance(service)) { throw new IllegalArgumentException("Service " + service.getClass().getName() + " does not implement the interface " + clazzes[i]); } } catch (final ClassNotFoundException e) { throw new IllegalArgumentException("Interface " + clazzes[i] + " implemented by service " + service.getClass().getName() + " cannot be located: " + e.getMessage()); } } } void invalidate() { service = null; useCounters.clear(); bundle = null; registration = null; if (cachedServices != null) { cachedServices = null; } } /** * get the bundle that has registered the service. * * @return the bundle object. * @see org.osgi.framework.ServiceReference#getBundle() * @category ServiceReference */ public Bundle getBundle() { return bundle; } /** * get a property. * * @param key * the key. * @return the value or null, if the entry does not exist. * @see org.osgi.framework.ServiceReference#getProperty(java.lang.String) * @category ServiceReference */ public Object getProperty(final String key) { // first, try the original case Object result = properties.get(key); if (result != null) { return result; } // then, try the lower case variant result = properties.get(key.toLowerCase()); if (result != null) { return result; } // bad luck, try case insensitive matching of the keys for (final String k : properties.keySet()) { if (k.equalsIgnoreCase(key)) { result = properties.get(k); break; } } return result; } /** * get all property keys. * * @return the array of all keys. * @see org.osgi.framework.ServiceReference#getPropertyKeys() * @category ServiceReference */ public String[] getPropertyKeys() { return properties.keySet().toArray(new String[properties.size()]); } /** * get the using bundles. * * @return the array of all bundles. * @see org.osgi.framework.ServiceReference#getUsingBundles() * @category ServiceReference */ public Bundle[] getUsingBundles() { synchronized (useCounters) { if (useCounters.isEmpty()) { return null; } return useCounters.keySet().toArray(new Bundle[useCounters.size()]); } } // FIXME: concurrency??? private boolean marker = false; /** * get the service object. If the service is a service factory, a cached * value might be returned. * * @param theBundle * the requesting bundle. * @return the service object. */ S getService(final Bundle theBundle) { if (service == null || marker) { return null; } synchronized (useCounters) { if (isServiceFactory) { if (cachedServices == null) { cachedServices = new HashMap<Bundle, S>(1); } final S cachedService = cachedServices.get(theBundle); if (cachedService != null) { incrementCounter(theBundle); return cachedService; } @SuppressWarnings("unchecked") final ServiceFactory<S> factory = (ServiceFactory<S>) service; final S factoredService; try { incrementCounter(theBundle); marker = true; factoredService = factory.getService(theBundle, registration); marker = false; checkService(factoredService, (String[]) properties.get(Constants.OBJECTCLASS)); // catch failed check and exceptions thrown in factory } catch (final IllegalArgumentException iae) { decrementCounter(theBundle); framework.notifyFrameworkListeners(FrameworkEvent.ERROR, bundle, new ServiceException( "Invalid service object", ServiceException.FACTORY_ERROR)); return null; } catch (final Throwable t) { decrementCounter(theBundle); framework.notifyFrameworkListeners(FrameworkEvent.ERROR, bundle, new ServiceException( "Exception while factoring the service", ServiceException.FACTORY_EXCEPTION, t)); return null; } cachedServices.put(theBundle, factoredService); return factoredService; } incrementCounter(theBundle); return service; } } private void incrementCounter(final Bundle theBundle) { Integer counter = useCounters.get(theBundle); if (counter == null) { counter = new Integer(1); } else { counter = new Integer(counter.intValue() + 1); } useCounters.put(theBundle, counter); } private void decrementCounter(final Bundle theBundle) { Integer counter = useCounters.get(theBundle); final int newValue = counter.intValue() - 1; if (newValue == 0) { counter = null; } else { counter = new Integer(newValue); } useCounters.put(theBundle, counter); } /** * unget the service. * * @param theBundle * the using bundle. * @return <tt>false</tt> if the context bundle's use count for the service * is zero or if the service has been unregistered; <tt>true</tt> * otherwise. */ @SuppressWarnings("unchecked") boolean ungetService(final Bundle theBundle) { synchronized (useCounters) { if (service == null) { return false; } Integer counter = useCounters.get(theBundle); if (counter == null) { return false; } if (counter.intValue() == 1) { if (isServiceFactory) { try { ((ServiceFactory<S>) service).ungetService(theBundle, registration, cachedServices.get(theBundle)); // catch exceptions thrown in factory } catch (final Throwable t) { framework.notifyFrameworkListeners( FrameworkEvent.ERROR, bundle, t); } useCounters.remove(theBundle); cachedServices.remove(theBundle); } return true; } else { counter = new Integer(counter.intValue() - 1); useCounters.put(theBundle, counter); return true; } } } /** * get a string representation of the service reference implementation. * * @return the string. * @category Object */ public String toString() { return "ServiceReference{" + service + "}"; } /** * compare service references. * * @param reference * , the reference to compare to * @return integer , return value < 0 if this < reference return value = 0 * if this = reference return value > 0 if tis > reference * @see org.osgi.framework.ServiceReference#compareTo(Object) * @category ServiceReference */ public int compareTo(final Object reference) { if (!(reference instanceof ServiceReferenceImpl) || ((ServiceReferenceImpl<?>) reference).framework != framework) { throw new IllegalArgumentException( "ServiceReference was not created by the same framework instance"); } final ServiceReferenceImpl<?> other = (ServiceReferenceImpl<?>) reference; final int comparedServiceIds = ((Long) properties .get(Constants.SERVICE_ID)).compareTo((Long) other.properties .get(Constants.SERVICE_ID)); if (comparedServiceIds == 0) { return 0; } final int res = ((Integer) properties.get(Constants.SERVICE_RANKING)) .compareTo((Integer) other.properties .get(Constants.SERVICE_RANKING)); if (res < 0) { return -1; } else if (res > 0) { return 1; } if (comparedServiceIds < 0) { return 1; } else { return -1; } } /** * test if bundle and class have same source * * @param theBundle * the bundle * @param className * the class name * @return true if bundle and class have same source * @see org.osgi.framework.ServiceReference#isAssignableTo(Bundle, String) * @category ServiceReference */ public boolean isAssignableTo(final Bundle theBundle, final String className) { // if the bundle is the one that registered the service, we are done if (theBundle == bundle || bundle == framework || theBundle == framework) { return true; } final BundleImpl otherBundle = (BundleImpl) theBundle; final BundleImpl ourBundle = (BundleImpl) bundle; try { return otherBundle.loadClass(className) == ourBundle .loadClass(className); } catch (final ClassNotFoundException e) { return true; } } /** * The service registration. It is a private inner class since this entity * is just once returned to the registrar and never retrieved again. It is * more an additional facet of the service than a separate entity. * * @author Jan S. Rellermeyer */ private final class ServiceRegistrationImpl implements ServiceRegistration<S> { protected ServiceRegistrationImpl() { } /** * get the service reference. * * @return the service reference. * @see org.osgi.framework.ServiceRegistration#getReference() * @category ServiceRegistration */ public ServiceReference<S> getReference() { if (service == null) { throw new IllegalStateException( "Service has already been uninstalled"); } return ServiceReferenceImpl.this; } /** * set some new service properties. * * @param newProps * the new service properties. * @see org.osgi.framework.ServiceRegistration#setProperties(java.util.Dictionary) * @category ServiceRegistration */ public void setProperties(final Dictionary<String, ?> newProps) { /* * The values for service.id and objectClass must not be overwritten */ if (service == null) { throw new IllegalStateException( "Service has already been uninstalled"); } final Map<String, Object> oldProps; // could be called from multiple threads synchronized(properties){ oldProps = new HashMap<String, Object>( properties); final HashMap<String, String> cases = new HashMap<String, String>( properties.size()); for (final String key : properties.keySet()) { final String lower = key.toLowerCase(); if (cases.containsKey(lower)) { throw new IllegalArgumentException( "Properties contain the same key in different case variants"); } cases.put(lower, key); } for (final Enumeration<String> keys = newProps.keys(); keys .hasMoreElements();) { final String key = keys.nextElement(); final Object value = newProps.get(key); final String lower = key.toLowerCase(); if (!forbidden.contains(lower)) { final Object existing = cases.get(lower); if (existing != null) { if (existing.equals(key)) { properties.remove(existing); } else { throw new IllegalArgumentException( "Properties already exists in a different case variant"); } } properties.put(key, value); } } } framework.notifyServiceListeners(ServiceEvent.MODIFIED, ServiceReferenceImpl.this, oldProps); } /** * unregister the service. * * @see org.osgi.framework.ServiceRegistration#unregister() * @category ServiceRegistration */ public void unregister() { if (service == null) { throw new IllegalStateException( "Service has already been uninstalled"); } framework.unregisterService(ServiceReferenceImpl.this); service = null; } } boolean isAssignableTo(final AbstractBundle otherBundle, final String[] clazzes) { for (int j = 0; j < clazzes.length; j++) { if (!isAssignableTo(otherBundle, clazzes[j])) { return false; } } return true; } }