blob: 02bdfec462f9b4f7ae7f9ddf80b603ef8c6302ff [file] [log] [blame]
Pierre De Ropb9dee1a2009-12-04 22:59:03 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3 * agreements. See the NOTICE file distributed with this work for additional information
4 * regarding copyright ownership. The ASF licenses this file to you under the Apache License,
5 * Version 2.0 (the "License"); you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless
7 * required by applicable law or agreed to in writing, software distributed under the License is
8 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
9 * or implied. See the License for the specific language governing permissions and limitations
10 * under the License.
11 */
12package org.apache.felix.dependencymanager.dependencies;
13
14import java.lang.reflect.InvocationHandler;
15import java.lang.reflect.Method;
16import java.lang.reflect.Proxy;
17
18import org.apache.felix.dependencymanager.DependencyActivatorBase;
19import org.apache.felix.dependencymanager.DependencyService;
20import org.apache.felix.dependencymanager.impl.Logger;
21import org.osgi.framework.BundleContext;
22import org.osgi.framework.ServiceReference;
23
24/**
25* A Temporal Service dependency that can block the caller thread between service updates. Only
26* useful for required stateless dependencies that can be replaced transparently.
27* A Dynamic Proxy is used to wrap the actual service dependency. When the dependency goes
28* away, an attempt is made to replace it with another one which satisfies the service dependency
29* criteria. If no service replacement is available, then any method invocation (through the
30* dynamic proxy) will block during a configurable timeout. On timeout, an unchecked ServiceUnavailable
31* exception is raised (but the service is not deactivated).<p>
32*
33* When an OSGi update takes place, we use the following locking strategy: A Read/Write lock is used
34* to synchronize the updating thread with respect to the other threads which invoke the backed service.
35* The Updating thread uses an exclusive Write lock and service invokers uses a Read lock. This model
36* allows multiple threads to invoke the backed service concurrently, but the updating thread won't
37* update the dependency if it's currently in use.<p>
38*
39* <b>This class only supports required dependencies, and temporal dependencies must be accessed outside
40* the Activator (OSGi) thread, because method invocations may block the caller thread when dependencies
41* are not satisfied.
42* </b>
43*
44* <p> Sample Code:<p>
45* <blockquote>
46*
47* <pre>
48* import org.apache.felix.dependencymanager.*;
49*
50* public class Activator extends DependencyActivatorBase {
51* public void init(BundleContext ctx, DependencyManager dm) throws Exception {
52* dm.add(createService()
53* .setImplementation(MyServer.class)
54* .add(createTemporalServiceDependency()
55* .setService(MyDependency.class)
56* .setTimeout(15000)));
57* }
58*
59* public void destroy(BundleContext ctx, DependencyManager dm) throws Exception {
60* }
61* }
62*
63* class MyServer implements Runnable {
64* MyDependency _dependency; // Auto-Injected by reflection.
65* void start() {
66* (new Thread(this)).start();
67* }
68*
69* public void run() {
70* try {
71* _dependency.doWork();
72* } catch (ServiceUnavailableException e) {
73* t.printStackTrace();
74* }
75* }
76* </pre>
77*
78* </blockquote>
79*/
80public class TemporalServiceDependency extends ServiceDependency implements InvocationHandler {
81 // Max millis to wait for service availability.
82 private long m_timeout = 30000;
83
84 /**
85 * Creates a new Temporal Service Dependency.
86 *
87 * @param context The bundle context of the bundle which is instantiating this dependency object
88 * @param logger the logger our Internal logger for logging events.
89 * @see DependencyActivatorBase#createTemporalServiceDependency()
90 */
91 public TemporalServiceDependency(BundleContext context, Logger logger) {
92 super(context, logger);
93 super.setRequired(true);
94 }
95
96 /**
97 * Sets the timeout for this temporal dependency. Specifying a timeout value of zero means that there is no timeout period,
98 * and an invocation on a missing service will fail immediately.
99 *
100 * @param timeout the dependency timeout value greater or equals to 0
101 * @throws IllegalArgumentException if the timeout is negative
102 * @return this temporal dependency
103 */
104 public TemporalServiceDependency setTimeout(long timeout) {
105 if (timeout < 0) {
106 throw new IllegalArgumentException("Invalid timeout value: " + timeout);
107 }
108 m_timeout = timeout;
109 return this;
110 }
111
112 /**
113 * The ServiceTracker calls us here in order to inform about a service arrival.
114 */
115 public synchronized void addedService(ServiceReference ref, Object service) {
116 boolean makeAvailable = makeAvailable();
117 if (makeAvailable) {
118 m_serviceInstance = Proxy.newProxyInstance(m_trackedServiceName.getClassLoader(), new Class[] { m_trackedServiceName }, this);
119 }
120 Object[] services = m_services.toArray();
121 for (int i = 0; i < services.length; i++) {
122 DependencyService ds = (DependencyService) services[i];
123 if (makeAvailable) {
124 ds.dependencyAvailable(this);
125 }
126 }
127 if (!makeAvailable) {
128 notifyAll();
129 }
130 }
131
132 /**
133 * The ServiceTracker calls us here when a tracked service properties are modified.
134 */
135 public void modifiedService(ServiceReference ref, Object service) {
136 // We don't care.
137 }
138
139 /**
140 * The ServiceTracker calls us here when a tracked service is lost.
141 */
142 public synchronized void removedService(ServiceReference ref, Object service) {
143 // Unget what we got in addingService (see ServiceTracker 701.4.1)
144 m_context.ungetService(ref);
145 }
146
147 /**
148 * @returns our dependency instance. Unlike in ServiceDependency, we always returns our proxy.
149 */
150 public synchronized Object getService() {
151 return m_serviceInstance;
152 }
153
154 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
155 Object service = m_tracker.getService();
156 if (service == null) {
157 synchronized (this) {
158 long start = System.currentTimeMillis();
159 long waitTime = m_timeout;
160 while (service == null) {
161 if (waitTime <= 0) {
162 throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
163 }
164 try {
165 wait(waitTime);
166 }
167 catch (InterruptedException e) {
168 throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
169 }
170 waitTime = m_timeout - (System.currentTimeMillis() - start);
171 service = m_tracker.getService();
172 }
173 }
174 }
175 try {
176 return method.invoke(service, args);
177 }
178 catch (IllegalAccessException iae) {
179 method.setAccessible(true);
180 return method.invoke(service, args);
181 }
182 }
183}