| /******************************************************************************* |
| * Copyright (c) 2003, 2021 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.internal.serviceregistry; |
| |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import org.eclipse.osgi.internal.debug.Debug; |
| import org.eclipse.osgi.internal.framework.BundleContextImpl; |
| import org.eclipse.osgi.internal.messages.Msg; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.FrameworkEvent; |
| import org.osgi.framework.ServiceException; |
| import org.osgi.framework.ServiceFactory; |
| import org.osgi.framework.ServiceRegistration; |
| |
| /** |
| * This class represents the use of a service by a bundle. One is created for each |
| * service acquired by a bundle. |
| * |
| * <p> |
| * This class manages a service factory. |
| * |
| * @ThreadSafe |
| */ |
| public class ServiceFactoryUse<S> extends ServiceUse<S> { |
| /** BundleContext associated with this service use */ |
| final BundleContextImpl context; |
| /** ServiceFactory object */ |
| final ServiceFactory<S> factory; |
| final Debug debug; |
| |
| /** Service object returned by ServiceFactory.getService() */ |
| /* @GuardedBy("this") */ |
| private S cachedService; |
| /** true if we are calling the factory getService method. Used to detect recursion. */ |
| /* @GuardedBy("this") */ |
| private boolean factoryInUse; |
| |
| /** |
| * Constructs a service use encapsulating the service factory. |
| * |
| * @param context bundle getting the service |
| * @param registration ServiceRegistration of the service |
| */ |
| ServiceFactoryUse(BundleContextImpl context, ServiceRegistrationImpl<S> registration) { |
| super(context, registration); |
| this.debug = context.getContainer().getConfiguration().getDebug(); |
| this.context = context; |
| this.factoryInUse = false; |
| this.cachedService = null; |
| @SuppressWarnings("unchecked") |
| ServiceFactory<S> f = (ServiceFactory<S>) registration.getServiceObject(); |
| this.factory = f; |
| } |
| |
| /** |
| * Get a service's service object and increment the use count. |
| * |
| * <p>The following steps are followed to get the service object: |
| * <ol> |
| * <li>The use count is incremented by one. |
| * <li>If the use count is now one, |
| * the {@link ServiceFactory#getService(Bundle, ServiceRegistration)} method |
| * is called to create a service object for the context bundle. |
| * This service object is cached. |
| * While the use count is greater than zero, |
| * subsequent calls to get the service object |
| * will return the cached service object. |
| * <br>If the service object returned by the {@link ServiceFactory} |
| * is not an <code>instanceof</code> |
| * all the classes named when the service was registered or |
| * the {@link ServiceFactory} throws an exception, |
| * <code>null</code> is returned and a |
| * {@link FrameworkEvent} of type {@link FrameworkEvent#ERROR} is broadcast. |
| * <li>The service object is returned. |
| * </ol> |
| * |
| * @return The service object. |
| */ |
| /* @GuardedBy("this") */ |
| @Override |
| S getService() { |
| assert Thread.holdsLock(this); |
| if (inUse()) { |
| incrementUse(); |
| return cachedService; |
| } |
| |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("getService[factory=" + registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| // check for recursive call on this thread |
| if (factoryInUse) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println(factory + ".getService() recursively called."); //$NON-NLS-1$ |
| } |
| |
| ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_RECURSION, factory.getClass().getName(), "getService"), ServiceException.FACTORY_RECURSION); //$NON-NLS-1$ |
| context.getContainer().getEventPublisher().publishFrameworkEvent(FrameworkEvent.WARNING, registration.getBundle(), se); |
| return null; |
| } |
| factoryInUse = true; |
| final S service; |
| try { |
| service = factoryGetService(); |
| if (service == null) { |
| return null; |
| } |
| } finally { |
| factoryInUse = false; |
| } |
| |
| this.cachedService = service; |
| incrementUse(); |
| |
| return service; |
| } |
| |
| /** |
| * Unget a service's service object. |
| * |
| * <p> |
| * Decrements the use count if the service was being used. |
| * |
| * <p>The following steps are followed to unget the service object: |
| * <ol> |
| * <li>If the use count is zero, return. |
| * <li>The use count is decremented by one. |
| * <li>If the use count is non zero, return. |
| * <li>The {@link ServiceFactory#ungetService(Bundle, ServiceRegistration, Object)} method |
| * is called to release the service object for the context bundle. |
| * </ol> |
| * @return true if the service was ungotten; otherwise false. |
| */ |
| /* @GuardedBy("this") */ |
| @Override |
| boolean ungetService() { |
| assert Thread.holdsLock(this); |
| if (!inUse()) { |
| return false; |
| } |
| |
| decrementUse(); |
| if (inUse()) { |
| return true; |
| } |
| |
| final S service = cachedService; |
| cachedService = null; |
| |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("ungetService[factory=" + registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| factoryUngetService(service); |
| return true; |
| } |
| |
| /** |
| * Release all uses of the service and reset the use count to zero. |
| * |
| * <ol> |
| * <li>The bundle's use count for this service is set to zero. |
| * <li>The {@link ServiceFactory#ungetService(Bundle, ServiceRegistration, Object)} method |
| * is called to release the service object for the bundle. |
| * </ol> |
| */ |
| /* @GuardedBy("this") */ |
| @Override |
| void release() { |
| super.release(); |
| |
| final S service = cachedService; |
| if (service == null) { |
| return; |
| } |
| cachedService = null; |
| |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("releaseService[factory=" + registration.getBundle() + "](" + context.getBundleImpl() + "," + registration + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| } |
| factoryUngetService(service); |
| } |
| |
| /** |
| * Return the service object for this service use. |
| * |
| * @return The service object. |
| */ |
| /* @GuardedBy("this") */ |
| @Override |
| S getCachedService() { |
| return cachedService; |
| } |
| |
| /** |
| * Call the service factory to get the service. |
| * |
| * @return The service returned by the factory or null if there was an error. |
| */ |
| /* @GuardedBy("this") */ |
| S factoryGetService() { |
| final S service; |
| try { |
| service = AccessController.doPrivileged((PrivilegedAction<S>) () -> factory.getService(context.getBundleImpl(), registration)); |
| } catch (Throwable t) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println(factory + ".getService() exception: " + t.getMessage()); //$NON-NLS-1$ |
| Debug.printStackTrace(t); |
| } |
| // allow the adaptor to handle this unexpected error |
| context.getContainer().handleRuntimeError(t); |
| ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, factory.getClass().getName(), "getService"), ServiceException.FACTORY_EXCEPTION, t); //$NON-NLS-1$ |
| context.getContainer().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se); |
| return null; |
| } |
| |
| if (service == null) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println(factory + ".getService() returned null."); //$NON-NLS-1$ |
| } |
| |
| ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_OBJECT_NULL_EXCEPTION, factory.getClass().getName()), ServiceException.FACTORY_ERROR); |
| context.getContainer().getEventPublisher().publishFrameworkEvent(FrameworkEvent.WARNING, registration.getBundle(), se); |
| return null; |
| } |
| |
| String[] clazzes = registration.getClasses(); |
| String invalidService = ServiceRegistry.checkServiceClass(clazzes, service); |
| if (invalidService != null) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println("Service object is not an instanceof " + invalidService); //$NON-NLS-1$ |
| } |
| ServiceException se = new ServiceException( |
| NLS.bind(Msg.SERVICE_FACTORY_NOT_INSTANCEOF_CLASS_EXCEPTION, |
| new Object[] { factory.getClass().getName(), service.getClass().getName(), |
| invalidService }), |
| ServiceException.FACTORY_ERROR); |
| context.getContainer().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se); |
| return null; |
| } |
| return service; |
| } |
| |
| /** |
| * Call the service factory to unget the service. |
| * |
| * @param service The service object to pass to the factory. |
| */ |
| /* @GuardedBy("this") */ |
| void factoryUngetService(final S service) { |
| try { |
| AccessController.doPrivileged((PrivilegedAction<Void>) () -> { |
| factory.ungetService(context.getBundleImpl(), registration, service); |
| return null; |
| }); |
| } catch (Throwable t) { |
| if (debug.DEBUG_SERVICES) { |
| Debug.println(factory + ".ungetService() exception"); //$NON-NLS-1$ |
| Debug.printStackTrace(t); |
| } |
| |
| ServiceException se = new ServiceException(NLS.bind(Msg.SERVICE_FACTORY_EXCEPTION, factory.getClass().getName(), "ungetService"), ServiceException.FACTORY_EXCEPTION, t); //$NON-NLS-1$ |
| context.getContainer().getEventPublisher().publishFrameworkEvent(FrameworkEvent.ERROR, registration.getBundle(), se); |
| } |
| } |
| } |