| /** |
| * 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.aries.blueprint.container; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.aries.blueprint.BlueprintConstants; |
| import org.apache.aries.blueprint.ExtendedBlueprintContainer; |
| import org.apache.aries.blueprint.ServiceProcessor; |
| import org.apache.aries.blueprint.di.AbstractRecipe; |
| import org.apache.aries.blueprint.di.CollectionRecipe; |
| import org.apache.aries.blueprint.di.MapRecipe; |
| import org.apache.aries.blueprint.di.Recipe; |
| import org.apache.aries.blueprint.di.Repository; |
| import org.apache.aries.blueprint.utils.JavaUtils; |
| import org.apache.aries.blueprint.utils.ReflectionUtils; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceFactory; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.blueprint.container.ComponentDefinitionException; |
| import org.osgi.service.blueprint.reflect.RefMetadata; |
| import org.osgi.service.blueprint.reflect.ServiceMetadata; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * A <code>Recipe</code> to export services into the OSGi registry. |
| * |
| * @version $Rev$, $Date$ |
| */ |
| public class ServiceRecipe extends AbstractRecipe { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(ServiceRecipe.class); |
| |
| private final ExtendedBlueprintContainer blueprintContainer; |
| private final ServiceMetadata metadata; |
| private final Recipe serviceRecipe; |
| private final CollectionRecipe listenersRecipe; |
| private final MapRecipe propertiesRecipe; |
| private final List<Recipe> explicitDependencies; |
| |
| private Map properties; |
| private final AtomicBoolean registered = new AtomicBoolean(); |
| private final AtomicReference<ServiceRegistration> registration = new AtomicReference<ServiceRegistration>(); |
| private Map registrationProperties; |
| private List<ServiceListener> listeners; |
| private volatile Object service; |
| |
| public ServiceRecipe(String name, |
| ExtendedBlueprintContainer blueprintContainer, |
| ServiceMetadata metadata, |
| Recipe serviceRecipe, |
| CollectionRecipe listenersRecipe, |
| MapRecipe propertiesRecipe, |
| List<Recipe> explicitDependencies) { |
| super(name); |
| this.prototype = false; |
| this.blueprintContainer = blueprintContainer; |
| this.metadata = metadata; |
| this.serviceRecipe = serviceRecipe; |
| this.listenersRecipe = listenersRecipe; |
| this.propertiesRecipe = propertiesRecipe; |
| this.explicitDependencies = explicitDependencies; |
| } |
| |
| public Recipe getServiceRecipe() { |
| return serviceRecipe; |
| } |
| |
| public CollectionRecipe getListenersRecipe() { |
| return listenersRecipe; |
| } |
| |
| @Override |
| public List<Recipe> getConstructorDependencies() { |
| List<Recipe> recipes = new ArrayList<Recipe>(); |
| if (explicitDependencies != null) { |
| recipes.addAll(explicitDependencies); |
| } |
| return recipes; |
| } |
| |
| public List<Recipe> getDependencies() { |
| List<Recipe> recipes = new ArrayList<Recipe>(); |
| if (serviceRecipe != null) { |
| recipes.add(serviceRecipe); |
| } |
| if (listenersRecipe != null) { |
| recipes.add(listenersRecipe); |
| } |
| if (propertiesRecipe != null) { |
| recipes.add(propertiesRecipe); |
| } |
| recipes.addAll(getConstructorDependencies()); |
| return recipes; |
| } |
| |
| protected Object internalCreate() throws ComponentDefinitionException { |
| ServiceRegistrationProxy proxy = new ServiceRegistrationProxy(); |
| addPartialObject(proxy); |
| internalGetService(null, null); // null bundle means we don't want to retrieve the actual service when used with a ServiceFactory |
| return proxy; |
| } |
| |
| public boolean isRegistered() { |
| return registered.get(); |
| } |
| |
| public void register() { |
| if (registered.compareAndSet(false, true)) { |
| createExplicitDependencies(); |
| |
| Hashtable props = new Hashtable(); |
| if (properties == null) { |
| properties = (Map) createRecipe(propertiesRecipe); |
| } |
| props.putAll(properties); |
| if (metadata.getRanking() == 0) { |
| props.remove(Constants.SERVICE_RANKING); |
| } else { |
| props.put(Constants.SERVICE_RANKING, metadata.getRanking()); |
| } |
| String componentName = getComponentName(); |
| if (componentName != null) { |
| props.put(BlueprintConstants.COMPONENT_NAME_PROPERTY, componentName); |
| } else { |
| props.remove(BlueprintConstants.COMPONENT_NAME_PROPERTY); |
| } |
| for (ServiceProcessor processor : blueprintContainer.getProcessors(ServiceProcessor.class)) { |
| processor.updateProperties(new PropertiesUpdater(), props); |
| } |
| |
| registrationProperties = props; |
| |
| Set<String> classes = getClasses(); |
| String[] classArray = classes.toArray(new String[classes.size()]); |
| |
| LOGGER.debug("Registering service {} with interfaces {} and properties {}", |
| new Object[] { name, classes, props }); |
| |
| registration.set(blueprintContainer.registerService(classArray, new TriggerServiceFactory(), props)); |
| } |
| } |
| |
| public void unregister() { |
| if (registered.compareAndSet(true, false)) { |
| LOGGER.debug("Unregistering service {}", name); |
| // This method needs to allow reentrance, so if we need to make sure the registration is |
| // set to null before actually unregistering the service |
| ServiceRegistration reg = registration.get(); |
| if (listeners != null) { |
| LOGGER.debug("Calling listeners for service unregistration"); |
| for (ServiceListener listener : listeners) { |
| listener.unregister(service, registrationProperties); |
| } |
| } |
| if (reg != null) { |
| reg.unregister(); |
| } |
| |
| registration.compareAndSet(reg, null); |
| } |
| } |
| |
| protected ServiceReference getReference() { |
| ServiceRegistration reg = registration.get(); |
| if (reg == null) { |
| throw new IllegalStateException("Service is not registered"); |
| } else { |
| return reg.getReference(); |
| } |
| } |
| |
| protected void setProperties(Dictionary props) { |
| ServiceRegistration reg = registration.get(); |
| if (reg == null) { |
| throw new IllegalStateException("Service is not registered"); |
| } else { |
| reg.setProperties(props); |
| // TODO: set serviceProperties? convert somehow? should listeners be notified of this? |
| } |
| } |
| |
| |
| protected Object internalGetService() { |
| return internalGetService(blueprintContainer.getBundleContext().getBundle(), null); |
| } |
| |
| /** |
| * Create the service object. |
| * We need to synchronize the access to the repository, |
| * but not on this ServiceRecipe instance to avoid deadlock. |
| * When using internalCreate(), no other lock but the on the repository |
| * should be held. |
| * |
| * @param bundle |
| * @param registration |
| * @return |
| */ |
| private Object internalGetService(Bundle bundle, ServiceRegistration registration) { |
| LOGGER.debug("Retrieving service for bundle {} and service registration {}", bundle, registration); |
| if (this.service == null) { |
| synchronized (blueprintContainer.getRepository().getInstanceLock()) { |
| if (this.service == null) { |
| createService(); |
| } |
| } |
| } |
| |
| Object service = this.service; |
| // We need the real service ... |
| if (bundle != null) { |
| if (service instanceof ServiceFactory) { |
| service = ((ServiceFactory) service).getService(bundle, registration); |
| } |
| if (service == null) { |
| throw new IllegalStateException("service is null"); |
| } |
| // Check if the service actually implement all the requested interfaces |
| validateClasses(service); |
| // We're not really interested in the service, but perform some sanity checks nonetheless |
| } else { |
| if (!(service instanceof ServiceFactory)) { |
| // Check if the service actually implement all the requested interfaces |
| validateClasses(service); |
| } |
| } |
| |
| return service; |
| } |
| |
| private void createService() { |
| try { |
| LOGGER.debug("Creating service instance"); |
| service = createRecipe(serviceRecipe); |
| LOGGER.debug("Service created: {}", service); |
| // When the service is first requested, we need to create listeners and call them |
| if (listeners == null) { |
| LOGGER.debug("Creating listeners"); |
| if (listenersRecipe != null) { |
| listeners = (List) createRecipe(listenersRecipe); |
| } else { |
| listeners = Collections.emptyList(); |
| } |
| LOGGER.debug("Listeners created: {}", listeners); |
| if (registered.get()) { |
| LOGGER.debug("Calling listeners for initial service registration"); |
| for (ServiceListener listener : listeners) { |
| listener.register(service, registrationProperties); |
| } |
| } else { |
| LOGGER.debug("Calling listeners for initial service unregistration"); |
| for (ServiceListener listener : listeners) { |
| listener.unregister(service, registrationProperties); |
| } |
| } |
| } |
| } catch (RuntimeException e) { |
| LOGGER.error("Error retrieving service from " + this, e); |
| throw e; |
| } |
| } |
| |
| private void validateClasses(Object service) { |
| // Check if the service actually implement all the requested interfaces |
| if (metadata.getAutoExport() == ServiceMetadata.AUTO_EXPORT_DISABLED) { |
| Set<String> allClasses = new HashSet<String>(); |
| ReflectionUtils.getSuperClasses(allClasses, service.getClass()); |
| ReflectionUtils.getImplementedInterfaces(allClasses, service.getClass()); |
| Set<String> classes = getClasses(); |
| classes.removeAll(allClasses); |
| if (!classes.isEmpty()) { |
| throw new ComponentDefinitionException("The service implementation does not implement the required interfaces: " + classes); |
| } |
| } |
| } |
| |
| public Object getService(Bundle bundle, ServiceRegistration registration) { |
| /** getService() can get called before registerService() returns with the registration object. |
| * So we need to set the registration object in case registration listeners call |
| * getServiceReference(). |
| */ |
| this.registration.compareAndSet(null, registration); |
| return internalGetService(bundle, registration); |
| } |
| |
| public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) { |
| if (this.service instanceof ServiceFactory) { |
| ((ServiceFactory) this.service).ungetService(bundle, registration, service); |
| } |
| } |
| |
| private Set<String> getClasses() { |
| Set<String> classes; |
| switch (metadata.getAutoExport()) { |
| case ServiceMetadata.AUTO_EXPORT_INTERFACES: |
| classes = ReflectionUtils.getImplementedInterfaces(new HashSet<String>(), internalGetService().getClass()); |
| break; |
| case ServiceMetadata.AUTO_EXPORT_CLASS_HIERARCHY: |
| classes = ReflectionUtils.getSuperClasses(new HashSet<String>(), internalGetService().getClass()); |
| break; |
| case ServiceMetadata.AUTO_EXPORT_ALL_CLASSES: |
| classes = ReflectionUtils.getSuperClasses(new HashSet<String>(), internalGetService().getClass()); |
| classes = ReflectionUtils.getImplementedInterfaces(classes, internalGetService().getClass()); |
| break; |
| default: |
| classes = new HashSet<String>(metadata.getInterfaces()); |
| break; |
| } |
| return classes; |
| } |
| |
| private void createExplicitDependencies() { |
| if (explicitDependencies != null) { |
| for (Recipe recipe : explicitDependencies) { |
| createRecipe(recipe); |
| } |
| } |
| } |
| |
| private Object createRecipe(Recipe recipe) { |
| String name = recipe.getName(); |
| Repository repo = blueprintContainer.getRepository(); |
| if (repo.getRecipe(name) != recipe) { |
| repo.putRecipe(name, recipe); |
| } |
| return repo.create(name); |
| } |
| |
| private String getComponentName() { |
| if (metadata.getServiceComponent() instanceof RefMetadata) { |
| RefMetadata ref = (RefMetadata) metadata.getServiceComponent(); |
| return ref.getComponentId(); |
| } else { |
| return null; |
| } |
| } |
| |
| private class TriggerServiceFactory implements ServiceFactory { |
| |
| public Object getService(Bundle bundle, ServiceRegistration registration) { |
| return ServiceRecipe.this.getService(bundle, registration); |
| } |
| |
| public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) { |
| ServiceRecipe.this.ungetService(bundle, registration, service); |
| } |
| |
| } |
| |
| private class ServiceRegistrationProxy implements ServiceRegistration { |
| |
| public ServiceReference getReference() { |
| return ServiceRecipe.this.getReference(); |
| } |
| |
| public void setProperties(Dictionary properties) { |
| ServiceRecipe.this.setProperties(properties); |
| } |
| |
| public void unregister() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| private class PropertiesUpdater implements ServiceProcessor.ServicePropertiesUpdater { |
| |
| public String getId() { |
| return metadata.getId(); |
| } |
| |
| public void updateProperties(Dictionary properties) { |
| Hashtable table = JavaUtils.getProperties(ServiceRecipe.this.getReference()); |
| JavaUtils.copy(table, properties); |
| ServiceRecipe.this.setProperties(table); |
| } |
| } |
| |
| } |