/*
* 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.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.aries.blueprint.ExtendedReferenceMetadata;
import org.apache.aries.blueprint.di.CollectionRecipe;
import org.apache.aries.blueprint.di.Recipe;
import org.apache.aries.blueprint.di.ValueRecipe;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.osgi.framework.ServiceReference;
import org.osgi.service.blueprint.container.BlueprintEvent;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.osgi.service.blueprint.container.ReifiedType;
import org.osgi.service.blueprint.container.ServiceUnavailableException;
import org.osgi.service.blueprint.reflect.ReferenceMetadata;
import org.osgi.service.blueprint.reflect.ServiceReferenceMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A recipe to create an unary OSGi service reference.
*
* TODO: check synchronization / thread safety
*
* TODO: looks there is a potential problem if the service is unregistered between a call
* to ServiceDispatcher#loadObject() and when the actual invocation finish
*
* @version $Rev: 1509205 $, $Date: 2013-08-01 08:34:18 -0400 (Thu, 01 Aug 2013) $
*/
public class ReferenceRecipe extends AbstractServiceReferenceRecipe {
private static final Logger LOGGER = LoggerFactory.getLogger(ReferenceRecipe.class);
private final ReferenceMetadata metadata;
private Object proxy;
private final Object monitor = new Object();
private volatile ServiceReference trackedServiceReference;
private volatile Object trackedService;
private Object defaultBean;
private final Collection<Class<?>> proxyChildBeanClasses;
private final Collection<WeakReference<Voidable>> proxiedChildren;
public ReferenceRecipe(String name,
ExtendedBlueprintContainer blueprintContainer,
ReferenceMetadata metadata,
ValueRecipe filterRecipe,
CollectionRecipe listenersRecipe,
List<Recipe> explicitDependencies) {
super(name, blueprintContainer, metadata, filterRecipe, listenersRecipe, explicitDependencies);
this.metadata = metadata;
if(metadata instanceof ExtendedReferenceMetadata)
proxyChildBeanClasses = ((ExtendedReferenceMetadata) metadata).getProxyChildBeanClasses();
else
proxyChildBeanClasses = null;
if (proxyChildBeanClasses != null)
proxiedChildren = new ArrayList<WeakReference<Voidable>>();
else
proxiedChildren = null;
}
@Override
protected Object internalCreate() throws ComponentDefinitionException {
try {
if (explicitDependencies != null) {
for (Recipe recipe : explicitDependencies) {
recipe.create();
}
}
// Create the proxy
Set<Class<?>> interfaces = new HashSet<Class<?>>();
Class<?> clz = getInterfaceClass();
if (clz != null) interfaces.add(clz);
proxy = createProxy(new ServiceDispatcher(), interfaces);
// Add partially created proxy to the context
ServiceProxyWrapper wrapper = new ServiceProxyWrapper();
addPartialObject(wrapper);
// Handle initial references
createListeners();
updateListeners();
// Return a ServiceProxy that can injection of references or proxies can be done correctly
return wrapper;
} catch (ComponentDefinitionException e) {
throw e;
} catch (Throwable t) {
throw new ComponentDefinitionException(t);
}
}
protected void doStop() {
synchronized (monitor) {
unbind();
monitor.notifyAll();
}
}
protected void retrack() {
ServiceReference ref = getBestServiceReference();
if (ref != null) {
bind(ref);
} else {
unbind();
}
}
protected void track(ServiceReference ref) {
// TODO: make this behavior configurable through a custom attribute
// TODO: policy = sticky | replace
synchronized (monitor) {
if (trackedServiceReference == null) {
retrack();
}
}
}
protected void untrack(ServiceReference ref) {
// TODO: make this behavior configurable through a custom attribute
// TODO: policy = sticky | replace
synchronized (monitor) {
if (trackedServiceReference == ref) {
retrack();
}
}
}
private void bind(ServiceReference ref) {
LOGGER.debug("Binding reference {} to {}", getName(), ref);
synchronized (monitor) {
ServiceReference oldReference = trackedServiceReference;
trackedServiceReference = ref;
voidProxiedChildren();
bind(trackedServiceReference, proxy);
if (ref != oldReference) {
if (oldReference != null && trackedService != null) {
try {
blueprintContainer.getBundleContext().ungetService(oldReference);
} catch (IllegalStateException ise) {
// In case the service no longer exists lets just cope and ignore.
}
}
trackedService = null;
}
monitor.notifyAll();
}
}
private void unbind() {
LOGGER.debug("Unbinding reference {}", getName());
synchronized (monitor) {
if (trackedServiceReference != null) {
unbind(trackedServiceReference, proxy);
ServiceReference oldReference = trackedServiceReference;
trackedServiceReference = null;
voidProxiedChildren();
if(trackedService != null){
try {
getBundleContextForServiceLookup().ungetService(oldReference);
} catch (IllegalStateException ise) {
// In case the service no longer exists lets just cope and ignore.
}
trackedService = null;
}
monitor.notifyAll();
}
}
}
private Object getService() throws InterruptedException {
synchronized (monitor) {
if (isStarted() && trackedServiceReference == null && metadata.getTimeout() > 0
&& metadata.getAvailability() == ServiceReferenceMetadata.AVAILABILITY_MANDATORY) {
//Here we want to get the blueprint bundle itself, so don't use #getBundleContextForServiceLookup()
blueprintContainer.getEventDispatcher().blueprintEvent(new BlueprintEvent(BlueprintEvent.WAITING, blueprintContainer.getBundleContext().getBundle(), blueprintContainer.getExtenderBundle(), new String[] { getOsgiFilter() }));
monitor.wait(metadata.getTimeout());
}
Object result = null;
if (trackedServiceReference == null) {
if (isStarted()) {
boolean failed = true;
if (metadata.getAvailability() == ReferenceMetadata.AVAILABILITY_OPTIONAL &&
metadata instanceof ExtendedReferenceMetadata) {
if (defaultBean == null) {
String defaultBeanId = ((ExtendedReferenceMetadata)metadata).getDefaultBean();
if (defaultBeanId != null) {
defaultBean = blueprintContainer.getComponentInstance(defaultBeanId);
failed = false;
}
} else {
failed = false;
}
result = defaultBean;
}
if (failed) {
if (metadata.getAvailability() == ServiceReferenceMetadata.AVAILABILITY_MANDATORY) {
LOGGER.info("Timeout expired when waiting for mandatory OSGi service reference {}", getOsgiFilter());
throw new ServiceUnavailableException("Timeout expired when waiting for mandatory OSGi service reference: " + getOsgiFilter(), getOsgiFilter());
} else {
LOGGER.info("No matching service for optional OSGi service reference {}", getOsgiFilter());
throw new ServiceUnavailableException("No matching service for optional OSGi service reference: " + getOsgiFilter(), getOsgiFilter());
}
}
} else {
throw new ServiceUnavailableException("The Blueprint container is being or has been destroyed: " + getOsgiFilter(), getOsgiFilter());
}
} else {
if (trackedService == null) {
trackedService = getServiceSecurely(trackedServiceReference);
}
if (trackedService == null) {
throw new IllegalStateException("getService() returned null for " + trackedServiceReference);
}
result = trackedService;
}
return result;
}
}
private ServiceReference getServiceReference() throws InterruptedException {
synchronized (monitor) {
if (!optional) {
getService();
}
return trackedServiceReference;
}
}
private void voidProxiedChildren() {
if(proxyChildBeanClasses != null) {
synchronized(proxiedChildren) {
for(Iterator<WeakReference<Voidable>> it = proxiedChildren.iterator(); it.hasNext();) {
Voidable v = it.next().get();
if(v == null)
it.remove();
else
v.voidReference();
}
}
}
}
public void addVoidableChild(Voidable v) {
if(proxyChildBeanClasses != null) {
synchronized (proxiedChildren) {
proxiedChildren.add(new WeakReference<Voidable>(v));
}
} else {
throw new IllegalStateException("Proxying of child beans is disabled for this recipe");
}
}
public Collection<Class<?>> getProxyChildBeanClasses() {
return proxyChildBeanClasses;
}
public class ServiceDispatcher implements Callable<Object> {
public Object call() throws Exception {
return getService();
}
}
public class ServiceProxyWrapper implements AggregateConverter.Convertible {
public Object convert(ReifiedType type) throws Exception {
if (type.getRawClass() == ServiceReference.class) {
return getServiceReference();
} else if (type.getRawClass().isInstance(proxy)) {
return proxy;
} else {
throw new ComponentDefinitionException("Unable to convert to " + type);
}
}
}
}