| /* |
| * 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.connect.felix.framework; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.felix.connect.felix.framework.capabilityset.CapabilitySet; |
| import org.apache.felix.connect.felix.framework.capabilityset.SimpleFilter; |
| import org.apache.felix.connect.felix.framework.wiring.BundleCapabilityImpl; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceEvent; |
| import org.osgi.framework.ServiceException; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.resource.Capability; |
| |
| public class ServiceRegistry |
| { |
| /** Counter for the service id */ |
| private final AtomicLong m_currentServiceId = new AtomicLong(1); |
| |
| // Maps bundle to an array of service registrations. |
| private final ConcurrentMap<Bundle, List<ServiceRegistration<?>>> m_regsMap = new ConcurrentHashMap<Bundle, List<ServiceRegistration<?>>>(); |
| |
| // Capability set for all service registrations. |
| private final CapabilitySet m_regCapSet = new CapabilitySet(Collections.singletonList(Constants.OBJECTCLASS), false); |
| |
| // Maps bundle to an array of usage counts. |
| private final ConcurrentMap<Bundle, UsageCount[]> m_inUseMap = new ConcurrentHashMap<Bundle, UsageCount[]>(); |
| |
| private final ServiceRegistryCallbacks m_callbacks; |
| |
| private final HookRegistry hookRegistry = new HookRegistry(); |
| |
| public ServiceRegistry(final ServiceRegistryCallbacks callbacks) |
| { |
| m_callbacks = callbacks; |
| } |
| |
| /** |
| * Get all service references for a bundle |
| * @param bundle |
| * @return List with all valid service references or {@code null}. |
| */ |
| public ServiceReference<?>[] getRegisteredServices(final Bundle bundle) |
| { |
| final List<ServiceRegistration<?>> regs = m_regsMap.get(bundle); |
| if (regs != null) |
| { |
| final List<ServiceReference<?>> refs = new ArrayList<ServiceReference<?>>(regs.size()); |
| // this is a per bundle list, therefore synchronizing this should be fine |
| synchronized ( regs ) |
| { |
| for (final ServiceRegistration<?> reg : regs) |
| { |
| try |
| { |
| refs.add(reg.getReference()); |
| } |
| catch (final IllegalStateException ex) |
| { |
| // Don't include the reference as it is not valid anymore |
| } |
| } |
| } |
| return refs.toArray(new ServiceReference[refs.size()]); |
| } |
| return null; |
| } |
| |
| /** |
| * Register a new service |
| * |
| * Caller must fire service event as this method is not doing it! |
| * |
| * @param bundle The bundle registering the service |
| * @param classNames The service class names |
| * @param svcObj The service object |
| * @param dict Optional service properties |
| * @return Service registration |
| */ |
| public ServiceRegistration<?> registerService( |
| final Bundle bundle, |
| final String[] classNames, |
| final Object svcObj, |
| final Dictionary<?,?> dict) |
| { |
| // Create the service registration. |
| final ServiceRegistrationImpl reg = new ServiceRegistrationImpl( |
| this, bundle, classNames, m_currentServiceId.getAndIncrement(), svcObj, dict); |
| |
| // Keep track of registered hooks. |
| this.hookRegistry.addHooks(classNames, svcObj, reg.getReference()); |
| |
| // Get the bundles current registered services. |
| final List<ServiceRegistration<?>> newRegs = new ArrayList<ServiceRegistration<?>>(); |
| List<ServiceRegistration<?>> regs = m_regsMap.putIfAbsent(bundle, newRegs); |
| if (regs == null) |
| { |
| regs = newRegs; |
| } |
| // this is a per bundle list, therefore synchronizing this should be fine |
| synchronized ( regs ) |
| { |
| regs.add(reg); |
| } |
| m_regCapSet.addCapability((BundleCapabilityImpl) reg.getReference()); |
| |
| return reg; |
| } |
| |
| /** |
| * Unregister a service |
| * @param bundle The bundle unregistering the service |
| * @param reg The service registration |
| */ |
| public void unregisterService( |
| final Bundle bundle, |
| final ServiceRegistration<?> reg) |
| { |
| // If this is a hook, it should be removed. |
| this.hookRegistry.removeHooks(reg.getReference()); |
| |
| // Now remove the registered service. |
| final List<ServiceRegistration<?>> regs = m_regsMap.get(bundle); |
| if (regs != null) |
| { |
| // this is a per bundle list, therefore synchronizing this should be fine |
| synchronized ( regs ) |
| { |
| regs.remove(reg); |
| } |
| } |
| m_regCapSet.removeCapability((BundleCapabilityImpl) reg.getReference()); |
| |
| // Notify callback objects about unregistering service. |
| if (m_callbacks != null) |
| { |
| m_callbacks.serviceChanged( |
| new ServiceEvent(ServiceEvent.UNREGISTERING, reg.getReference()), null); |
| } |
| |
| // Now forcibly unget the service object for all stubborn clients. |
| final ServiceReference<?> ref = reg.getReference(); |
| ungetServices(ref); |
| |
| // Invalidate registration |
| ((ServiceRegistrationImpl) reg).invalidate(); |
| |
| // Bundles are allowed to get a reference while unregistering |
| // get fresh set of bundles (should be empty, but this is a sanity check) |
| ungetServices(ref); |
| |
| // Now flush all usage counts as the registration is invalid |
| for (Bundle usingBundle : m_inUseMap.keySet()) |
| { |
| flushUsageCount(usingBundle, ref, null); |
| } |
| } |
| |
| private void ungetServices(final ServiceReference<?> ref) |
| { |
| final Bundle[] clients = getUsingBundles(ref); |
| for (int i = 0; (clients != null) && (i < clients.length); i++) |
| { |
| final UsageCount[] usages = m_inUseMap.get(clients[i]); |
| for (int x = 0; (usages != null) && (x < usages.length); x++) |
| { |
| if (usages[x].m_ref.equals(ref)) |
| { |
| ungetService(clients[i], ref, (usages[x].m_prototype ? usages[x].getService() : null)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method retrieves all services registrations for the specified bundle and |
| * invokes {@code ServiceRegistration.unregister()} on each one. This method is |
| * only called be the framework to clean up after a stopped bundle. |
| * |
| * @param bundle the bundle whose services should be unregistered. |
| **/ |
| public void unregisterServices(final Bundle bundle) |
| { |
| // Simply remove all service registrations for the bundle. |
| final List<ServiceRegistration<?>> regs = m_regsMap.remove(bundle); |
| |
| // Note, there is no race condition here with respect to the |
| // bundle registering more services, because its bundle context |
| // has already been invalidated by this point, so it would not |
| // be able to register more services. |
| |
| // Unregister each service. |
| if (regs != null) |
| { |
| final List<ServiceRegistration<?>> copyRefs; |
| // there shouldn't be a need to sync, but just to be safe |
| // we create a copy array and use that for iterating |
| synchronized ( regs ) |
| { |
| copyRefs = new ArrayList<ServiceRegistration<?>>(regs); |
| } |
| for (final ServiceRegistration<?> reg : copyRefs) |
| { |
| if (((ServiceRegistrationImpl) reg).isValid()) |
| { |
| try |
| { |
| reg.unregister(); |
| } |
| catch (final IllegalStateException e) |
| { |
| // Ignore exception if the service has already been unregistered |
| } |
| } |
| } |
| } |
| } |
| |
| public Collection<Capability> getServiceReferences(final String className, SimpleFilter filter) |
| { |
| if ((className == null) && (filter == null)) |
| { |
| // Return all services. |
| filter = new SimpleFilter(null, null, SimpleFilter.MATCH_ALL); |
| } |
| else if ((className != null) && (filter == null)) |
| { |
| // Return services matching the class name. |
| filter = new SimpleFilter(Constants.OBJECTCLASS, className, SimpleFilter.EQ); |
| } |
| else if ((className != null) && (filter != null)) |
| { |
| // Return services matching the class name and filter. |
| final List<SimpleFilter> filters = new ArrayList<SimpleFilter>(2); |
| filters.add(new SimpleFilter(Constants.OBJECTCLASS, className, SimpleFilter.EQ)); |
| filters.add(filter); |
| filter = new SimpleFilter(null, filters, SimpleFilter.AND); |
| } |
| // else just use the specified filter. |
| |
| return m_regCapSet.match(filter, false); |
| } |
| |
| public ServiceReference<?>[] getServicesInUse(final Bundle bundle) |
| { |
| final UsageCount[] usages = m_inUseMap.get(bundle); |
| if (usages != null) |
| { |
| final ServiceReference<?>[] refs = new ServiceReference[usages.length]; |
| for (int i = 0; i < refs.length; i++) |
| { |
| refs[i] = usages[i].m_ref; |
| } |
| return refs; |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <S> S getService(final Bundle bundle, final ServiceReference<S> ref, final boolean isServiceObjects) |
| { |
| // prototype scope is only possible if called from ServiceObjects |
| final boolean isPrototype = isServiceObjects && ref.getProperty(Constants.SERVICE_SCOPE) == Constants.SCOPE_PROTOTYPE; |
| UsageCount usage = null; |
| Object svcObj = null; |
| |
| // Get the service registration. |
| final ServiceRegistrationImpl reg = |
| ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration(); |
| |
| // We don't allow cycles when we call out to the service factory. |
| if ( reg.currentThreadMarked() ) |
| { |
| throw new ServiceException( |
| "ServiceFactory.getService() resulted in a cycle.", |
| ServiceException.FACTORY_ERROR, |
| null); |
| } |
| |
| try |
| { |
| reg.markCurrentThread(); |
| |
| // Make sure the service registration is still valid. |
| if (reg.isValid()) |
| { |
| // Get the usage count, or create a new one. If this is a |
| // prototype, the we'll alway create a new one. |
| usage = obtainUsageCount(bundle, ref, null, isPrototype); |
| |
| // Increment the usage count and grab the already retrieved |
| // service object, if one exists. |
| incrementToPositiveValue(usage.m_count); |
| svcObj = usage.getService(); |
| |
| if ( isServiceObjects ) |
| { |
| incrementToPositiveValue(usage.m_serviceObjectsCount); |
| } |
| |
| // If we have a usage count, but no service object, then we haven't |
| // cached the service object yet, so we need to create one. |
| if (usage != null) |
| { |
| ServiceHolder holder = null; |
| |
| // There is a possibility that the holder is unset between the compareAndSet() and the get() |
| // below. If that happens get() returns null and we may have to set a new holder. This is |
| // why the below section is in a loop. |
| while (holder == null) |
| { |
| ServiceHolder h = new ServiceHolder(); |
| if (usage.m_svcHolderRef.compareAndSet(null, h)) |
| { |
| holder = h; |
| try { |
| svcObj = reg.getService(bundle); |
| holder.m_service = svcObj; |
| } finally { |
| holder.m_latch.countDown(); |
| } |
| } |
| else |
| { |
| holder = usage.m_svcHolderRef.get(); |
| if (holder != null) |
| { |
| boolean interrupted = false; |
| do |
| { |
| try |
| { |
| // Need to ensure that the other thread has obtained |
| // the service. |
| holder.m_latch.await(); |
| if (interrupted) |
| { |
| Thread.currentThread().interrupt(); |
| } |
| interrupted = false; |
| } |
| catch (InterruptedException e) |
| { |
| interrupted = true; |
| Thread.interrupted(); |
| } |
| } |
| while (interrupted); |
| svcObj = holder.m_service; |
| } |
| } |
| |
| // if someone concurrently changed the holder, loop again |
| if (holder != usage.m_svcHolderRef.get()) |
| holder = null; |
| } |
| if (svcObj != null && isPrototype) |
| { |
| UsageCount existingUsage = obtainUsageCount(bundle, ref, svcObj, null); |
| if (existingUsage != null && existingUsage != usage) |
| { |
| flushUsageCount(bundle, ref, usage); |
| usage = existingUsage; |
| incrementToPositiveValue(usage.m_count); |
| if ( isServiceObjects ) |
| { |
| incrementToPositiveValue(usage.m_serviceObjectsCount); |
| } |
| } |
| } |
| } |
| } |
| } |
| finally |
| { |
| reg.unmarkCurrentThread(); |
| |
| if (!reg.isValid() || (svcObj == null)) |
| { |
| flushUsageCount(bundle, ref, usage); |
| } |
| } |
| |
| return (S) svcObj; |
| } |
| |
| // Increment the Atomic Long by 1, and ensure the result is at least 1. |
| // This method uses a loop, optimistic algorithm to do this in a threadsafe |
| // way without locks. |
| private void incrementToPositiveValue(AtomicLong al) |
| { |
| boolean success = false; |
| |
| while (!success) |
| { |
| long oldVal = al.get(); |
| long newVal = Math.max(oldVal + 1L, 1L); |
| checkCountOverflow(newVal); |
| |
| success = al.compareAndSet(oldVal, newVal); |
| } |
| } |
| |
| private void checkCountOverflow(long c) |
| { |
| if (c == Long.MAX_VALUE) |
| { |
| throw new ServiceException( |
| "The use count for the service overflowed.", |
| ServiceException.UNSPECIFIED, |
| null); |
| } |
| } |
| |
| public boolean ungetService(final Bundle bundle, final ServiceReference<?> ref, final Object svcObj) |
| { |
| final ServiceRegistrationImpl reg = |
| ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration(); |
| |
| if ( reg.currentThreadMarked() ) |
| { |
| throw new IllegalStateException( |
| "ServiceFactory.ungetService() resulted in a cycle."); |
| } |
| |
| try |
| { |
| // Mark the current thread to avoid cycles |
| reg.markCurrentThread(); |
| |
| // Get the usage count. |
| UsageCount usage = obtainUsageCount(bundle, ref, svcObj, null); |
| // If there are no cached services, then just return immediately. |
| if (usage == null) |
| { |
| return false; |
| } |
| // if this is a call from service objects and the service was not fetched from |
| // there, return false |
| if ( svcObj != null ) |
| { |
| if (usage.m_serviceObjectsCount.decrementAndGet() < 0) |
| { |
| return false; |
| } |
| } |
| |
| // If usage count will go to zero, then unget the service |
| // from the registration. |
| long count = usage.m_count.decrementAndGet(); |
| try |
| { |
| if (count <= 0) |
| { |
| ServiceHolder holder = usage.m_svcHolderRef.get(); |
| Object svc = holder != null ? holder.m_service : null; |
| |
| if (svc != null) |
| { |
| // Check the count again to ensure that nobody else has just |
| // obtained the service again |
| if (usage.m_count.get() <= 0) |
| { |
| if (usage.m_svcHolderRef.compareAndSet(holder, null)) |
| { |
| // Temporarily increase the usage again so that the |
| // service factory still sees the usage in the unget |
| usage.m_count.incrementAndGet(); |
| try |
| { |
| // Remove reference from usages array. |
| reg.ungetService(bundle, svc); |
| } |
| finally |
| { |
| // now we can decrease the usage again |
| usage.m_count.decrementAndGet(); |
| } |
| |
| } |
| } |
| } |
| } |
| |
| return count >= 0; |
| } |
| finally |
| { |
| if (!reg.isValid()) |
| { |
| usage.m_svcHolderRef.set(null); |
| } |
| |
| // If the registration is invalid or the usage count for a prototype |
| // reached zero, then flush it. Non-prototype services are not flushed |
| // on ungetService() when they reach 0 as this introduces a race |
| // condition of concurrently the same service is obtained via getService() |
| if (!reg.isValid() || (count <= 0 && svcObj != null)) |
| { |
| flushUsageCount(bundle, ref, usage); |
| } |
| } |
| } |
| finally |
| { |
| reg.unmarkCurrentThread(); |
| } |
| } |
| |
| |
| /** |
| * This is a utility method to release all services being |
| * used by the specified bundle. |
| * @param bundle the bundle whose services are to be released. |
| **/ |
| public void ungetServices(final Bundle bundle) |
| { |
| UsageCount[] usages = m_inUseMap.get(bundle); |
| if (usages == null) |
| { |
| return; |
| } |
| |
| // Note, there is no race condition here with respect to the |
| // bundle using more services, because its bundle context |
| // has already been invalidated by this point, so it would not |
| // be able to look up more services. |
| |
| // Remove each service object from the |
| // service cache. |
| for (int i = 0; i < usages.length; i++) |
| { |
| if (usages[i].m_svcHolderRef.get() == null) |
| continue; |
| |
| // Keep ungetting until all usage count is zero. |
| while (ungetService(bundle, usages[i].m_ref, usages[i].m_prototype ? usages[i].getService() : null)) |
| { |
| // Empty loop body. |
| } |
| } |
| } |
| |
| public Bundle[] getUsingBundles(ServiceReference<?> ref) |
| { |
| Bundle[] bundles = null; |
| for (Iterator<Map.Entry<Bundle, UsageCount[]>> iter = m_inUseMap.entrySet().iterator(); iter.hasNext(); ) |
| { |
| Map.Entry<Bundle, UsageCount[]> entry = iter.next(); |
| Bundle bundle = entry.getKey(); |
| UsageCount[] usages = entry.getValue(); |
| for (int useIdx = 0; useIdx < usages.length; useIdx++) |
| { |
| if (usages[useIdx].m_ref.equals(ref) && usages[useIdx].m_count.get() > 0) |
| { |
| // Add the bundle to the array to be returned. |
| if (bundles == null) |
| { |
| bundles = new Bundle[] { bundle }; |
| } |
| else |
| { |
| Bundle[] nbs = new Bundle[bundles.length + 1]; |
| System.arraycopy(bundles, 0, nbs, 0, bundles.length); |
| nbs[bundles.length] = bundle; |
| bundles = nbs; |
| } |
| } |
| } |
| } |
| return bundles; |
| } |
| |
| void servicePropertiesModified(ServiceRegistration<?> reg, Dictionary<?,?> oldProps) |
| { |
| this.hookRegistry.updateHooks(reg.getReference()); |
| if (m_callbacks != null) |
| { |
| m_callbacks.serviceChanged( |
| new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()), oldProps); |
| } |
| } |
| |
| /** |
| * Obtain a UsageCount object, by looking for an existing one or creating a new one (if possible). |
| * This method tries to find a UsageCount object in the {@code m_inUseMap}. If one is found then |
| * this is returned, otherwise a UsageCount object will be created, but this can only be done if |
| * the {@code isPrototype} parameter is not {@code null}. If {@code isPrototype} is {@code TRUE} |
| * then a new UsageCount object will always be created. |
| * @param bundle The bundle using the service. |
| * @param ref The Service Reference. |
| * @param svcObj A Service Object, if applicable. |
| * @param isPrototype {@code TRUE} if we know that this is a prototype, {@ FALSE} if we know that |
| * it isn't. There are cases where we don't know (the pure lookup case), in that case use {@code null}. |
| * @return The UsageCount object if it could be obtained, or {@code null} otherwise. |
| */ |
| UsageCount obtainUsageCount(Bundle bundle, ServiceReference<?> ref, Object svcObj, Boolean isPrototype) |
| { |
| UsageCount usage = null; |
| |
| // This method uses an optimistic concurrency mechanism with a conditional put/replace |
| // on the m_inUseMap. If this fails (because another thread made changes) this thread |
| // retries the operation. This is the purpose of the while loop. |
| boolean success = false; |
| while (!success) |
| { |
| UsageCount[] usages = m_inUseMap.get(bundle); |
| |
| // If we know it's a prototype, then we always need to create a new usage count |
| if (!Boolean.TRUE.equals(isPrototype)) |
| { |
| for (int i = 0; (usages != null) && (i < usages.length); i++) |
| { |
| if (usages[i].m_ref.equals(ref) |
| && ((svcObj == null && !usages[i].m_prototype) || usages[i].getService() == svcObj)) |
| { |
| return usages[i]; |
| } |
| } |
| } |
| |
| // We haven't found an existing usage count object so we need to create on. For this we need to |
| // know whether this is a prototype or not. |
| if (isPrototype == null) |
| { |
| // If this parameter isn't passed in we can't create a usage count. |
| return null; |
| } |
| |
| // Add a new Usage Count. |
| usage = new UsageCount(ref, isPrototype); |
| if (usages == null) |
| { |
| UsageCount[] newUsages = new UsageCount[] { usage }; |
| success = m_inUseMap.putIfAbsent(bundle, newUsages) == null; |
| } |
| else |
| { |
| UsageCount[] newUsages = new UsageCount[usages.length + 1]; |
| System.arraycopy(usages, 0, newUsages, 0, usages.length); |
| newUsages[usages.length] = usage; |
| success = m_inUseMap.replace(bundle, usages, newUsages); |
| } |
| } |
| return usage; |
| } |
| |
| /** |
| * Utility method to flush the specified bundle's usage count for the |
| * specified service reference. This should be called to completely |
| * remove the associated usage count object for the specified service |
| * reference. If the goal is to simply decrement the usage, then get |
| * the usage count and decrement its counter. This method will also |
| * remove the specified bundle from the "in use" map if it has no more |
| * usage counts after removing the usage count for the specified service |
| * reference. |
| * @param bundle The bundle whose usage count should be removed. |
| * @param ref The service reference whose usage count should be removed. |
| **/ |
| void flushUsageCount(Bundle bundle, ServiceReference<?> ref, UsageCount uc) |
| { |
| // This method uses an optimistic concurrency mechanism with conditional modifications |
| // on the m_inUseMap. If this fails (because another thread made changes) this thread |
| // retries the operation. This is the purpose of the while loop. |
| boolean success = false; |
| while (!success) |
| { |
| UsageCount[] usages = m_inUseMap.get(bundle); |
| final UsageCount[] orgUsages = usages; |
| for (int i = 0; (usages != null) && (i < usages.length); i++) |
| { |
| if ((uc == null && usages[i].m_ref.equals(ref)) || (uc == usages[i])) |
| { |
| // If this is the only usage, then point to empty list. |
| if ((usages.length - 1) == 0) |
| { |
| usages = null; |
| } |
| // Otherwise, we need to do some array copying. |
| else |
| { |
| UsageCount[] newUsages = new UsageCount[usages.length - 1]; |
| System.arraycopy(usages, 0, newUsages, 0, i); |
| if (i < newUsages.length) |
| { |
| System.arraycopy( |
| usages, i + 1, newUsages, i, newUsages.length - i); |
| } |
| usages = newUsages; |
| i--; |
| } |
| } |
| } |
| |
| if (usages == orgUsages) |
| return; // no change in map |
| |
| if (orgUsages != null) |
| { |
| if (usages != null) |
| success = m_inUseMap.replace(bundle, orgUsages, usages); |
| else |
| success = m_inUseMap.remove(bundle, orgUsages); |
| } |
| } |
| } |
| |
| public HookRegistry getHookRegistry() |
| { |
| return this.hookRegistry; |
| } |
| |
| static class UsageCount |
| { |
| final ServiceReference<?> m_ref; |
| final boolean m_prototype; |
| |
| final AtomicLong m_count = new AtomicLong(); |
| final AtomicLong m_serviceObjectsCount = new AtomicLong(); |
| final AtomicReference<ServiceHolder> m_svcHolderRef = new AtomicReference<ServiceHolder>(); |
| |
| UsageCount(final ServiceReference<?> ref, final boolean isPrototype) |
| { |
| m_ref = ref; |
| m_prototype = isPrototype; |
| } |
| |
| Object getService() |
| { |
| ServiceHolder sh = m_svcHolderRef.get(); |
| return sh == null ? null : sh.m_service; |
| } |
| } |
| |
| static class ServiceHolder |
| { |
| final CountDownLatch m_latch = new CountDownLatch(1); |
| volatile Object m_service; |
| } |
| |
| public interface ServiceRegistryCallbacks |
| { |
| void serviceChanged(ServiceEvent event, Dictionary<?,?> oldProps); |
| } |
| } |