/*
* Copyright 2002-2006 the original author or authors.
*
* Licensed 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.
*
* Created on 26-Jan-2006 by Adrian Colyer
*/
package org.springframework.osgi.service;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.target.HotSwappableTargetSource;
/**
* @author Adrian Colyer
* @since 2.0
*/
public class OsgiServiceInterceptor implements MethodBeforeAdvice, ServiceListener {
private static final Log log = LogFactory.getLog(OsgiServiceInterceptor.class);
private final BundleContext bundleContext;
private final HotSwappableTargetSource targetSource;
private final Class serviceType;
private final String lookupFilter;
private ServiceReference serviceReference;
private boolean serviceUnavailable = false;
private int maxRetries = OsgiServiceProxyFactoryBean.DEFAULT_MAX_RETRIES;
private long retryIntervalMillis = OsgiServiceProxyFactoryBean.DEFAULT_MILLIS_BETWEEN_RETRIES;
/**
* @param context
* @param reference
* @param targetSource
*/
public OsgiServiceInterceptor(
BundleContext context,
ServiceReference reference,
HotSwappableTargetSource targetSource,
Class serviceType,
String lookupFilter) {
this.bundleContext = context;
this.serviceReference = reference;
this.targetSource = targetSource;
this.serviceType = serviceType;
this.lookupFilter = lookupFilter;
registerAsServiceListener();
}
/**
* The maximum number of times that we should attempt to rebind to a
* service that has been unregistered.
*
* @param maxRetries
*/
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
/**
* Number of milliseconds to wait between retry attempts when the target
* service has been unregistered.
*
* @param interval
*/
public void setRetryIntervalMillis(long interval) {
this.retryIntervalMillis = interval;
}
/* (non-Javadoc)
* @see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method, java.lang.Object[], java.lang.Object)
*/
public synchronized void before(Method method, Object[] args, Object target) throws Throwable {
if (this.serviceUnavailable) {
int numAttempts = 0;
while ((numAttempts++ < this.maxRetries) && !rebindToService()) {
Thread.sleep(this.retryIntervalMillis);
}
if (this.serviceUnavailable) {
// no luck!
throw new ServiceUnavailableException(
"The target OSGi service of type '" + this.serviceType +
"' matching filter '" + this.lookupFilter + "' was unregistered " +
"and no suitable replacement was found after retrying " +
this.maxRetries + " times.",
this.serviceType,
this.lookupFilter);
}
}
}
/**
* Start listening to service events in case the service we are proxying
* becomes unavailable.
*/
private void registerAsServiceListener() {
this.bundleContext.addServiceListener(this);
}
/* (non-Javadoc)
* @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
*/
public void serviceChanged(ServiceEvent event) {
if (event.getServiceReference().equals(this.serviceReference)) {
// something has changed in the target service
switch (event.getType()) {
case ServiceEvent.REGISTERED:
// no-op for now maybe we will need to do something here in time?
break;
case ServiceEvent.UNREGISTERING:
handleServiceUnavailable();
break;
case ServiceEvent.MODIFIED:
handleServiceModified();
break;
default:
throw new IllegalStateException("Unrecognised OSGi ServiceEvent type: " + event.getType());
}
}
}
/**
* The target service has been modified, rebind to it
*/
private void handleServiceModified() {
Object newTarget = this.bundleContext.getService(this.serviceReference);
this.targetSource.swap(newTarget);
log.info("Target OSGi service of type '" + this.serviceType +
"' matched by filter '" + this.lookupFilter + "' was rebound.");
}
/**
*
*/
private synchronized void handleServiceUnavailable() {
if (log.isInfoEnabled()) {
log.info("Target OSGi service of type '" + this.serviceType +
"' matched by filter '" + this.lookupFilter + "' has been unregistered.");
}
this.serviceUnavailable = true;
}
private synchronized boolean rebindToService() {
log.info("Attempting to rebind to OSGi service of type '" + this.serviceType +
"' using filter '" + this.lookupFilter + "'.");
ServiceReference[] sRefs = OsgiServiceUtils.getServices(this.bundleContext, this.serviceType, this.lookupFilter);
if (sRefs.length == 0) {
if (log.isInfoEnabled()) {
log.info("No target OSGi service of type '" + this.serviceType +
"' matching filter '" + this.lookupFilter + "' is available.");
}
return false;
}
this.serviceReference = sRefs[0];
handleServiceModified();
this.serviceUnavailable = false;
return true;
}
}