blob: 195fa12b528ec52aabec581cd70670da893dd377 [file] [log] [blame]
Marcel Offermansa962bc92009-11-21 17:59:33 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.apache.felix.dependencymanager;
20
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.lang.reflect.Modifier;
24import java.lang.reflect.Proxy;
25import java.util.AbstractMap;
26import java.util.Arrays;
27import java.util.Comparator;
28import java.util.HashSet;
29import java.util.Map;
30import java.util.Set;
31
32import org.osgi.framework.BundleContext;
33import org.osgi.framework.Constants;
34import org.osgi.framework.InvalidSyntaxException;
35import org.osgi.framework.ServiceReference;
36
37/**
38 * Service dependency that can track an OSGi service.
39 *
40 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
41 */
42public class ServiceDependency implements Dependency, ServiceTrackerCustomizer, ServiceComponentDependency {
43 private boolean m_isRequired;
44 private Service m_service;
45 private volatile ServiceTracker m_tracker;
46 private BundleContext m_context;
47 private boolean m_isAvailable;
48 private volatile Class m_trackedServiceName;
49 private Object m_nullObject;
50 private volatile String m_trackedServiceFilter;
51 private volatile String m_trackedServiceFilterUnmodified;
52 private volatile ServiceReference m_trackedServiceReference;
53 private volatile boolean m_isStarted;
54 private Object m_callbackInstance;
55 private String m_callbackAdded;
56 private String m_callbackChanged;
57 private String m_callbackRemoved;
58 private boolean m_autoConfig;
59 private ServiceReference m_reference;
60 private Object m_serviceInstance;
61 private final Logger m_logger;
62 private String m_autoConfigInstance;
63 private boolean m_autoConfigInvoked;
64 private Object m_defaultImplementation;
65 private Object m_defaultImplementationInstance;
66
67 private static final Comparator COMPARATOR = new Comparator() {
68 public int getRank(ServiceReference ref) {
69 Object ranking = ref.getProperty(Constants.SERVICE_RANKING);
70 if (ranking != null && (ranking instanceof Integer)) {
71 return ((Integer) ranking).intValue();
72 }
73 return 0;
74 }
75
76 public int compare(Object a, Object b) {
77 ServiceReference ra = (ServiceReference) a, rb = (ServiceReference) b;
78 int ranka = getRank(ra);
79 int rankb = getRank(rb);
80 if (ranka < rankb) {
81 return -1;
82 }
83 else if (ranka > rankb) {
84 return 1;
85 }
86 return 0;
87 }
88 };
89
90 /**
91 * Entry to wrap service properties behind a Map.
92 */
93 private final static class ServicePropertiesMapEntry implements Map.Entry {
94 private final String m_key;
95 private Object m_value;
96
97 public ServicePropertiesMapEntry(String key, Object value) {
98 m_key = key;
99 m_value = value;
100 }
101
102 public Object getKey() {
103 return m_key;
104 }
105
106 public Object getValue() {
107 return m_value;
108 }
109
110 public String toString() {
111 return m_key + "=" + m_value;
112 }
113
114 public Object setValue(Object value) {
115 Object oldValue = m_value;
116 m_value = value;
117 return oldValue;
118 }
119
120 public boolean equals(Object o) {
121 if (!(o instanceof Map.Entry)) {
122 return false;
123 }
124 Map.Entry e = (Map.Entry) o;
125 return eq(m_key, e.getKey()) && eq(m_value, e.getValue());
126 }
127
128 public int hashCode() {
129 return ((m_key == null) ? 0 : m_key.hashCode()) ^ ((m_value == null) ? 0 : m_value.hashCode());
130 }
131
132 private static final boolean eq(Object o1, Object o2) {
133 return (o1 == null ? o2 == null : o1.equals(o2));
134 }
135 }
136
137 /**
138 * Wraps service properties behind a Map.
139 */
140 private final static class ServicePropertiesMap extends AbstractMap {
141 private final ServiceReference m_ref;
142
143 public ServicePropertiesMap(ServiceReference ref) {
144 m_ref = ref;
145 }
146
147 public Object get(Object key) {
148 return m_ref.getProperty(key.toString());
149 }
150
151 public int size() {
152 return m_ref.getPropertyKeys().length;
153 }
154
155 public Set entrySet() {
156 Set set = new HashSet();
157 String[] keys = m_ref.getPropertyKeys();
158 for (int i = 0; i < keys.length; i++) {
159 set.add(new ServicePropertiesMapEntry(keys[i], m_ref.getProperty(keys[i])));
160 }
161 return set;
162 }
163 }
164
165 /**
166 * Creates a new service dependency.
167 *
168 * @param context the bundle context
169 * @param logger the logger
170 */
171 public ServiceDependency(BundleContext context, Logger logger) {
172 m_context = context;
173 m_logger = logger;
174 m_autoConfig = true;
175 }
176
177 public synchronized boolean isRequired() {
178 return m_isRequired;
179 }
180
181 public synchronized boolean isAvailable() {
182 return m_isAvailable;
183 }
184
185 public synchronized boolean isAutoConfig() {
186 return m_autoConfig;
187 }
188
189 public synchronized Object getService() {
190 Object service = null;
191 if (m_isStarted) {
192 service = m_tracker.getService();
193 }
194 if (service == null) {
195 service = getDefaultImplementation();
196 if (service == null) {
197 service = getNullObject();
198 }
199 }
200 return service;
201 }
202
203 public Object lookupService() {
204 Object service = null;
205 if (m_isStarted) {
206 service = m_tracker.getService();
207 }
208 else {
209 ServiceReference[] refs = null;
210 ServiceReference ref = null;
211 if (m_trackedServiceName != null) {
212 if (m_trackedServiceFilter != null) {
213 try {
214 refs = m_context.getServiceReferences(m_trackedServiceName.getName(), m_trackedServiceFilter);
215 if (refs != null) {
216 Arrays.sort(refs, COMPARATOR);
217 ref = refs[0];
218 }
219 }
220 catch (InvalidSyntaxException e) {
221 throw new IllegalStateException("Invalid filter definition for dependency.");
222 }
223 }
224 else if (m_trackedServiceReference != null) {
225 ref = m_trackedServiceReference;
226 }
227 else {
228 ref = m_context.getServiceReference(m_trackedServiceName.getName());
229 }
230 if (ref != null) {
231 service = m_context.getService(ref);
232 }
233 }
234 else {
235 throw new IllegalStateException("Could not lookup dependency, no service name specified.");
236 }
237 }
238 if (service == null) {
239 service = getDefaultImplementation();
240 if (service == null) {
241 service = getNullObject();
242 }
243 }
244 return service;
245 }
246
247 private Object getNullObject() {
248 if (m_nullObject == null) {
249 Class trackedServiceName;
250 synchronized (this) {
251 trackedServiceName = m_trackedServiceName;
252 }
253 try {
254 m_nullObject = Proxy.newProxyInstance(trackedServiceName.getClassLoader(), new Class[] {trackedServiceName}, new DefaultNullObject());
255 }
256 catch (Exception e) {
257 m_logger.log(Logger.LOG_ERROR, "Could not create null object for " + trackedServiceName + ".", e);
258 }
259 }
260 return m_nullObject;
261 }
262
263 private Object getDefaultImplementation() {
264 if (m_defaultImplementation != null) {
265 if (m_defaultImplementation instanceof Class) {
266 try {
267 m_defaultImplementationInstance = ((Class) m_defaultImplementation).newInstance();
268 }
269 catch (Exception e) {
270 m_logger.log(Logger.LOG_ERROR, "Could not create default implementation instance of class " + m_defaultImplementation + ".", e);
271 }
272 }
273 else {
274 m_defaultImplementationInstance = m_defaultImplementation;
275 }
276 }
277 return m_defaultImplementationInstance;
278 }
279
280 public synchronized Class getInterface() {
281 return m_trackedServiceName;
282 }
283
284 public void start(Service service) {
285 synchronized (this) {
286 if (m_isStarted) {
287 throw new IllegalStateException("Service dependency was already started." + m_trackedServiceName);
288 }
289 m_service = service;
290 if (m_trackedServiceName != null) {
291 if (m_trackedServiceFilter != null) {
292 try {
293 m_tracker = new ServiceTracker(m_context, m_context.createFilter(m_trackedServiceFilter), this);
294 }
295 catch (InvalidSyntaxException e) {
296 throw new IllegalStateException("Invalid filter definition for dependency.");
297 }
298 }
299 else if (m_trackedServiceReference != null) {
300 m_tracker = new ServiceTracker(m_context, m_trackedServiceReference, this);
301 }
302 else {
303 m_tracker = new ServiceTracker(m_context, m_trackedServiceName.getName(), this);
304 }
305 }
306 else {
307 throw new IllegalStateException("Could not create tracker for dependency, no service name specified.");
308 }
309 m_isStarted = true;
310 }
311 m_tracker.open();
312 }
313
314 public void stop(Service service) {
315 synchronized (this) {
316 if (!m_isStarted) {
317 throw new IllegalStateException("Service dependency was not started.");
318 }
319 m_isStarted = false;
320 }
321 m_tracker.close();
322 m_tracker = null;
323 }
324
325 public Object addingService(ServiceReference ref) {
326 Object service = m_context.getService(ref);
327 // first check to make sure the service is actually an instance of our service
328 if (!m_trackedServiceName.isInstance(service)) {
329 return null;
330 }
331
332 // we remember these for future reference, needed for required service callbacks
333 m_reference = ref;
334 m_serviceInstance = service;
335 return service;
336 }
337
338 public void addedService(ServiceReference ref, Object service) {
339 if (makeAvailable()) {
340 m_service.dependencyAvailable(this);
341 // try to invoke callback, if specified, but only for optional dependencies
342 // because callbacks for required dependencies are handled differently
343 if (!isRequired()) {
344 invokeAdded(ref, service);
345 }
346 }
347 else {
348 m_service.dependencyChanged(this);
349 invokeAdded(ref, service);
350 }
351 }
352
353 public void invokeAdded() {
354 invokeAdded(m_reference, m_serviceInstance);
355 }
356
357 public void invokeAdded(ServiceReference reference, Object serviceInstance) {
358 Object[] callbackInstances = getCallbackInstances();
359 if ((callbackInstances != null) && (m_callbackAdded != null)) {
360 invokeCallbackMethod(callbackInstances, m_callbackAdded, reference, serviceInstance);
361 }
362 }
363
364 public void modifiedService(ServiceReference ref, Object service) {
365 m_reference = ref;
366 m_serviceInstance = service;
367 m_service.dependencyChanged(this);
368 // only invoke the changed callback if the service itself is "active"
369 if (((ServiceImpl) m_service).isRegistered()) {
370 invokeChanged(ref, service);
371 }
372 }
373
374 public void invokeChanged(ServiceReference reference, Object serviceInstance) {
375 Object[] callbackInstances = getCallbackInstances();
376 if ((callbackInstances != null) && (m_callbackChanged != null)) {
377 invokeCallbackMethod(callbackInstances, m_callbackChanged, reference, serviceInstance);
378 }
379 }
380
381 public void removedService(ServiceReference ref, Object service) {
382 if (makeUnavailable()) {
383 m_service.dependencyUnavailable(this);
384 // try to invoke callback, if specified, but only for optional dependencies
385 // because callbacks for required dependencies are handled differently
386 if (!isRequired()) {
387 invokeRemoved(ref, service);
388 }
389 }
390 else {
391 m_service.dependencyChanged(this);
392 invokeRemoved(ref, service);
393 }
394
395 // unget what we got in addingService (see ServiceTracker 701.4.1)
396 m_context.ungetService(ref);
397 }
398
399 public void invokeRemoved() {
400 invokeRemoved(m_reference, m_serviceInstance);
401 }
402
403 public void invokeRemoved(ServiceReference reference, Object serviceInstance) {
404 Object[] callbackInstances = getCallbackInstances();
405 if ((callbackInstances != null) && (m_callbackRemoved != null)) {
406 invokeCallbackMethod(callbackInstances, m_callbackRemoved, reference, serviceInstance);
407 }
408 }
409
410 private synchronized boolean makeAvailable() {
411 if (!m_isAvailable) {
412 m_isAvailable = true;
413 return true;
414 }
415 return false;
416 }
417
418 private synchronized boolean makeUnavailable() {
419 if ((m_isAvailable) && (m_tracker.getServiceReference() == null)) {
420 m_isAvailable = false;
421 return true;
422 }
423 return false;
424 }
425
426 private synchronized Object[] getCallbackInstances() {
427 return m_callbackInstance != null ? new Object[] { m_callbackInstance } : ((ServiceImpl) m_service).getCompositionInstances();
428 }
429
430 private void invokeCallbackMethod(Object[] instances, String methodName, ServiceReference reference, Object service) {
431 for (int i = 0; i < instances.length; i++) {
432 try {
433 invokeCallbackMethod(instances[i], methodName, reference, service);
434 }
435 catch (NoSuchMethodException e) {
436 m_logger.log(Logger.LOG_DEBUG, "Method '" + methodName + "' does not exist on " + instances[i] + ". Callback skipped.");
437 }
438 }
439 }
440
441 private void invokeCallbackMethod(Object instance, String methodName, ServiceReference reference, Object service) throws NoSuchMethodException {
442 Class currentClazz = instance.getClass();
443 boolean done = false;
444 while (!done && currentClazz != null) {
445 Class trackedServiceName;
446 synchronized (this) {
447 trackedServiceName = m_trackedServiceName;
448 }
449 done = invokeMethod(instance, currentClazz, methodName,
450 new Class[][] {{ServiceReference.class, trackedServiceName}, {ServiceReference.class, Object.class}, {ServiceReference.class}, {trackedServiceName}, {Object.class}, {}, {Map.class, trackedServiceName}},
451 new Object[][] {{reference, service}, {reference, service}, {reference}, {service}, {service}, {}, {new ServicePropertiesMap(reference), service}},
452 false);
453 if (!done) {
454 currentClazz = currentClazz.getSuperclass();
455 }
456 }
457 if (!done && currentClazz == null) {
458 throw new NoSuchMethodException(methodName);
459 }
460 }
461
462 private boolean invokeMethod(Object object, Class clazz, String name, Class[][] signatures, Object[][] parameters, boolean isSuper) {
463 Method m = null;
464 for (int i = 0; i < signatures.length; i++) {
465 Class[] signature = signatures[i];
466 try {
467 m = clazz.getDeclaredMethod(name, signature);
468 if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) {
469 m.setAccessible(true);
470 try {
471 m.invoke(object, parameters[i]);
472 }
473 catch (InvocationTargetException e) {
474 m_logger.log(Logger.LOG_ERROR, "Exception while invoking method " + m + ".", e);
475 }
476 // we did find and invoke the method, so we return true
477 return true;
478 }
479 }
480 catch (NoSuchMethodException e) {
481 // ignore this and keep looking
482 }
483 catch (Exception e) {
484 // could not even try to invoke the method
485 m_logger.log(Logger.LOG_ERROR, "Exception while trying to invoke method " + m + ".", e);
486 }
487 }
488 return false;
489 }
490
491 // ----- CREATION
492
493 /**
494 * Sets the name of the service that should be tracked.
495 *
496 * @param serviceName the name of the service
497 * @return this service dependency
498 */
499 public synchronized ServiceDependency setService(Class serviceName) {
500 ensureNotActive();
501 if (serviceName == null) {
502 throw new IllegalArgumentException("Service name cannot be null.");
503 }
504 m_trackedServiceName = serviceName;
505 m_trackedServiceReference = null;
506 m_trackedServiceFilter = null;
507 return this;
508 }
509
510 /**
511 * Sets the name of the service that should be tracked. You can either specify
512 * only the name, or the name and a filter. In the latter case, the filter is used
513 * to track the service and should only return services of the type that was specified
514 * in the name. To make sure of this, the filter is actually extended internally to
515 * filter on the correct name.
516 *
517 * @param serviceName the name of the service
518 * @param serviceFilter the filter condition
519 * @return this service dependency
520 */
521 public synchronized ServiceDependency setService(Class serviceName, String serviceFilter) {
522 ensureNotActive();
523 if (serviceName == null) {
524 throw new IllegalArgumentException("Service name cannot be null.");
525 }
526 m_trackedServiceName = serviceName;
527 if (serviceFilter != null) {
528 m_trackedServiceFilterUnmodified = serviceFilter;
529 m_trackedServiceFilter ="(&(" + Constants.OBJECTCLASS + "=" + serviceName.getName() + ")" + serviceFilter + ")";
530 }
531 else {
532 m_trackedServiceFilterUnmodified = null;
533 m_trackedServiceFilter = null;
534 }
535 m_trackedServiceReference = null;
536 return this;
537 }
538
539 /**
540 * Sets the name of the service that should be tracked. You can either specify
541 * only the name, or the name and a reference. In the latter case, the service reference
542 * is used to track the service and should only return services of the type that was
543 * specified in the name.
544 *
545 * @param serviceName the name of the service
546 * @param serviceReference the service reference to track
547 * @return this service dependency
548 */
549 public synchronized ServiceDependency setService(Class serviceName, ServiceReference serviceReference) {
550 ensureNotActive();
551 if (serviceName == null) {
552 throw new IllegalArgumentException("Service name cannot be null.");
553 }
554 m_trackedServiceName = serviceName;
555 m_trackedServiceReference = serviceReference;
556 m_trackedServiceFilterUnmodified = null;
557 m_trackedServiceFilter = null;
558 return this;
559 }
560
561 /**
562 * Sets the default implementation for this service dependency. You can use this to supply
563 * your own implementation that will be used instead of a Null Object when the dependency is
564 * not available. This is also convenient if the service dependency is not an interface
565 * (which would cause the Null Object creation to fail) but a class.
566 *
567 * @param implementation the instance to use or the class to instantiate if you want to lazily
568 * instantiate this implementation
569 * @return this service dependency
570 */
571 public synchronized ServiceDependency setDefaultImplementation(Object implementation) {
572 ensureNotActive();
573 m_defaultImplementation = implementation;
574 return this;
575 }
576
577 /**
578 * Sets the required flag which determines if this service is required or not.
579 *
580 * @param required the required flag
581 * @return this service dependency
582 */
583 public synchronized ServiceDependency setRequired(boolean required) {
584 ensureNotActive();
585 m_isRequired = required;
586 return this;
587 }
588
589 /**
590 * Sets auto configuration for this service. Auto configuration allows the
591 * dependency to fill in any attributes in the service implementation that
592 * are of the same type as this dependency. Default is on.
593 *
594 * @param autoConfig the value of auto config
595 * @return this service dependency
596 */
597 public synchronized ServiceDependency setAutoConfig(boolean autoConfig) {
598 ensureNotActive();
599 m_autoConfig = autoConfig;
600 m_autoConfigInvoked = true;
601 return this;
602 }
603
604 /**
605 * Sets auto configuration for this service. Auto configuration allows the
606 * dependency to fill in the attribute in the service implementation that
607 * has the same type and instance name.
608 *
609 * @param instanceName the name of attribute to auto config
610 * @return this service dependency
611 */
612 public synchronized ServiceDependency setAutoConfig(String instanceName) {
613 ensureNotActive();
614 m_autoConfig = (instanceName != null);
615 m_autoConfigInstance = instanceName;
616 m_autoConfigInvoked = true;
617 return this;
618 }
619
620 /**
621 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
622 * dependency is added or removed. When you specify callbacks, the auto configuration
623 * feature is automatically turned off, because we're assuming you don't need it in this
624 * case.
625 *
626 * @param added the method to call when a service was added
627 * @param removed the method to call when a service was removed
628 * @return this service dependency
629 */
630 public synchronized ServiceDependency setCallbacks(String added, String removed) {
631 return setCallbacks(null, added, null, removed);
632 }
633
634 /**
635 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
636 * dependency is added, changed or removed. When you specify callbacks, the auto
637 * configuration feature is automatically turned off, because we're assuming you don't
638 * need it in this case.
639 *
640 * @param added the method to call when a service was added
641 * @param changed the method to call when a service was changed
642 * @param removed the method to call when a service was removed
643 * @return this service dependency
644 */
645 public synchronized ServiceDependency setCallbacks(String added, String changed, String removed) {
646 return setCallbacks(null, added, changed, removed);
647 }
648
649 /**
650 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
651 * dependency is added or removed. They are called on the instance you provide. When you
652 * specify callbacks, the auto configuration feature is automatically turned off, because
653 * we're assuming you don't need it in this case.
654 *
655 * @param instance the instance to call the callbacks on
656 * @param added the method to call when a service was added
657 * @param removed the method to call when a service was removed
658 * @return this service dependency
659 */
660 public synchronized ServiceDependency setCallbacks(Object instance, String added, String removed) {
661 return setCallbacks(instance, added, null, removed);
662 }
663
664 /**
665 * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
666 * dependency is added, changed or removed. They are called on the instance you provide. When you
667 * specify callbacks, the auto configuration feature is automatically turned off, because
668 * we're assuming you don't need it in this case.
669 *
670 * @param instance the instance to call the callbacks on
671 * @param added the method to call when a service was added
672 * @param changed the method to call when a service was changed
673 * @param removed the method to call when a service was removed
674 * @return this service dependency
675 */
676 public synchronized ServiceDependency setCallbacks(Object instance, String added, String changed, String removed) {
677 ensureNotActive();
678 // if at least one valid callback is specified, we turn off auto configuration, unless
679 // someone already explicitly invoked autoConfig
680 if ((added != null || removed != null || changed != null) && ! m_autoConfigInvoked) {
681 setAutoConfig(false);
682 }
683 m_callbackInstance = instance;
684 m_callbackAdded = added;
685 m_callbackChanged = changed;
686 m_callbackRemoved = removed;
687 return this;
688 }
689
690 private void ensureNotActive() {
691 if (m_tracker != null) {
692 throw new IllegalStateException("Cannot modify state while active.");
693 }
694 }
695
696 public synchronized String toString() {
697 return "ServiceDependency[" + m_trackedServiceName + " " + m_trackedServiceFilterUnmodified + "]";
698 }
699
700 public String getAutoConfigName() {
701 return m_autoConfigInstance;
702 }
703
704 public String getName() {
705 StringBuilder sb = new StringBuilder();
706 sb.append(m_trackedServiceName.getName());
707 if (m_trackedServiceFilterUnmodified != null) {
708 sb.append(m_trackedServiceFilterUnmodified);
709 }
710 return sb.toString();
711 }
712
713 public int getState() {
714 return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
715 }
716
717 public String getType() {
718 return "service";
719 }
720}