blob: 02bdfec462f9b4f7ae7f9ddf80b603ef8c6302ff [file] [log] [blame]
/*
* 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.felix.dependencymanager.dependencies;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.apache.felix.dependencymanager.DependencyActivatorBase;
import org.apache.felix.dependencymanager.DependencyService;
import org.apache.felix.dependencymanager.impl.Logger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
/**
* A Temporal Service dependency that can block the caller thread between service updates. Only
* useful for required stateless dependencies that can be replaced transparently.
* A Dynamic Proxy is used to wrap the actual service dependency. When the dependency goes
* away, an attempt is made to replace it with another one which satisfies the service dependency
* criteria. If no service replacement is available, then any method invocation (through the
* dynamic proxy) will block during a configurable timeout. On timeout, an unchecked ServiceUnavailable
* exception is raised (but the service is not deactivated).<p>
*
* When an OSGi update takes place, we use the following locking strategy: A Read/Write lock is used
* to synchronize the updating thread with respect to the other threads which invoke the backed service.
* The Updating thread uses an exclusive Write lock and service invokers uses a Read lock. This model
* allows multiple threads to invoke the backed service concurrently, but the updating thread won't
* update the dependency if it's currently in use.<p>
*
* <b>This class only supports required dependencies, and temporal dependencies must be accessed outside
* the Activator (OSGi) thread, because method invocations may block the caller thread when dependencies
* are not satisfied.
* </b>
*
* <p> Sample Code:<p>
* <blockquote>
*
* <pre>
* import org.apache.felix.dependencymanager.*;
*
* public class Activator extends DependencyActivatorBase {
* public void init(BundleContext ctx, DependencyManager dm) throws Exception {
* dm.add(createService()
* .setImplementation(MyServer.class)
* .add(createTemporalServiceDependency()
* .setService(MyDependency.class)
* .setTimeout(15000)));
* }
*
* public void destroy(BundleContext ctx, DependencyManager dm) throws Exception {
* }
* }
*
* class MyServer implements Runnable {
* MyDependency _dependency; // Auto-Injected by reflection.
* void start() {
* (new Thread(this)).start();
* }
*
* public void run() {
* try {
* _dependency.doWork();
* } catch (ServiceUnavailableException e) {
* t.printStackTrace();
* }
* }
* </pre>
*
* </blockquote>
*/
public class TemporalServiceDependency extends ServiceDependency implements InvocationHandler {
// Max millis to wait for service availability.
private long m_timeout = 30000;
/**
* Creates a new Temporal Service Dependency.
*
* @param context The bundle context of the bundle which is instantiating this dependency object
* @param logger the logger our Internal logger for logging events.
* @see DependencyActivatorBase#createTemporalServiceDependency()
*/
public TemporalServiceDependency(BundleContext context, Logger logger) {
super(context, logger);
super.setRequired(true);
}
/**
* Sets the timeout for this temporal dependency. Specifying a timeout value of zero means that there is no timeout period,
* and an invocation on a missing service will fail immediately.
*
* @param timeout the dependency timeout value greater or equals to 0
* @throws IllegalArgumentException if the timeout is negative
* @return this temporal dependency
*/
public TemporalServiceDependency setTimeout(long timeout) {
if (timeout < 0) {
throw new IllegalArgumentException("Invalid timeout value: " + timeout);
}
m_timeout = timeout;
return this;
}
/**
* The ServiceTracker calls us here in order to inform about a service arrival.
*/
public synchronized void addedService(ServiceReference ref, Object service) {
boolean makeAvailable = makeAvailable();
if (makeAvailable) {
m_serviceInstance = Proxy.newProxyInstance(m_trackedServiceName.getClassLoader(), new Class[] { m_trackedServiceName }, this);
}
Object[] services = m_services.toArray();
for (int i = 0; i < services.length; i++) {
DependencyService ds = (DependencyService) services[i];
if (makeAvailable) {
ds.dependencyAvailable(this);
}
}
if (!makeAvailable) {
notifyAll();
}
}
/**
* The ServiceTracker calls us here when a tracked service properties are modified.
*/
public void modifiedService(ServiceReference ref, Object service) {
// We don't care.
}
/**
* The ServiceTracker calls us here when a tracked service is lost.
*/
public synchronized void removedService(ServiceReference ref, Object service) {
// Unget what we got in addingService (see ServiceTracker 701.4.1)
m_context.ungetService(ref);
}
/**
* @returns our dependency instance. Unlike in ServiceDependency, we always returns our proxy.
*/
public synchronized Object getService() {
return m_serviceInstance;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object service = m_tracker.getService();
if (service == null) {
synchronized (this) {
long start = System.currentTimeMillis();
long waitTime = m_timeout;
while (service == null) {
if (waitTime <= 0) {
throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
}
try {
wait(waitTime);
}
catch (InterruptedException e) {
throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
}
waitTime = m_timeout - (System.currentTimeMillis() - start);
service = m_tracker.getService();
}
}
}
try {
return method.invoke(service, args);
}
catch (IllegalAccessException iae) {
method.setAccessible(true);
return method.invoke(service, args);
}
}
}