/**
* Licensed to the Austrian Association for Software Tool Integration (AASTI)
* under one or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. The AASTI 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.openengsb.core.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.openengsb.core.api.OsgiServiceNotAvailableException;
import org.openengsb.core.api.OsgiUtilsService;
import org.openengsb.core.api.context.ContextHolder;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
public class DefaultOsgiUtilsService implements OsgiUtilsService {
/**
* serves as common invocation handler for proxies that resolve osgi-services dynamically. The proxy tries to
* resolve the service for the given timeout. A timeout of 0 means that the proxy will wait for the service
* indefinitely {@link ServiceTracker#waitForService(long)} A timeout < 0 means that the service tracker will not
* wait for the service at all. If the service is not available immediately an
* {@link OsgiServiceNotAvailableException} is thrown.
*
*/
private final class ServiceTrackerInvocationHandler implements InvocationHandler {
private ServiceTracker tracker;
private Long timeout = -1L;
private final String info;
protected ServiceTrackerInvocationHandler(Filter filter, long timeout) {
this(filter);
this.timeout = timeout;
}
protected ServiceTrackerInvocationHandler(Filter filter) {
tracker = new ServiceTracker(bundleContext, filter, null);
info = filter.toString();
}
protected ServiceTrackerInvocationHandler(String className, long timeout) {
this(className);
this.timeout = timeout;
}
protected ServiceTrackerInvocationHandler(String className) {
tracker = new ServiceTracker(bundleContext, className, null);
info = "Class: " + className;
}
protected ServiceTrackerInvocationHandler(Class<?> targetClass, long timeout) {
this(targetClass.getName(), timeout);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object service = getService();
if (service == null) {
throw new OsgiServiceNotAvailableException("could not resolve service with tracker: " + info);
}
try {
return method.invoke(service, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
private synchronized Object getService() throws InterruptedException {
tracker.open();
try {
if (timeout < 0) {
return tracker.getService();
} else {
return tracker.waitForService(timeout);
}
} finally {
tracker.close();
}
}
}
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultOsgiUtilsService.class);
private static final long DEFAULT_TIMEOUT = 30000L;
private BundleContext bundleContext;
public DefaultOsgiUtilsService() {
}
public DefaultOsgiUtilsService(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
@Override
public <T> T getService(Class<T> clazz) throws OsgiServiceNotAvailableException {
return getService(clazz, DEFAULT_TIMEOUT);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getService(Class<T> clazz, long timeout) throws OsgiServiceNotAvailableException {
ServiceTracker tracker = new ServiceTracker(bundleContext, clazz.getName(), null);
Object result = waitForServiceFromTracker(tracker, timeout);
if (result == null) {
throw new OsgiServiceNotAvailableException(String.format("no service of type %s available at the time",
clazz.getName()));
}
return (T) result;
}
@Override
public Object getService(Filter filter)
throws OsgiServiceNotAvailableException {
return getService(filter, DEFAULT_TIMEOUT);
}
@Override
public Object getService(Filter filter, long timeout) throws OsgiServiceNotAvailableException {
ServiceTracker t = new ServiceTracker(bundleContext, filter, null);
LOGGER.debug("getting service for filter {} from tracker", filter);
Object result = waitForServiceFromTracker(t, timeout);
if (result == null) {
throw new OsgiServiceNotAvailableException(String.format(
"no service matching filter \"%s\" available at the time", filter.toString()));
}
return result;
}
@Override
public Object getService(String filterString) throws OsgiServiceNotAvailableException {
return getService(filterString, DEFAULT_TIMEOUT);
}
@Override
public Object getService(String filterString, long timeout) throws OsgiServiceNotAvailableException {
return getService(FilterUtils.createFilter(filterString), timeout);
}
@Override
public <T> T getServiceWithId(Class<? extends T> clazz, String id) throws OsgiServiceNotAvailableException {
return getServiceWithId(clazz, id, DEFAULT_TIMEOUT);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getServiceWithId(Class<? extends T> clazz, String id, long timeout)
throws OsgiServiceNotAvailableException {
return (T) getServiceWithId(clazz.getName(), id, timeout);
}
@Override
public Object getServiceWithId(String className, String id) throws OsgiServiceNotAvailableException {
return getServiceWithId(className, id, DEFAULT_TIMEOUT);
}
@Override
public Object getServiceWithId(String className, String id, long timeout) throws OsgiServiceNotAvailableException {
Filter filter = FilterUtils.makeFilter(className, String.format("(%s=%s)", Constants.SERVICE_PID, id));
return getService(filter, timeout);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getOsgiServiceProxy(final Filter filter, Class<T> targetClass, final long timeout) {
return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class<?>[]{ targetClass },
new ServiceTrackerInvocationHandler(filter, timeout));
}
@Override
public <T> T getOsgiServiceProxy(final String filter, Class<T> targetClass, long timeout) {
return getOsgiServiceProxy(FilterUtils.createFilter(filter), targetClass, timeout);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getOsgiServiceProxy(Class<T> targetClass, long timeout) {
return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class<?>[]{ targetClass },
new ServiceTrackerInvocationHandler(targetClass, timeout));
}
@Override
public <T> T getOsgiServiceProxy(Class<T> targetClass) {
return getOsgiServiceProxy(targetClass, DEFAULT_TIMEOUT);
}
@Override
public <T> T getOsgiServiceProxy(Filter filter, Class<T> targetClass) {
return getOsgiServiceProxy(filter, targetClass, DEFAULT_TIMEOUT);
}
@Override
public <T> T getOsgiServiceProxy(String filter, Class<T> targetClass) {
return getOsgiServiceProxy(filter, targetClass, DEFAULT_TIMEOUT);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getServiceForLocation(Class<T> clazz, String location, String context)
throws OsgiServiceNotAvailableException, IllegalArgumentException {
Filter compiled = OsgiUtils.getFilterForLocation(clazz, location, context);
return (T) getService(compiled);
}
@Override
public Object getServiceForLocation(String location, String context) throws OsgiServiceNotAvailableException,
IllegalArgumentException {
return getService(OsgiUtils.getFilterForLocation(location, context));
}
@Override
public Object getServiceForLocation(String location) throws OsgiServiceNotAvailableException,
IllegalArgumentException {
LOGGER.debug("retrieve service for location: {}", location);
return getService(OsgiUtils.getFilterForLocation(location));
}
@Override
public <T> T getServiceForLocation(Class<T> clazz, String location) throws OsgiServiceNotAvailableException,
IllegalArgumentException {
return getServiceForLocation(clazz, location, ContextHolder.get().getCurrentContextId());
}
/**
* tries to retrieve the service from the given service-tracker for the amount of milliseconds provided by the given
* timeout.
*
* @throws OsgiServiceNotAvailableException if the service could not be found within the given timeout
*/
private static Object waitForServiceFromTracker(ServiceTracker tracker, long timeout)
throws OsgiServiceNotAvailableException {
synchronized (tracker) {
tracker.open();
try {
return tracker.waitForService(timeout);
} catch (InterruptedException e) {
throw new OsgiServiceNotAvailableException(e);
} finally {
tracker.close();
}
}
}
@Override
public <T> List<ServiceReference<T>> listServiceReferences(Class<T> clazz) {
return listServiceReferences(clazz, null);
}
@Override
public List<ServiceReference<?>> listServiceReferences(String filter) {
ServiceReference<?>[] serviceReferences;
try {
serviceReferences = bundleContext.getServiceReferences((String) null, filter);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException(e);
}
if (serviceReferences == null) {
return Collections.emptyList();
}
return Arrays.asList(serviceReferences);
}
@Override
public <T> List<ServiceReference<T>> listServiceReferences(Class<T> clazz, String filter) {
Collection<ServiceReference<T>> serviceReferences;
try {
serviceReferences = bundleContext.getServiceReferences(clazz, filter);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException(e);
}
return Lists.newArrayList(serviceReferences);
}
@Override
public <T> List<T> listServices(Class<T> clazz) {
ServiceTracker tracker = new ServiceTracker(bundleContext, clazz.getName(), null);
return getListFromTracker(tracker);
}
private <T> List<T> getListFromTracker(ServiceTracker tracker) {
tracker.open();
Object[] services = tracker.getServices();
List<T> result = new ArrayList<T>();
if (services != null) {
CollectionUtils.addAll(result, services);
}
tracker.close();
return result;
}
@Override
public <T> List<T> listServices(Class<T> clazz, String filterString) throws IllegalArgumentException {
Filter filter = FilterUtils.makeFilter(clazz, filterString);
ServiceTracker tracker = new ServiceTracker(bundleContext, filter, null);
return getListFromTracker(tracker);
}
@Override
public <T> Iterator<T> getServiceIterator(Iterable<ServiceReference<T>> references) {
return Iterators.transform(references.iterator(), new Function<ServiceReference<T>, T>() {
@Override
public T apply(ServiceReference<T> input) {
return bundleContext.getService(input);
}
});
}
@Override
public <T> Iterator<T> getServiceIterator(Iterable<ServiceReference> references, Class<T> serviceType) {
return Iterators.transform(references.iterator(), new Function<ServiceReference, T>() {
@Override
public T apply(ServiceReference input) {
return (T) bundleContext.getService(input);
}
});
}
/**
* Any bundle-context is fine here, since it does not matter from which bundlecontext services are retrieved.
*/
public void setBundleContext(BundleContext bundleContext) {
this.bundleContext = bundleContext;
}
}